package http_parser.lolevel;

import java.nio.ByteBuffer; import http_parser.HTTPException; import http_parser.HTTPMethod; import http_parser.HTTPParserUrl; import http_parser.ParserType; import static http_parser.lolevel.HTTPParser.C.*; import static http_parser.lolevel.HTTPParser.State.*;

public class HTTPParser {

      /* lots of unsigned chars here, not sure what
         to about them, `bytes` in java suck...  */

      ParserType type;
      State state;
      HState header_state;
boolean strict;

      int index;
      int flags; // TODO

      int nread;
      long content_length;

int p_start; // updated each call to execute to indicate where the buffer was before we began calling it.

/** READ-ONLY **/
public int http_major;
public int http_minor;
public int status_code;   /* responses only */
public HTTPMethod method; /* requests only */

/* true  = Upgrade header was present and the parser has exited because of that.
 * false = No upgrade header present.
 * Should be checked when http_parser_execute() returns in addition to
 * error checking.
 */
public boolean upgrade;

/** PUBLIC **/
      // TODO : this is used in c to maintain application state.
      // is this even necessary? we have state in java ?
      // consider
// Object data; /* A pointer to get hook to the "connection" or "socket" object */

/*
 * technically we could combine all of these (except for url_mark) into one
 * variable, saving stack space, but it seems more clear to have them
 * separated.
 */
int header_field_mark = -1;
int header_value_mark = -1;
int url_mark = -1;
int body_mark = -1;

/**
 * Construct a Parser for ParserType.HTTP_BOTH, meaning it
 * determines whether it's parsing a request or a response.
 */
      public HTTPParser() {
              this(ParserType.HTTP_BOTH);
      }

/**
 * Construct a Parser and initialise it to parse either
 * requests or responses.
 */
      public HTTPParser(ParserType type) {
              this.type  = type;
              switch(type) {
                      case HTTP_REQUEST:
                              this.state = State.start_req;
                              break;
                      case HTTP_RESPONSE:
                              this.state = State.start_res;
                              break;
                      case HTTP_BOTH:
                              this.state = State.start_req_or_res;
                              break;
                      default:
                              throw new HTTPException("can't happen, invalid ParserType enum");
              }
      }

/*
 * Utility to facilitate System.out.println style debugging (the way god intended)
 */
      static void p(Object o) {System.out.println(o);}

/** Comment from C version follows
 *
 * Our URL parser.
 *
 * This is designed to be shared by http_parser_execute() for URL validation,
 * hence it has a state transition + byte-for-byte interface. In addition, it
 * is meant to be embedded in http_parser_parse_url(), which does the dirty
 * work of turning state transitions URL components for its API.
 *
 * This function should only be invoked with non-space characters. It is
 * assumed that the caller cares about (and can detect) the transition between
 * URL and non-URL states by looking for these.
 */
public State parse_url_char(byte ch) {

  int chi = ch & 0xff;            // utility, ch without signedness for table lookups.

  if(SPACE == ch){
    throw new HTTPException("space as url char");
  }

  switch(state) {
    case req_spaces_before_url:
      /* Proxied requests are followed by scheme of an absolute URI (alpha).
      * All methods except CONNECT are followed by '/' or '*'.
      */
      if(SLASH == ch || STAR == ch){
        return req_path;
      }
      if(isAtoZ(ch)){
        return req_schema;
      }
      break;
    case req_schema:
      if(isAtoZ(ch)){
        return req_schema;
      }
      if(COLON == ch){
        return req_schema_slash;
      }
      break;
    case req_schema_slash:
      if(SLASH == ch){
        return req_schema_slash_slash;
      }
      break;
    case req_schema_slash_slash:
      if(SLASH == ch){
        return req_host_start;
      }
      break;
    case req_host_start:
      if (ch == (byte)'[') {
        return req_host_v6_start;
      }
      if (isHostChar(ch)) {
        return req_host;
      }
      break;

    case req_host:
      if (isHostChar(ch)) {
        return req_host;
      }

    /* FALLTHROUGH */
    case req_host_v6_end:
      switch (ch) {
        case ':':
          return req_port_start;
        case '/':
          return req_path;
        case '?':
          return req_query_string_start;
      }
      break;

    case req_host_v6:
      if (ch == ']') {
        return req_host_v6_end;
      }

    /* FALLTHROUGH */
    case req_host_v6_start:
      if (isHex(ch) || ch == ':') {
        return req_host_v6;
      }
      break;

    case req_port:
      switch (ch) {
        case '/':
          return req_path;
        case '?':
          return req_query_string_start;
      }

    /* FALLTHROUGH */
    case req_port_start:
      if (isDigit(ch)) {
        return req_port;
      }
      break;

    case req_path:
      if (isNormalUrlChar(chi)) {
        return req_path;
      }
      switch (ch) {
        case '?':
          return req_query_string_start;
        case '#':
          return req_fragment_start;
      }

      break;

    case req_query_string_start:
    case req_query_string:
      if (isNormalUrlChar(chi)) {
        return req_query_string;
      }

      switch (ch) {
        case '?':
          /* allow extra '?' in query string */
          return req_query_string;

        case '#':
          return req_fragment_start;
      }

      break;

    case req_fragment_start:
      if (isNormalUrlChar(chi)) {
        return req_fragment;
      }
      switch (ch) {
        case '?':
          return req_fragment;

        case '#':
          return req_fragment_start;
      }
      break;

    case req_fragment:
      if (isNormalUrlChar(ch)) {
        return req_fragment;
      }

      switch (ch) {
        case '?':
        case '#':
          return req_fragment;
      }

      break;
    default:
      break;
  }

  /* We should never fall out of the switch above unless there's an error */
  return dead;
}

/** Execute the parser with the currently available data contained in
 * the buffer. The buffers position() and limit() need to be set
 * correctly (obviously) and a will be updated approriately when the
 * method returns to reflect the consumed data.
 */
public int execute(ParserSettings settings, ByteBuffer data) {

  int p     = data.position();
  this.p_start = p; // this is used for pretty printing errors.
                    // and returning the amount of processed bytes.

  // In case the headers don't provide information about the content
  // length, `execute` needs to be called with an empty buffer to
  // indicate that all the data has been send be the client/server,
  // else there is no way of knowing the message is complete.
  int len = (data.limit() - data.position());
  if (0 == len) {

// if (State.body_identity_eof == state) { // settings.call_on_message_complete(this); // }

  switch (state) {
    case body_identity_eof:
      settings.call_on_message_complete(this);
      return data.position() - this.p_start;

    case dead:
    case start_req_or_res:
    case start_res:
    case start_req:
      return data.position() - this.p_start;

    default:
      // should we really consider this an error!?
      throw new HTTPException("empty bytes! "+state); // error
  }
}

// in case the _previous_ call to the parser only has data to get to
// the middle of certain fields, we need to update marks to point at
// the beginning of the current buffer.
switch (state) {
  case header_field:
    header_field_mark = p;
    break;
  case header_value:
    header_value_mark = p;
    break;
  case req_path:
  case req_schema:
  case req_schema_slash:
  case req_schema_slash_slash:
  case req_host_start:
  case req_host_v6_start:
  case req_host_v6:
  case req_host_v6_end:
  case req_host:
  case req_port_start:
  case req_port:
  case req_query_string_start:
  case req_query_string:
  case req_fragment_start:
  case req_fragment:
    url_mark = p;
    break;
}
boolean reexecute = false;
int pe = 0;
byte ch = 0;
int chi = 0;
byte c = -1;
int to_read = 0;

// this is where the work gets done, traverse the available data...
while (data.position() != data.limit() || reexecute) {
// p(state + “: r: ” + reexecute + “

“ +p );

if(!reexecute){
  p = data.position();
  pe = data.limit();
  ch     = data.get();           // the current character to process.
  chi = ch & 0xff;            // utility, ch without signedness for table lookups.
  c      = -1;                   // utility variably used for up- and downcasing etc.
  to_read =  0;                   // used to keep track of how much of body, etc. is left to read

  if (parsing_header(state)) {
    ++nread;
    if (nread > HTTP_MAX_HEADER_SIZE) {
      return error(settings, "possible buffer overflow", data);
    }
  }
}
reexecute = false;
// p(state + “ :

“ + ch + ” : “ + (((CR == ch) || (LF == ch)) ? ch : (”‘“ + (char)ch + ”’“)) +”: “+p );

switch (state) {
   /*
    * this state is used after a 'Connection: close' message
    * the parser will error out if it reads another message
    */
  case dead:
    if (CR == ch || LF == ch){
      break;
    }
    return error(settings, "Connection already closed", data);

  case start_req_or_res:
    if (CR == ch || LF == ch){
      break;
    }
    flags = 0;
    content_length = -1;

    if (H == ch) {
      state = State.res_or_resp_H;
    } else {
      type   = ParserType.HTTP_REQUEST;
      method = start_req_method_assign(ch);
      if (null == method) {
        return error(settings, "invalid method", data);
      }
      index  = 1;
      state  = State.req_method;
    }
    settings.call_on_message_begin(this);
    break;

  case res_or_resp_H:
    if (T == ch) {
      type  = ParserType.HTTP_RESPONSE;
      state = State.res_HT;
    } else {
      if (E != ch) {
        return error(settings, "not E", data);
      }
      type   = ParserType.HTTP_REQUEST;
      method = HTTPMethod.HTTP_HEAD;
      index  = 2;
      state  = State.req_method;
    }
    break;

  case start_res:
    flags = 0;
    content_length = -1;

    switch(ch) {
      case H:
        state = State.res_H;
        break;
      case CR:
      case LF:
        break;
      default:
        return error(settings, "Not H or CR/LF", data);
    }

    settings.call_on_message_begin(this);
    break;

  case res_H:
    if (strict && T != ch) {
      return error(settings, "Not T", data);
    }
    state = State.res_HT;
    break;
  case res_HT:
    if (strict && T != ch) {

return error(settings, “Not T2”, data);

  }
  state = State.res_HTT;
  break;
case res_HTT:
  if (strict && P != ch) {

return error(settings, “Not P”, data);

  }
  state = State.res_HTTP;
  break;
case res_HTTP:
  if (strict && SLASH != ch) {

return error(settings, “Not ‘/’”, data);

  }
  state = State.res_first_http_major;
  break;

case res_first_http_major:
  if (!isDigit(ch)) {

return error(settings, “Not a digit”, data);

  }
  http_major = (int) ch - 0x30;
  state = State.res_http_major;
  break;

/* major HTTP version or dot */
case res_http_major:
  if (DOT == ch) {
    state = State.res_first_http_minor;
    break;
  }
  if (!isDigit(ch)) {

return error(settings, “Not a digit”, data);

}
http_major *= 10;
http_major += (ch - 0x30);

if (http_major > 999) {

return error(settings, “invalid http major version: ”, data);

  }
  break;

/* first digit of minor HTTP version */
case res_first_http_minor:
  if (!isDigit(ch)) {

return error(settings, “Not a digit”, data);

  }
  http_minor = (int)ch - 0x30;
  state = State.res_http_minor;
  break;

/* minor HTTP version or end of request line */
case res_http_minor:
  if (SPACE == ch) {
    state = State.res_first_status_code;
    break;
  }
  if (!isDigit(ch)) {

return error(settings, “Not a digit”, data);

}
http_minor *= 10;
http_minor += (ch - 0x30);
if (http_minor > 999) {

return error(settings, “invalid http minor version: ”, data);

  }
  break;

case res_first_status_code:
  if (!isDigit(ch)) {
    if (SPACE == ch) {
      break;
    }

return error(settings, “Not a digit (status code)”, data);

  }
  status_code = (int)ch - 0x30;
  state = State.res_status_code;
  break;

case res_status_code:
  if (!isDigit(ch)) {
    switch(ch) {
      case SPACE:
        state = State.res_status;
        break;
      case CR:
        state = State.res_line_almost_done;
        break;
      case LF:
        state = State.header_field_start;
        break;
      default:

return error(settings, “not a valid status code”, data);

  }
  break;
}
status_code *= 10;
status_code += (int)ch - 0x30;
if (status_code > 999) {

return error(settings, “ridiculous status code:”, data);

  }

  if (status_code > 99) {
    settings.call_on_status_complete(this);
  }
  break;

case res_status:
  /* the human readable status. e.g. "NOT FOUND"
   * we are not humans so just ignore this
   * we are not men, we are devo. */

   if (CR == ch) {
    state = State.res_line_almost_done;
    break;
   }
   if (LF == ch) {
    state = State.header_field_start;
    break;
   }
   break;

case res_line_almost_done:
  if (strict && LF != ch) {

return error(settings, “not LF”, data);

  }
  state = State.header_field_start;
  break;

case start_req:
  if (CR==ch || LF == ch) {
    break;
  }
  flags = 0;
  content_length = -1;

  if(!isAtoZ(ch)){
    return error(settings, "invalid method", data);
  }

  method = start_req_method_assign(ch);
  if (null == method) {
    return error(settings, "invalid method", data);
  }
  index  = 1;
  state  = State.req_method;

  settings.call_on_message_begin(this);
  break;

case req_method:
  if (0 == ch) {
    return error(settings, "NULL in method", data);
  }

  byte [] arr = method.bytes;

  if (SPACE == ch && index == arr.length) {
    state = State.req_spaces_before_url;
  } else if (arr[index] == ch) {
    // wuhu!
  } else if (HTTPMethod.HTTP_CONNECT == method) {
      if (1 == index && H == ch) {
        method = HTTPMethod.HTTP_CHECKOUT;
      } else if (2 == index && P == ch) {
        method = HTTPMethod.HTTP_COPY;
      }
  } else if (HTTPMethod.HTTP_MKCOL == method) {
      if        (1 == index && O == ch) {
        method = HTTPMethod.HTTP_MOVE;
      } else if (1 == index && E == ch) {
        method = HTTPMethod.HTTP_MERGE;
      } else if (1 == index && DASH == ch) { /* M-SEARCH */
        method = HTTPMethod.HTTP_MSEARCH;
      } else if (2 == index && A == ch) {
        method = HTTPMethod.HTTP_MKACTIVITY;
      }
  } else if (1 == index && HTTPMethod.HTTP_POST     == method) {
    if(R == ch) {
      method = HTTPMethod.HTTP_PROPFIND; /* or HTTP_PROPPATCH */
    }else if(U == ch){
      method = HTTPMethod.HTTP_PUT; /* or HTTP_PURGE */
    }else if(A == ch){
      method = HTTPMethod.HTTP_PATCH;
    }
  } else if (2 == index) {
    if(HTTPMethod.HTTP_PUT == method) {
      if(R == ch){
        method = HTTPMethod.HTTP_PURGE; 
      }
    }else if(HTTPMethod.HTTP_UNLOCK == method){
      if(S == ch){
        method = HTTPMethod.HTTP_UNSUBSCRIBE;
      }
    }
  }else if(4 == index && HTTPMethod.HTTP_PROPFIND == method && P == ch){
    method = HTTPMethod.HTTP_PROPPATCH;
  } else {
    return error(settings, "Invalid HTTP method", data);
  }

  ++index;
  break;

/******************* URL *******************/
case req_spaces_before_url:
  if (SPACE == ch) {
    break;
  }
  url_mark  = p;
  if(HTTPMethod.HTTP_CONNECT == method){
    state = req_host_start;
  }

  state = parse_url_char(ch);
  if(state == dead){
    return error(settings, "Invalid something", data);
  }
  break;

case req_schema:
case req_schema_slash:
case req_schema_slash_slash:
case req_host_start:
case req_host_v6_start:
case req_host_v6:
case req_port_start:
  switch (ch) {
    /* No whitespace allowed here */
    case SPACE:
    case CR:
    case LF:
      return error(settings, "unexpected char in path", data);
    default:
      state = parse_url_char(ch);
      if(dead == state){
        return error(settings, "unexpected char in path", data);
      }
  }
  break;

case req_host:
case req_host_v6_end:
case req_port:
case req_path:
case req_query_string_start:
case req_query_string:
case req_fragment_start:
case req_fragment:
  switch (ch) {
    case SPACE:
      settings.call_on_url(this, data, url_mark, p-url_mark);
      settings.call_on_path(this, data, url_mark, p - url_mark);
      url_mark = -1;
      state = State.req_http_start;
      break;
    case CR:
    case LF:
      http_major = 0;
      http_minor = 9;
      state = (CR == ch) ? req_line_almost_done : header_field_start;
      settings.call_on_url(this, data, url_mark, p-url_mark); //TODO check params!!!
      settings.call_on_path(this, data, url_mark, p-url_mark);
      url_mark = -1;
      break;
    default:
      state = parse_url_char(ch);
      if(dead == state){
        return error(settings, "unexpected char in path", data);
      }
  }
  break;
/******************* URL *******************/

/******************* HTTP 1.1 *******************/
case req_http_start:
  switch (ch) {
    case H:
      state = State.req_http_H;
      break;
    case SPACE:
      break;
    default:
      return error(settings, "error in req_http_H", data);
  }
  break;

case req_http_H:
  if (strict && T != ch) {
    return error(settings, "unexpected char", data);
  }
  state = State.req_http_HT;
  break;

case req_http_HT:
  if (strict && T != ch) {
    return error(settings, "unexpected char", data);
  }
  state = State.req_http_HTT;
  break;

case req_http_HTT:
  if (strict && P != ch) {
    return error(settings, "unexpected char", data);
  }
  state = State.req_http_HTTP;
  break;

case req_http_HTTP:
  if (strict && SLASH != ch) {
    return error(settings, "unexpected char", data);
  }
  state = req_first_http_major;
  break;

/* first digit of major HTTP version */
case req_first_http_major:
  if (!isDigit(ch)) {

return error(settings, “non digit in http major”, data);

  }
  http_major = (int)ch - 0x30;
  state = State.req_http_major;
  break;

/* major HTTP version or dot */
case req_http_major:
  if (DOT == ch) {
    state = State.req_first_http_minor;
    break;
  }

  if (!isDigit(ch)) {

return error(settings, “non digit in http major”, data);

}

http_major *= 10;
http_major += (int)ch - 0x30;

if (http_major > 999) {

return error(settings, “ridiculous http major”, data);

  };
  break;

/* first digit of minor HTTP version */
case req_first_http_minor:
  if (!isDigit(ch)) {

return error(settings, “non digit in http minor”, data);

  }
  http_minor = (int)ch - 0x30;
  state = State.req_http_minor;
  break;

case req_http_minor:
  if (ch == CR) {
    state = State.req_line_almost_done;
    break;
  }

  if (ch == LF) {
    state = State.header_field_start;
    break;
  }

  /* XXX allow spaces after digit? */

  if (!isDigit(ch)) {

return error(settings, “non digit in http minor”, data);

}

http_minor *= 10;
http_minor += (int)ch - 0x30;

if (http_minor > 999) {

return error(settings, “ridiculous http minor”, data);

  };

  break;

/* end of request line */
case req_line_almost_done:
{
  if (ch != LF) {

return error(settings, “missing LF after request line”, data);

  }
  state = header_field_start;
  break;
}

/******************* HTTP 1.1 *******************/

/******************* Header *******************/
case header_field_start:
{
  if (ch == CR) {
    state = headers_almost_done;
    break;
  }

  if (ch == LF) {
    /* they might be just sending \n instead of \r\n so this would be
     * the second \n to denote the end of headers*/
    state = State.headers_almost_done;
    reexecute = true;
    break;
  }

  c = token(ch);

  if (0 == c) {
    return error(settings, "invalid char in header:", data);
  }

  header_field_mark = p;

  index = 0;
  state = State.header_field;

  switch (c) {
    case C:
      header_state = HState.C;
      break;

    case P:
      header_state = HState.matching_proxy_connection;
      break;

    case T:
      header_state = HState.matching_transfer_encoding;
      break;

    case U:
      header_state = HState.matching_upgrade;
      break;

    default:
      header_state = HState.general;
      break;
  }
  break;
}

case header_field:
{
  c = token(ch);
  if (0 != c) {
    switch (header_state) {
      case general:
        break;

      case C:
        index++;
        header_state = (O == c ? HState.CO : HState.general);
        break;

      case CO:
        index++;
        header_state = (N == c ? HState.CON : HState.general);
        break;

      case CON:
        index++;
        switch (c) {
          case N:
            header_state = HState.matching_connection;
            break;
          case T:
            header_state = HState.matching_content_length;
            break;
          default:
            header_state = HState.general;
            break;
        }
        break;

      /* connection */

      case matching_connection:
        index++;
        if (index > CONNECTION.length || c != CONNECTION[index]) {
          header_state = HState.general;
        } else if (index == CONNECTION.length-1) {
          header_state = HState.connection;
        }
        break;

      /* proxy-connection */

      case matching_proxy_connection:
        index++;
        if (index > PROXY_CONNECTION.length || c != PROXY_CONNECTION[index]) {
          header_state = HState.general;
        } else if (index == PROXY_CONNECTION.length-1) {
          header_state = HState.connection;
        }
        break;

      /* content-length */

      case matching_content_length:
        index++;
        if (index > CONTENT_LENGTH.length || c != CONTENT_LENGTH[index]) {
          header_state = HState.general;
        } else if (index == CONTENT_LENGTH.length-1) {
          header_state = HState.content_length;
        }
        break;

      /* transfer-encoding */

      case matching_transfer_encoding:
        index++;
        if (index > TRANSFER_ENCODING.length || c != TRANSFER_ENCODING[index]) {
          header_state = HState.general;
        } else if (index == TRANSFER_ENCODING.length-1) {
          header_state = HState.transfer_encoding;
        }
        break;

      /* upgrade */

      case matching_upgrade:
        index++;
        if (index > UPGRADE.length || c != UPGRADE[index]) {
          header_state = HState.general;
        } else if (index == UPGRADE.length-1) {
          header_state = HState.upgrade;
        }
        break;

      case connection:
      case content_length:
      case transfer_encoding:
      case upgrade:
        if (SPACE != ch) header_state = HState.general;
        break;

      default:

return error(settings, “Unknown Header State”, data);

  } // switch: header_state
  break;
} // 0 != c

if (COLON == ch)  {
  settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark);
  header_field_mark = -1;

  state = State.header_value_start;
  break;
}

if (CR == ch) {
  state = State.header_almost_done;
  settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark);

  header_field_mark = -1;
  break;
}

if (ch == LF) {
  settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark);
  header_field_mark = -1;

  state = State.header_field_start;
  break;
}

return error(settings, “invalid header field”, data);

}

case header_value_start:
{
  if ((SPACE == ch) || (TAB  == ch)) break;

  header_value_mark = p;

  state = State.header_value;
  index = 0;

  if (CR == ch) {
    settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
    header_value_mark = -1;

    header_state = HState.general;
    state = State.header_almost_done;
    break;
  }

  if (LF == ch) {
    settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
    header_value_mark = -1;

    state = State.header_field_start;
    break;
  }

  c = upper(ch);

  switch (header_state) {
    case upgrade:
      flags |= F_UPGRADE;
      header_state = HState.general;
      break;

    case transfer_encoding:
      /* looking for 'Transfer-Encoding: chunked' */
      if (C == c) {
        header_state = HState.matching_transfer_encoding_chunked;
      } else {
        header_state = HState.general;
      }
      break;

    case content_length:
      if (!isDigit(ch)) {

return error(settings, “Content-Length not numeric”, data);

      }
      content_length = (int)ch - 0x30;
      break;

    case connection:
      /* looking for 'Connection: keep-alive' */
      if (K == c) {
        header_state = HState.matching_connection_keep_alive;
      /* looking for 'Connection: close' */
      } else if (C == c) {
        header_state = HState.matching_connection_close;
      } else {
        header_state = HState.general;
      }
      break;

    default:
      header_state = HState.general;
      break;
  }
  break;
} // header value start

case header_value:
{

  if (CR == ch) {
    settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
    header_value_mark = -1;

    state = State.header_almost_done;
    break;
  }

  if (LF == ch) {
    settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
    header_value_mark = -1;
    state = header_almost_done;
    reexecute = true;
    break;
  }

  c = upper(ch);
  switch (header_state) {
    case general:
      break;

    case connection:
    case transfer_encoding:

return error(settings, “Shouldn’t be here”, data);

case content_length:
  if (SPACE == ch) {
    break;
  }
  if (!isDigit(ch)) {

return error(settings, “Content-Length not numeric”, data);

      }

      long t = content_length; 
      t *= 10;
      t += (long)ch - 0x30;

      /* Overflow? */
      // t will wrap and become negative ...
      if (t < content_length) { 
        return error(settings, "Invalid content length", data);
      }
      content_length = t;
      break;

    /* Transfer-Encoding: chunked */
    case matching_transfer_encoding_chunked:
      index++;
      if (index > CHUNKED.length || c != CHUNKED[index]) {
        header_state = HState.general;
      } else if (index == CHUNKED.length-1) {
        header_state = HState.transfer_encoding_chunked;
      }
      break;

    /* looking for 'Connection: keep-alive' */
    case matching_connection_keep_alive:
      index++;
      if (index > KEEP_ALIVE.length || c != KEEP_ALIVE[index]) {
        header_state = HState.general;
      } else if (index == KEEP_ALIVE.length-1) {
        header_state = HState.connection_keep_alive;
      }
      break;

    /* looking for 'Connection: close' */
    case matching_connection_close:
      index++;
      if (index > CLOSE.length || c != CLOSE[index]) {
        header_state = HState.general;
      } else if (index == CLOSE.length-1) {
        header_state = HState.connection_close;
      }
      break;

    case transfer_encoding_chunked:
    case connection_keep_alive:
    case connection_close:
      if (SPACE != ch) header_state = HState.general;
      break;

    default:
      state = State.header_value;
      header_state = HState.general;
      break;
  }
  break;
} // header_value

case header_almost_done:
  if (!header_almost_done(ch)) {
    return error(settings, "incorrect header ending, expecting LF", data);
  }
  break;

case header_value_lws:
  if (SPACE == ch || TAB == ch ){
    state = header_value_start;
  } else {
    state = header_field_start;
    reexecute = true;
  }
  break;

case headers_almost_done:
  if (LF != ch) {
    return error(settings, "header not properly completed", data);
  }
  if (0 != (flags & F_TRAILING)) {
    /* End of a chunked request */
    state = new_message();
    settings.call_on_headers_complete(this);
    settings.call_on_message_complete(this);
    break;
  }

  state = headers_done;

  if (0 != (flags & F_UPGRADE) || HTTPMethod.HTTP_CONNECT == method) {
    upgrade = true;
  }

  /* Here we call the headers_complete callback. This is somewhat
  * different than other callbacks because if the user returns 1, we
  * will interpret that as saying that this message has no body. This
  * is needed for the annoying case of recieving a response to a HEAD
  * request.
  */

  /* (responses to HEAD request contain a CONTENT-LENGTH header
  * but no content)
  *
  * Consider what to do here: I don't like the idea of the callback
  * interface having a different contract in the case of HEAD
  * responses. The alternatives would be either to:
  *
  * a.) require the header_complete callback to implement a different
  * interface or
  *
  * b.) provide an overridden execute(bla, bla, boolean
  * parsingHeader) implementation ...
  */

  /*TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO */
  if (null != settings.on_headers_complete) {
    settings.call_on_headers_complete(this);
    //return;
  }

  //        if (null != settings.on_headers_complete) {
  //          switch (settings.on_headers_complete.cb(parser)) {
  //            case 0:
  //              break;
  //
  //            case 1:
  //              flags |= F_SKIPBODY;
  //              break;
  //
  //            default:
  //              return p - data; /* Error */ // TODO // RuntimeException ?
  //          }
  //        }
  reexecute = true;
  break;

case headers_done:
  if (strict && (LF != ch)) {
    return error(settings, "STRICT CHECK", data); //TODO correct error msg
  }

  nread = 0;

  // Exit, the rest of the connect is in a different protocol.
  if (upgrade) {
    state = new_message();
    settings.call_on_message_complete(this);
    return data.position()-this.p_start;
  }

  if (0 != (flags & F_SKIPBODY)) {
    state = new_message();
    settings.call_on_message_complete(this);
  } else if (0 != (flags & F_CHUNKED)) {
    /* chunked encoding - ignore Content-Length header */
    state = State.chunk_size_start;
  } else {
    if (content_length == 0) {
      /* Content-Length header given but zero: Content-Length: 0\r\n */
      state = new_message();
      settings.call_on_message_complete(this);
    } else if (content_length != -1) {
      /* Content-Length header given and non-zero */
      state = State.body_identity;
    } else {
      if (type == ParserType.HTTP_REQUEST || !http_message_needs_eof()) {
        /* Assume content-length 0 - read the next */
        state = new_message();
        settings.call_on_message_complete(this);
      } else {
        /* Read body until EOF */
        state = State.body_identity_eof;
      }
    }
  }

  break;
/******************* Header *******************/

/******************* Body *******************/
case body_identity:
  to_read = min(pe - p, content_length); //TODO change to use buffer?
  body_mark = p;

  if (to_read > 0) {
    settings.call_on_body(this, data, p, to_read);
    data.position(p+to_read);
    content_length -= to_read;

    if (content_length == 0) {
      state = message_done;
      reexecute = true;
    }
  }
  break;

case body_identity_eof:
  to_read = pe - p;  // TODO change to use buffer ?
  if (to_read > 0) {
    settings.call_on_body(this, data, p, to_read);
    data.position(p+to_read);
  }
  break;

case message_done:
  state = new_message();
  settings.call_on_message_complete(this);
  break;
/******************* Body *******************/

/******************* Chunk *******************/
case chunk_size_start:
  if (1 != this.nread) {

return error(settings, “nread != 1 (chunking)”, data);

}
if (0 == (flags & F_CHUNKED)) {

return error(settings, “not chunked”, data);

}

c = UNHEX[chi];
if (c == -1) {

return error(settings, “invalid hex char in chunk content length”, data);

  }
  content_length = c;
  state = State.chunk_size;
  break;

case chunk_size:
  if (0 == (flags & F_CHUNKED)) {
    return error(settings, "not chunked", data);
  }

  if (CR == ch) {
    state = State.chunk_size_almost_done;
    break;
  }

  c = UNHEX[chi];

  if (c == -1) {
    if (SEMI == ch || SPACE == ch) {
      state = State.chunk_parameters;
      break;
    }
    return error(settings, "invalid hex char in chunk content length", data);
  }
  long t = content_length;

  t *= 16;
  t += c;
  if(t < content_length){
    return error(settings, "invalid content length", data);
  }
  content_length = t;
  break;

case chunk_parameters:
  if (0 == (flags & F_CHUNKED)) {

return error(settings, “not chunked”, data);

  }
  /* just ignore this shit. TODO check for overflow */
  if (CR == ch) {
    state = State.chunk_size_almost_done;
    break;
  }
  break;

case chunk_size_almost_done:
  if (0 == (flags & F_CHUNKED)) {

return error(settings, “not chunked”, data);

}
if (strict && LF != ch) {

return error(settings, “expected LF at end of chunk size”, data);

  }

  this.nread = 0;

  if (0 == content_length) {
    flags |= F_TRAILING;
    state = State.header_field_start;
  } else {
    state = State.chunk_data;
  }
  break;

case chunk_data:
  //TODO Apply changes from C version for s_chunk_data
  if (0 == (flags & F_CHUNKED)) {
    return error(settings, "not chunked", data);
  }

  to_read = min(pe-p, content_length);
  if (to_read > 0) {
    settings.call_on_body(this, data, p, to_read);
    data.position(p+to_read);
  }

  if (to_read == content_length) {
    state = State.chunk_data_almost_done;
  }

  content_length -= to_read;
  break;

case chunk_data_almost_done:
  if (0 == (flags & F_CHUNKED)) {

return error(settings, “not chunked”, data);

}
if (strict && CR != ch) {

return error(settings, “chunk data terminated incorrectly, expected CR”, data);

  }
  state = State.chunk_data_done;
  //TODO CALLBACK_DATA(body)
  // settings.call_on_body(this, data,p,?);
  break;

case chunk_data_done:
  if (0 == (flags & F_CHUNKED)) {

return error(settings, “not chunked”, data);

}
if (strict && LF != ch) {

return error(settings, “chunk data terminated incorrectly, expected LF”, data);

  }
  state = State.chunk_size_start;
  break;
/******************* Chunk *******************/

default:

return error(settings, “unhandled state”, data);

    } // switch
  } // while

  p = data.position();

  /* Reaching this point assumes that we only received part of a
   * message, inform the callbacks about the progress made so far*/

        settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark);
  settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
  settings.call_on_url         (this, data, url_mark,          p-url_mark);
  settings.call_on_path        (this, data, url_mark,          p-url_mark);

  return data.position()-this.p_start;
} // execute

int error (ParserSettings settings, String mes, ByteBuffer data) {
  settings.call_on_error(this, mes, data, this.p_start);
  this.state = State.dead;
  return data.position()-this.p_start;
}

public boolean http_message_needs_eof() {
  if(type == ParserType.HTTP_REQUEST){
    return false;
  }
  /* See RFC 2616 section 4.4 */
  if ((status_code / 100 == 1) || /* 1xx e.g. Continue */
      (status_code == 204) ||     /* No Content */
      (status_code == 304) ||     /* Not Modified */
      (flags & F_SKIPBODY) != 0) {     /* response to a HEAD request */
        return false;
    }
  if ((flags & F_CHUNKED) != 0 || content_length != -1) {
    return false;
  }

  return true;
}

/* If http_should_keep_alive() in the on_headers_complete or
 * on_message_complete callback returns true, then this will be should be
 * the last message on the connection.
 * If you are the server, respond with the "Connection: close" header.
 * If you are the client, close the connection.
 */
public boolean http_should_keep_alive() {
  if (http_major > 0 && http_minor > 0) {
    /* HTTP/1.1 */
    if ( 0 != (flags & F_CONNECTION_CLOSE) ) {
      return false;
    }
  } else {
    /* HTTP/1.0 or earlier */
    if ( 0 == (flags & F_CONNECTION_KEEP_ALIVE) ) {
      return false;
    }
  }
  return !http_message_needs_eof();
}

public int parse_url(ByteBuffer data, boolean is_connect, HTTPParserUrl u) {

  UrlFields uf = UrlFields.UF_MAX;
  UrlFields old_uf = UrlFields.UF_MAX;
  u.port = 0;
  u.field_set = 0;
  state = (is_connect ? State.req_host_start : State.req_spaces_before_url);
  int p_init = data.position();
  int p = 0;
  byte ch = 0;
  while (data.position() != data.limit()) {
    p = data.position();
    ch = data.get();
    state = parse_url_char(ch);
    switch(state) {
      case dead:
        return 1;

      /* Skip delimeters */
      case req_schema_slash:
      case req_schema_slash_slash:
      case req_host_start:
      case req_host_v6_start:
      case req_host_v6_end:
      case req_port_start:
      case req_query_string_start:
      case req_fragment_start:
        continue;

      case req_schema:
        uf = UrlFields.UF_SCHEMA;
        break;

      case req_host:
      case req_host_v6:
        uf = UrlFields.UF_HOST;
        break;

      case req_port:
        uf = UrlFields.UF_PORT;
        break;

      case req_path:
        uf = UrlFields.UF_PATH;
        break;

      case req_query_string:
        uf = UrlFields.UF_QUERY;
        break;

      case req_fragment:
        uf = UrlFields.UF_FRAGMENT;
        break;

      default:
        return 1;
    }
    /* Nothing's changed; soldier on */
    if (uf == old_uf) {
      u.field_data[uf.getIndex()].len++;
      continue;
    }

    u.field_data[uf.getIndex()].off = p - p_init;
    u.field_data[uf.getIndex()].len = 1;

    u.field_set |= (1 << uf.getIndex());
    old_uf = uf;

  }

  /* CONNECT requests can only contain "hostname:port" */
  if (is_connect && u.field_set != ((1 << UrlFields.UF_HOST.getIndex())|(1 << UrlFields.UF_PORT.getIndex()))) {
    return 1;
  }

  /* Make sure we don't end somewhere unexpected */
  switch (state) {
    case req_host_v6_start:
    case req_host_v6:
    case req_host_v6_end:
    case req_host:
    case req_port_start:
      return 1;
    default:
      break;
  }

  if (0 != (u.field_set & (1 << UrlFields.UF_PORT.getIndex()))) {
    /* Don't bother with endp; we've already validated the string */
    int v = strtoi(data, p_init + u.field_data[UrlFields.UF_PORT.getIndex()].off);

    /* Ports have a max value of 2^16 */
    if (v > 0xffff) {
      return 1;
    }

    u.port = v;
  }

  return 0;
}

//hacky reimplementation of srttoul, tailored for our simple needs
//we only need to parse port val, so no negative values etc
int strtoi(ByteBuffer data, int start_pos) {
  data.position(start_pos);
  byte ch;
  String str = "";
  while(data.position() < data.limit()) {
    ch = data.get();
    if(Character.isWhitespace((char)ch)){
      continue;
    }
    if(isDigit(ch)){
      str = str + (char)ch; //TODO replace with something less hacky
    }else{
      break;
    }
  }
  return Integer.parseInt(str);
}

boolean isDigit(byte b) {
  if (b >= 0x30 && b <=0x39) {
    return true;
  }
  return false;
}

boolean isHex(byte b) {
  return isDigit(b) || (lower(b) >= 0x61 /*a*/ && lower(b) <= 0x66 /*f*/);
}

boolean isAtoZ(byte b) {
  byte c = lower(b);
  return (c>= 0x61 /*a*/ && c <=  0x7a /*z*/);
}

byte lower (byte b) {
  return (byte)(b|0x20);
}

byte upper(byte b) {
  char c = (char)(b);
  return (byte)Character.toUpperCase(c);
}

byte token(byte b) {
  if(!strict){
      return (b == (byte)' ') ? (byte)' ' : (byte)tokens[b] ;
  }else{
      return (byte)tokens[b];
  }
}

boolean isHostChar(byte ch){
  if(!strict){
    return (isAtoZ(ch)) || isDigit(ch) || DOT == ch || DASH == ch || UNDER == ch ;
  }else{
    return (isAtoZ(ch)) || isDigit(ch) || DOT == ch || DASH == ch;
  }
}

boolean isNormalUrlChar(int chi) {
  if(!strict){
    return (chi > 0x80) || normal_url_char[chi];
  }else{
    return normal_url_char[chi];
  }
}

HTTPMethod start_req_method_assign(byte c){
  switch (c) {
    case C: return HTTPMethod.HTTP_CONNECT;  /* or COPY, CHECKOUT */
    case D: return HTTPMethod.HTTP_DELETE;
    case G: return HTTPMethod.HTTP_GET;
    case H: return HTTPMethod.HTTP_HEAD;
    case L: return HTTPMethod.HTTP_LOCK;
    case M: return HTTPMethod.HTTP_MKCOL;    /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */
    case N: return HTTPMethod.HTTP_NOTIFY;
    case O: return HTTPMethod.HTTP_OPTIONS;
    case P: return HTTPMethod.HTTP_POST;     /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
    case R: return HTTPMethod.HTTP_REPORT;
    case S: return HTTPMethod.HTTP_SUBSCRIBE;
    case T: return HTTPMethod.HTTP_TRACE;
    case U: return HTTPMethod.HTTP_UNLOCK; /* or UNSUBSCRIBE */
  }
  return null; // ugh.
}

boolean header_almost_done(byte ch) {
  if (strict && LF != ch) {
    return false;
  }

  state = State.header_value_lws;
  // TODO java enums support some sort of bitflag mechanism !?
  switch (header_state) {
    case connection_keep_alive:
      flags |= F_CONNECTION_KEEP_ALIVE;
      break;
    case connection_close:
      flags |= F_CONNECTION_CLOSE;
      break;
    case transfer_encoding_chunked:
      flags |= F_CHUNKED;
      break;
    default:
      break;
  }
  return true;
}

// boolean headers_almost_done (byte ch, ParserSettings settings) { // } // headers_almost_done

final int min (int a, int b) {
  return a < b ? a : b;
}

final int min (int a, long b) {
  return a < b ? a : (int)b;
}

/* probably not the best place to hide this ... */
      public boolean HTTP_PARSER_STRICT;
State new_message() {
  if (HTTP_PARSER_STRICT){
    return http_should_keep_alive() ? start_state() : State.dead;
  } else {
    return start_state();
  }

}

State start_state() {
  return type == ParserType.HTTP_REQUEST ? State.start_req : State.start_res;
}

      boolean parsing_header(State state) {

              switch (state) {
                      case chunk_data :
                      case chunk_data_almost_done :
                      case chunk_data_done :
                      case body_identity :
                      case body_identity_eof :
    case message_done :
                              return false;

              }
  return true;
      }

      /* "Dial C for Constants" */
static class C {
  static final int HTTP_MAX_HEADER_SIZE = 80 * 1024;

  static final int F_CHUNKED               = 1 << 0;
  static final int F_CONNECTION_KEEP_ALIVE = 1 << 1;
  static final int F_CONNECTION_CLOSE      = 1 << 2;
  static final int F_TRAILING              = 1 << 3;
  static final int F_UPGRADE               = 1 << 4;
  static final int F_SKIPBODY              = 1 << 5;

  static final byte [] UPCASE = {
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x2d,0x00,0x2f,
    0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,  0x38,0x39,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x41,0x42,0x43,0x44,0x45,0x46,0x47,  0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
    0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,  0x58,0x59,0x5a,0x00,0x00,0x00,0x00,0x5f,
    0x00,0x41,0x42,0x43,0x44,0x45,0x46,0x47,  0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
    0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,  0x58,0x59,0x5a,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  };
  static final byte [] CONNECTION = {
    0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e,
  };
  static final byte [] PROXY_CONNECTION = {
    0x50, 0x52, 0x4f, 0x58, 0x59, 0x2d, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e,
  };
  static final byte [] CONTENT_LENGTH = {
    0x43, 0x4f, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x2d, 0x4c, 0x45, 0x4e, 0x47, 0x54, 0x48,
  };
  static final byte [] TRANSFER_ENCODING = {
    0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x2d, 0x45, 0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47,
  };
  static final byte [] UPGRADE = {
    0x55, 0x50, 0x47, 0x52, 0x41, 0x44, 0x45,
  };
  static final byte [] CHUNKED = {
    0x43, 0x48, 0x55, 0x4e, 0x4b, 0x45, 0x44,
  };
  static final byte [] KEEP_ALIVE = {
    0x4b, 0x45, 0x45, 0x50, 0x2d, 0x41, 0x4c, 0x49, 0x56, 0x45,
  };
  static final byte [] CLOSE = {
    0x43, 0x4c, 0x4f, 0x53, 0x45,
  };

  /* Tokens as defined by rfc 2616. Also lowercases them.
   *        token       = 1*<any CHAR except CTLs or separators>
   *     separators     = "(" | ")" | "<" | ">" | "@"
   *                    | "," | ";" | ":" | "\" | <">
   *                    | "/" | "[" | "]" | "?" | "="
   *                    | "{" | "}" | SP | HT
   */

  static final char [] tokens = {

/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */

0,       0,       0,       0,       0,       0,       0,       0,

/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */

0,       0,       0,       0,       0,       0,       0,       0,

/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */

0,       0,       0,       0,       0,       0,       0,       0,

/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */

0,       0,       0,       0,       0,       0,       0,       0,

/* 32 sp 33 ! 34 “ 35 # 36 $ 37 % 38 & 39 ‘ */

0,     '!',       0,     '#',     '$',     '%',     '&',    '\'',

/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */

0,       0,      '*',     '+',       0,     '-',     '.',     0 ,

/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */

'0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',

/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */

'8',     '9',      0,       0,       0,       0,       0,       0,

/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */

0,      'A',     'B',     'C',     'D',     'E',     'F',     'G',

/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */

'H',     'I',     'J',     'K',     'L',     'M',     'N',     'O',

/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */

'P',     'Q',     'R',     'S',     'T',     'U',     'V',     'W',

/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */

'X',     'Y',     'Z',      0,       0,       0,       0,      '_',

/* 96 ‘ 97 a 98 b 99 c 100 d 101 e 102 f 103 g */

0,      'A',     'B',     'C',     'D',     'E',     'F',     'G',

/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */

'H',     'I',     'J',     'K',     'L',     'M',     'N',     'O',

/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */

'P',     'Q',     'R',     'S',     'T',     'U',     'V',     'W',

/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */

'X',     'Y',     'Z',      0,      '|',      0,      '~',       0,

/* hi bit set, not ascii */

    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0, };

static final byte [] UNHEX =
{    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
    ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};

static final boolean [] normal_url_char = {

/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */

false,   false,   false,   false,   false,   false,   false,   false,

/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */

false,   false,   false,   false,   false,   false,   false,   false,

/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */

false,   false,   false,   false,   false,   false,   false,   false,

/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */

false,   false,   false,   false,   false,   false,   false,   false,

/* 32 sp 33 ! 34 “ 35 # 36 $ 37 % 38 & 39 ‘ */

false,    true,    true,   false,    true,    true,    true,    true,

/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */

true,    true,    true,    true,    true,    true,    true,    true,

/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */

true,    true,    true,    true,    true,    true,    true,    true,

/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */

true,    true,    true,    true,    true,    true,    true,   false,

/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */

true,    true,    true,    true,    true,    true,    true,    true,

/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */

true,    true,    true,    true,    true,    true,    true,    true,

/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */

true,    true,    true,    true,    true,    true,    true,    true,

/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */

true,    true,    true,    true,    true,    true,    true,    true,

/* 96 ‘ 97 a 98 b 99 c 100 d 101 e 102 f 103 g */

true,    true,    true,    true,    true,    true,    true,    true,

/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */

true,    true,    true,    true,    true,    true,    true,    true,

/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */

true,    true,    true,    true,    true,    true,    true,    true,

/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */

true,    true,    true,    true,    true,    true,    true,   false,

/* hi bit set, not ascii */ /* Remainder of non-ASCII range are accepted as-is to support implicitly UTF-8

*    encoded paths. This is out of spec, but clients generate this and most other
*    HTTP servers support it. We should, too. */

    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,
    true,    true,    true,    true,    true,    true,    true,    true,

   };

   public static final byte A = 0x41;
   public static final byte B = 0x42;
   public static final byte C = 0x43;
   public static final byte D = 0x44;
   public static final byte E = 0x45;
   public static final byte F = 0x46;
   public static final byte G = 0x47;
   public static final byte H = 0x48;
   public static final byte I = 0x49;
   public static final byte J = 0x4a;
   public static final byte K = 0x4b;
   public static final byte L = 0x4c;
   public static final byte M = 0x4d;
   public static final byte N = 0x4e;
   public static final byte O = 0x4f;
   public static final byte P = 0x50;
   public static final byte Q = 0x51;
   public static final byte R = 0x52;
   public static final byte S = 0x53;
   public static final byte T = 0x54;
   public static final byte U = 0x55;
   public static final byte V = 0x56;
   public static final byte W = 0x57;
   public static final byte X = 0x58;
   public static final byte Y = 0x59;
   public static final byte Z = 0x5a;
   public static final byte UNDER = 0x5f;
   public static final byte CR = 0x0d;
   public static final byte LF = 0x0a;
   public static final byte DOT = 0x2e;
   public static final byte SPACE = 0x20;
   public static final byte TAB = 0x09;
   public static final byte SEMI = 0x3b;
   public static final byte COLON = 0x3a;
   public static final byte HASH = 0x23;
   public static final byte QMARK = 0x3f;
   public static final byte SLASH = 0x2f;
   public static final byte DASH = 0x2d;
   public static final byte STAR = 0x2a;
   public static final byte NULL = 0x00;
 }

 enum State {

   dead

   , start_req_or_res
   , res_or_resp_H
   , start_res
   , res_H
   , res_HT
   , res_HTT
   , res_HTTP
   , res_first_http_major
   , res_http_major
   , res_first_http_minor
   , res_http_minor
   , res_first_status_code
   , res_status_code
   , res_status
   , res_line_almost_done

   , start_req

   , req_method
   , req_spaces_before_url
   , req_schema
   , req_schema_slash
   , req_schema_slash_slash
   , req_host_start
   , req_host_v6_start
   , req_host_v6
   , req_host_v6_end
   , req_host
   , req_port_start
   , req_port
   , req_path
   , req_query_string_start
   , req_query_string
   , req_fragment_start
   , req_fragment
   , req_http_start
   , req_http_H
   , req_http_HT
   , req_http_HTT
   , req_http_HTTP
   , req_first_http_major
   , req_http_major
   , req_first_http_minor
   , req_http_minor
   , req_line_almost_done

   , header_field_start
   , header_field
   , header_value_start
   , header_value
   , header_value_lws

   , header_almost_done

   , chunk_size_start
   , chunk_size
   , chunk_parameters
   , chunk_size_almost_done

   , headers_almost_done
   , headers_done

// This space intentionally not left blank, comment from c, for orientation… // the c version uses <= s_header_almost_done in java, we list the states explicitly // in ‘parsing_header()` /* Important: ’s_headers_done’ must be the last ‘header’ state. All

* states beyond this must be 'body' states. It is used for overflow
* checking. See the PARSING_HEADER() macro.
*/
   , chunk_data
   , chunk_data_almost_done
   , chunk_data_done

   , body_identity
   , body_identity_eof
   , message_done

 }
 enum HState {
     general
   , C
   , CO
   , CON

   , matching_connection
   , matching_proxy_connection
   , matching_content_length
   , matching_transfer_encoding
   , matching_upgrade

   , connection
   , content_length
   , transfer_encoding
   , upgrade

   , matching_transfer_encoding_chunked
   , matching_connection_keep_alive
   , matching_connection_close

   , transfer_encoding_chunked
   , connection_keep_alive
   , connection_close
 }
 public enum UrlFields {
     UF_SCHEMA(0)
   , UF_HOST(1)
   , UF_PORT(2)
   , UF_PATH(3)
   , UF_QUERY(4)
   , UF_FRAGMENT(5)
   , UF_MAX(6);

   private final int index;

   private UrlFields(int index) {
     this.index = index;
   }
   public int getIndex() {
     return index;
   }

 }

}