Package rabbit.proxy

Source Code of rabbit.proxy.HttpHeaderReader

package rabbit.proxy;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import rabbit.http.Header;
import rabbit.http.HttpHeader;
import rabbit.util.Logger;
import rabbit.util.TrafficLogger;

/** A handler that reads http headers
*
* @author <a href="mailto:robo@khelekore.org">Robert Olofsson</a>
*/
public class HttpHeaderReader extends BaseSocketHandler
    implements LineListener {
   
    private HttpHeader header;
    private Header head = null;   
    private boolean append = false;
    private boolean request = true;
    private boolean strictHttp = true;
    private HttpHeaderListener reader;
    private boolean headerRead = false;

    // State variables.
    private boolean keepalive = true;
    private boolean ischunked = false;
    private long dataSize = -1;   // -1 for unknown.
    private int startParseAt = 0;

    private TrafficLogger tl;

    private static final ByteBuffer HTTP_IDENTIFIER =
    ByteBuffer.wrap (new byte[]{(byte)'H', (byte)'T', (byte)'T',
        (byte)'P', (byte)'/'});
   
    private static final ByteBuffer EXTRA_LAST_CHUNK =
    ByteBuffer.wrap (new byte[]{(byte)'0', (byte)'\r', (byte)'\n',
        (byte)'\r', (byte)'\n'});
   

    /**
     * @param request true if a request is read, false if a response is read.
     *                Servers may respond without header (HTTP/0.9) so try to
     *                handle that.
     */
    public HttpHeaderReader (SocketChannel channel, ByteBuffer buffer,
           Selector selector, Logger logger, TrafficLogger tl,
           boolean request, boolean strictHttp,
           HttpHeaderListener reader)
  throws IOException {
  super (channel, buffer, selector, logger);
  this.tl = tl;
  this.request = request;
  this.strictHttp = strictHttp;
  this.reader = reader;
  if (buffer != null) {
      if (buffer.hasRemaining ()) {
    startParseAt = buffer.position ();
    parseBuffer ();
      } else {
    buffer.clear ();
      }
  }
    }

    public void timeout () {
  reader.timeout ();
    }
   
    public void run () {
  Logger logger = getLogger ();
  try {
      if (buffer == null)
    allocateBuffer ();
      // read http request
      // make sure we have room for reading.
      int pos = buffer.position ();
      buffer.limit (buffer.capacity ());
      int read = channel.read (buffer);
      if (read == -1) {
    closeDown ();
    reader.closed ();
    return;
      }
      tl.read (read);
      buffer.position (startParseAt);
      buffer.limit (read + pos);
      parseBuffer ();
  } catch (IOException e) {
      logger.logWarn ("Failed to handle connection: " + e);
      reader.failed (e);
  }
    }
   
    private void parseBuffer () throws IOException {
  int startPos = buffer.position ();
  buffer.mark ();
  boolean done = handleBuffer ();
  if (!done) {
      register ();
      int fullPosition = buffer.position ();
      buffer.reset ();
      int pos = buffer.position ();
      if (pos == startPos) {
    if (buffer.remaining () + pos >= buffer.capacity ()) {
        // ok, we did no progress, abort, client is sending
        // too long lines.
        // TODO: perhaps grow buffer....
        throw new RequestLineTooLongException ();
    } else {
        // set back position so the next read aligns...
        buffer.position (fullPosition);
    }   
      } else {
    // ok, some data handled, make space for more.
    buffer.compact ();
    startParseAt = 0;
      }
  } else {
      setState ();     
      unregister ();
      reader.httpHeaderRead (header, buffer,
           keepalive, ischunked, dataSize);
  }
    }

    @Override protected int getSocketOperations () {
  return
      buffer != null && buffer.hasRemaining () ? 0 : SelectionKey.OP_READ;
    }

    private void setState () {
  dataSize = -1;
  String cl = header.getHeader ("Content-Length");
  if (cl != null) {
      try {
    dataSize = Long.parseLong (cl);
      } catch (NumberFormatException e) {
    dataSize = -1;
      }
  }
  String con = header.getHeader ("Connection");
  // Netscape specific header...
  String pcon = header.getHeader ("Proxy-Connection");
  if (con != null && con.equalsIgnoreCase ("close"))
      setKeepAlive (false);
  if (keepalive && pcon != null && pcon.equalsIgnoreCase ("close"))
      setKeepAlive (false);
 
  if (header.isResponse ()) {
      if (header.getResponseHTTPVersion ().equals ("HTTP/1.1")) {
    String chunked = header.getHeader ("Transfer-Encoding");
    setKeepAlive (true);
    ischunked = false;
   
    if (chunked != null && chunked.equalsIgnoreCase ("chunked")) {
        /* If we handle chunked data we must read the whole page
         * before continuing, since the chunk footer must be
         * appended to the header (read the RFC)...
         *
         * As of RFC 2616 this is not true anymore...
         * this means that we throw away footers and it is legal.
         */
        ischunked = true;
        header.removeHeader ("Content-Length");
        dataSize = -1;
    }
      } else {
    setKeepAlive (false);
      }
     
      if (!(dataSize > -1 || ischunked))
    setKeepAlive (false);
  } else {
      String httpVersion = header.getHTTPVersion ();
      if (httpVersion != null && httpVersion.equals ("HTTP/1.1")) {
    String chunked = header.getHeader ("Transfer-Encoding");
    if (chunked != null && chunked.equalsIgnoreCase ("chunked")) {
        ischunked = true;
        header.removeHeader ("Content-Length");
        dataSize = -1;
    }
      }
  }
    }

    /** read the data from the buffer and try to build a http header.
     *
     * @return true if a full header was read, false if more data is needed.
     */
    private boolean handleBuffer () throws IOException {
  if (!request && header == null) {
      if (!verifyResponse ())
    return true;
  }
  LineReader lr = new LineReader (strictHttp);
  while (!headerRead && buffer.hasRemaining ())
      lr.readLine (buffer, this);
  return headerRead;
    }

    /** Verify that the response starts with "HTTP/"
     *  Failure to verify response => treat all of data as content = HTTP/0.9.
     */
    private boolean verifyResponse () throws IOException
  // some broken web servers (apache/2.0.4x) send multiple last-chunks
  if (buffer.remaining () > 4 && matchBuffer (EXTRA_LAST_CHUNK)) {
      Logger log = getLogger ();
      log.logWarn ("Found a last-chunk, trying to ignore it.");
      buffer.position (buffer.position () + EXTRA_LAST_CHUNK.capacity ());
      return verifyResponse ();
  }

  if (buffer.remaining () > 4 && !matchBuffer (HTTP_IDENTIFIER)) {
      Logger log = getLogger ();
      log.logWarn ("http response header with odd start:" +
       getBufferStartString (5));
      header = new HttpHeader ();
      header.setStatusLine ("HTTP/1.1 200 OK");
      header.setHeader ("Connection", "close");
      return true;
  }

  return true;
    }

    private boolean matchBuffer (ByteBuffer test) {
  int len = test.remaining ();
  if (buffer.remaining () < len)
      return false;
  int pos = buffer.position ();
  for (int i = 0; i < len; i++)
      if (buffer.get (pos + i) != test.get (i))
    return false;
  return true;
    }

    private String getBufferStartString (int size) {
  try {
      byte[] arr = new byte[size];
      buffer.get (arr);
      return new String (arr, "ASCII");
  } catch (UnsupportedEncodingException e) {
      return "unable to get ASCII: " + e.toString ();
  }
    }   
   
    /** Handle a newly read line. */
    public void lineRead (String line) throws IOException {
  if (line.length () == 0) {
      headerRead = header != null;
      return;
  }

  if (header == null) {
      header = new HttpHeader ();
      header.setRequestLine (line);
      headerRead = false;
      return;
  }

  if (header.isDot9Request ()) {
      headerRead = true;
      return;
  }

  char c;
  if (header.size () == 0 &&
      line.length () > 0 &&
      ((c = line.charAt (0)) == ' ' || c == '\t')) {
      header.setReasonPhrase (header.getReasonPhrase () + line);
      headerRead = false;
      return;
  }

  readHeader (line);
  headerRead = false;
    }

    public void readHeader (String msg) throws IOException {
  if (msg == null)
      throw (new IOException ("Couldnt read headers, connection must be closed"));
  char c = msg.charAt (0);
  if (c == ' ' || c == '\t' || append) {
      if (head != null) {
    head.append (msg);
    append = checkQuotes (head.getValue ());
      } else {
    throw (new IOException ("Malformed header from: " +
          channel.socket ().getInetAddress () +
          ", msg: " + msg));
      }
      return;
  }
  int i = msg.indexOf (':');     
  if (i < 0) {
      switch (msg.charAt (0)) {
      case 'h':
      case 'H':
    if (msg.toLowerCase ().startsWith ("http/")) {
        /* ignoring header since it looks
         * like a duplicate responseline
         */
        return;
    }
    // fallthrough
      default:
    throw (new IOException ("Malformed header:" + msg));
      }
  }
  int j = i;
  while (j > 0 && ((c = msg.charAt (j - 1)) == ' ' || c == '\t'))
      j--;
  // ok, the header may be empty, so trim away whites.
  String value = msg.substring (i + 1);
 
  /* there are some sites with broken headers
   * like http://docs1.excite.com/functions.js
   * which returns lines such as this (20040416) /robo
   * msg is: 'Cache-control: must-revalidate"'
   * so we only check for append when in strict mode...
   */
  if (strictHttp)
      append = checkQuotes (value);
  if (!append)
      value = value.trim ();
  head = new Header (msg.substring (0, j), value);
  header.addHeader (head);
    }

    private boolean checkQuotes (String v) {
  int q = v.indexOf ('"');
  if (q == -1)
      return false;
  boolean halfquote = false;
  int l = v.length ();
  for (; q < l; q++) {
      char c = v.charAt (q);
      switch (c) {
      case '\\':
    q++;    // skip one...
    break;
      case '"':
    halfquote = !halfquote;
    break;
      }
  }
  return halfquote;
    }

    /** Set the keep alive value to currentkeepalive & keepalive
     * @param keepalive the new keepalive value.
     */
    private void setKeepAlive (boolean keepalive) {
  this.keepalive = (this.keepalive && keepalive);
    }
}
TOP

Related Classes of rabbit.proxy.HttpHeaderReader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.