/** 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 void Execute (ParserSettings settings, MemoryStream data)
{
int p = (int) data.Position;
int p_err = p; // this is used for pretty printing errors.
// 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 = (int) (data.Length - data.Position);
if (0 == len) {
if (State.body_identity_eof == state)
settings.RaiseOnMessageComplete(this);
}
// 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 State.header_field:
header_field_mark = p;
break;
case State.header_value:
header_value_mark = p;
break;
case State.req_fragment:
fragment_mark = p;
url_mark = p;
break;
case State.req_query_string:
query_string_mark = p;
url_mark = p;
break;
case State.req_path:
path_mark = p;
// JACKSON ADDED, I assume java can fall through?
url_mark = p;
break;
case State.req_host:
case State.req_schema:
case State.req_schema_slash:
case State.req_schema_slash_slash:
case State.req_port:
case State.req_query_string_start:
case State.req_fragment_start:
url_mark = p;
break;
}
// this is where the work gets done, traverse the available data...
while (data.Position != data.Length) {
p = (int) data.Position;
int pe = (int) data.Length;
int ch = (byte) data.ReadByte (); // the current character to process.
int c = -1; // utility variably used for up- and downcasing etc.
int 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) {
settings.RaiseOnError (this, "possible buffer overflow", data, p_err);
}
}
switch (state) {
/*
* this state is used after a 'Connection: close' message
* the parser will error out if it reads another message
*/
case State.dead:
settings.RaiseOnError (this, "Connection already closed", data, p_err);
// JACKSON: Added this break
break;
case State.start_res_or_res:
if (CR == ch || LF == ch){
break;
}
flags = 0;
content_length = -1;
settings.RaiseOnMessageBegin (this);
if (H == ch)
state = State.res_or_resp_H;
else {
type = ParserType.HTTP_REQUEST;
method = start_req_method_assign (ch);
if (HttpMethod.ERROR == method)
settings.RaiseOnError (this, "invalid method", data, p_err);
index = 1;
state = State.req_method;
}
break;
case State.res_or_resp_H:
if (T == ch) {
type = ParserType.HTTP_RESPONSE;
state = State.res_HT;
} else {
if (E != ch)
settings.RaiseOnError (this, "not E", data, p_err);
type = ParserType.HTTP_REQUEST;
method = HttpMethod.HTTP_HEAD;
index = 2;
state = State.req_method;
}
break;
case State.start_res:
flags = 0;
content_length = -1;
settings.RaiseOnMessageBegin (this);
switch (ch) {
case H:
state = State.res_H;
break;
case CR:
case LF:
break;
default:
settings.RaiseOnError (this, "Not H or CR/LF", data, p_err);
break;
}
break;
case State.res_H:
if (strict && T != ch)
settings.RaiseOnError (this, "Not T", data, p_err);
state = State.res_HT;
break;
case State.res_HT:
if (strict && T != ch)
settings.RaiseOnError (this, "Not T2", data, p_err);
state = State.res_HTT;
break;
case State.res_HTT:
if (strict && P != ch)
settings.RaiseOnError (this, "Not P", data, p_err);
state = State.res_HTTP;
break;
case State.res_HTTP:
if (strict && SLASH != ch)
settings.RaiseOnError (this, "Not '/'", data, p_err);
state = State.res_first_http_major;
break;
case State.res_first_http_major:
if (!isDigit (ch))
settings.RaiseOnError (this, "Not a digit", data, p_err);
http_major = (int) ch - 0x30;
state = State.res_http_major;
break;
/* major HTTP version or dot */
case State.res_http_major:
if (DOT == ch) {
state = State.res_http_minor;
break;
}
if (!isDigit (ch))
settings.RaiseOnError(this, "Not a digit", data, p_err);
http_major *= 10;
http_major += (ch - 0x30);
if (http_major > 999)
settings.RaiseOnError(this, "invalid http major version: " + http_major, data, p_err);
break;
/* first digit of minor HTTP version */
case State.res_first_http_minor:
if (!isDigit (ch))
settings.RaiseOnError (this, "Not a digit", data, p_err);
http_minor = (int)ch - 0x30;
state = State.res_http_minor;
break;
/* minor HTTP version or end of request line */
case State.res_http_minor:
if (SPACE == ch) {
state = State.res_first_status_code;
break;
}
if (!isDigit (ch))
settings.RaiseOnError(this, "Not a digit", data, p_err);
http_minor *= 10;
http_minor += (ch - 0x30);
if (http_minor > 999)
settings.RaiseOnError(this, "invalid http minor version: " + http_minor, data, p_err);
break;
case State.res_first_status_code:
if (!isDigit (ch)) {
if (SPACE == ch)
break;
settings.RaiseOnError (this, "Not a digit (status code)", data, p_err);
}
status_code = (int)ch - 0x30;
state = State.res_status_code;
break;
case State.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:
settings.RaiseOnError(this, "not a valid status code", data, p_err);
break;
}
break;
}
status_code *= 10;
status_code += (int)ch - 0x30;
if (status_code > 999)
settings.RaiseOnError(this, "ridiculous status code:"+status_code, data, p_err);
break;
case State.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 State.res_line_almost_done:
if (strict && LF != ch)
settings.RaiseOnError (this, "not LF", data, p_err);
state = State.header_field_start;
break;
case State.start_req:
if (CR==ch || LF == LF)
break;
flags = 0;
content_length = -1;
settings.RaiseOnMessageBegin (this);
method = start_req_method_assign (ch);
if (HttpMethod.ERROR == method)
settings.RaiseOnError (this, "invalid method", data, p_err);
index = 1;
state = State.req_method;
break;
case State.req_method:
if (0 == ch)
settings.RaiseOnError( this, "NULL in method", data, p_err);
byte [] arr = HttpMethodBytes.GetBytes (method);
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 (2 == index && A == ch) {
method = HttpMethod.HTTP_MKACTIVITY;
}
} else if (1 == index && HttpMethod.HTTP_POST == method && R == ch) {
method = HttpMethod.HTTP_PROPFIND;
} else if (1 == index && HttpMethod.HTTP_POST == method && U == ch) {
method = HttpMethod.HTTP_PUT;
} else if (4 == index && HttpMethod.HTTP_PROPFIND == method && P == ch) {
method = HttpMethod.HTTP_PROPPATCH;
} else {
settings.RaiseOnError (this, "Invalid HTTP method", data, p_err);
}
++index;
break;
/******************* URL *******************/
case State.req_spaces_before_url:
if (SPACE == ch)
break;
if (SLASH == ch) {
url_mark = p;
path_mark = p;
state = State.req_path;
break;
}
if (isAtoZ (ch)) {
url_mark = p;
state = State.req_schema;
break;
}
settings.RaiseOnError (this, "Invalid something", data, p_err);
break;
case State.req_schema:
if (isAtoZ (ch))
break;
if (COLON == ch) {
state = State.req_schema_slash;
break;
} else if (DOT == ch) {
state = State.req_host;
break;
}
settings.RaiseOnError (this, "invalid char in schema: "+ch, data, p_err);
break;
case State.req_schema_slash:
if (strict && SLASH != ch)
settings.RaiseOnError (this, "invalid char in schema, not /", data, p_err);
state = State.req_schema_slash_slash;
break;
case State.req_schema_slash_slash:
if (strict && SLASH != ch)
settings.RaiseOnError(this, "invalid char in schema, not /", data, p_err);
state = State.req_host;
break;
case State.req_host:
if (isAtoZ (ch))
break;
if (isDigit (ch) || DOT == ch || DASH == ch)
break;
switch (ch) {
case COLON:
state = State.req_port;
break;
case SLASH:
path_mark = p;
break;
case SPACE:
/* The request line looks like:
* "GET http://foo.bar.com HTTP/1.1"
* That is, there is no path.
*/
settings.RaiseOnUrl (this, data, url_mark, p-url_mark);
url_mark = -1;
state = State.req_http_start;
break;
default:
settings.RaiseOnError(this, "host error in method line", data, p_err);
break;
}
break;
case State.req_port:
if (isDigit (ch))
break;
switch (ch) {
case SLASH:
path_mark = p;
state = State.req_path;
break;
case SPACE:
/* The request line looks like:
* "GET http://foo.bar.com:1234 HTTP/1.1"
* That is, there is no path.
*/
settings.RaiseOnUrl (this,data,url_mark,p-url_mark);
url_mark = -1;
state = State.req_http_start;
break;
default:
settings.RaiseOnError (this, "invalid port", data, p_err);
break;
}
break;
case State.req_path:
if (usual (ch))
break;
switch (ch) {
case SPACE:
settings.RaiseOnUrl (this,data,url_mark, p-url_mark);
url_mark = -1;
settings.RaiseOnPath(this,data,path_mark, p-path_mark);
path_mark = -1;
state = State.req_http_start;
break;
case CR:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
settings.RaiseOnPath(this,data,path_mark, p-path_mark);
path_mark = -1;
http_minor = 9;
state = State.res_line_almost_done;
break;
case LF:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
settings.RaiseOnPath(this,data,path_mark, p-path_mark);
path_mark = -1;
http_minor = 9;
state = State.header_field_start;
break;
case QMARK:
settings.RaiseOnPath(this,data,path_mark, p-path_mark);
path_mark = -1;
state = State.req_query_string_start;
break;
case HASH:
settings.RaiseOnPath(this,data,path_mark, p-path_mark);
path_mark = -1;
state = State.req_fragment_start;
break;
default:
settings.RaiseOnError(this, "unexpected char in path", data, p_err);
break;
}
break;
case State.req_query_string_start:
if (usual(ch)) {
query_string_mark = p;
state = State.req_query_string;
break;
}
switch (ch) {
case QMARK: break;
case SPACE:
settings.RaiseOnUrl(this, data, url_mark, p-url_mark);
url_mark = -1;
state = State.req_http_start;
break;
case CR:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
http_minor = 9;
state = State.res_line_almost_done;
break;
case LF:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
http_minor = 9;
state = State.header_field_start;
break;
case HASH:
state = State.req_fragment_start;
break;
default:
settings.RaiseOnError(this, "unexpected char in path", data, p_err);
break;
}
break;
case State.req_query_string:
if (usual(ch)) {
break;
}
switch (ch) {
case QMARK: break; // allow extra '?' in query string
case SPACE:
settings.RaiseOnUrl(this, data, url_mark, p-url_mark);
url_mark = -1;
settings.RaiseOnQueryString(this, data, query_string_mark, p-query_string_mark);
query_string_mark = -1;
state = State.req_http_start;
break;
case CR:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
settings.RaiseOnQueryString(this, data, query_string_mark, p-query_string_mark);
query_string_mark = -1;
http_minor = 9;
state = State.res_line_almost_done;
break;
case LF:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
settings.RaiseOnQueryString(this, data, query_string_mark, p-query_string_mark);
query_string_mark = -1;
http_minor = 9;
state = State.header_field_start;
break;
case HASH:
settings.RaiseOnQueryString(this, data, query_string_mark, p-query_string_mark);
query_string_mark = -1;
state = State.req_fragment_start;
break;
default:
settings.RaiseOnError(this, "unexpected char in path", data, p_err);
break;
}
break;
case State.req_fragment_start:
if (usual(ch)) {
fragment_mark = p;
state = State.req_fragment;
break;
}
switch (ch) {
case SPACE:
settings.RaiseOnUrl(this, data, url_mark, p-url_mark);
url_mark = -1;
state = State.req_http_start;
break;
case CR:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
http_minor = 9;
state = State.res_line_almost_done;
break;
case LF:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
http_minor = 9;
state = State.header_field_start;
break;
case QMARK:
fragment_mark = p;
state = State.req_fragment;
break;
case HASH:
break;
default:
settings.RaiseOnError(this, "unexpected char in path", data, p_err);
break;
}
break;
case State.req_fragment:
if (usual(ch)) {
break;
}
switch (ch) {
case SPACE:
settings.RaiseOnUrl(this, data, url_mark, p-url_mark);
url_mark = -1;
settings.RaiseOnFragment(this, data, fragment_mark, p-fragment_mark);
fragment_mark = -1;
state = State.req_http_start;
break;
case CR:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
settings.RaiseOnFragment(this, data, query_string_mark, p-query_string_mark);
fragment_mark = -1;
http_minor = 9;
state = State.res_line_almost_done;
break;
case LF:
settings.RaiseOnUrl(this,data,url_mark, p-url_mark);
url_mark = -1;
settings.RaiseOnFragment(this, data, query_string_mark, p-query_string_mark);
fragment_mark = -1;
http_minor = 9;
state = State.header_field_start;
break;
case QMARK:
case HASH:
break;
default:
settings.RaiseOnError(this, "unexpected char in path", data, p_err);
break;
}
break;
/******************* URL *******************/
/******************* HTTP 1.1 *******************/
case State.req_http_start:
switch (ch) {
case H:
state = State.req_http_H;
break;
case SPACE:
break;
default:
settings.RaiseOnError(this, "error in req_http_H", data, p_err);
break;
}
break;
case State.req_http_H:
if (strict && T != ch)
settings.RaiseOnError(this, "unexpected char", data, p_err);
state = State.req_http_HT;
break;
case State.req_http_HT:
if (strict && T != ch)
settings.RaiseOnError(this, "unexpected char", data, p_err);
state = State.req_http_HTT;
break;
case State.req_http_HTT:
if (strict && P != ch)
settings.RaiseOnError(this, "unexpected char", data, p_err);
state = State.req_http_HTTP;
break;
case State.req_http_HTTP:
if (strict && SLASH != ch)
settings.RaiseOnError(this, "unexpected char", data, p_err);
state = State.req_first_http_major;
break;
/* first digit of major HTTP version */
case State.req_first_http_major:
if (!isDigit(ch))
settings.RaiseOnError(this, "non digit in http major", data, p_err);
http_major = (int)ch - 0x30;
state = State.req_http_major;
break;
/* major HTTP version or dot */
case State.req_http_major:
if (DOT == ch) {
state = State.req_first_http_minor;
break;
}
if (!isDigit(ch))
settings.RaiseOnError(this, "non digit in http major", data, p_err);
http_major *= 10;
http_major += (int)ch - 0x30;
if (http_major > 999)
settings.RaiseOnError(this, "ridiculous http major", data, p_err);
break;
/* first digit of minor HTTP version */
case State.req_first_http_minor:
if (!isDigit(ch))
settings.RaiseOnError(this, "non digit in http minor", data, p_err);
http_minor = (int)ch - 0x30;
state = State.req_http_minor;
break;
case State.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))
settings.RaiseOnError(this, "non digit in http minor", data, p_err);
http_minor *= 10;
http_minor += (int)ch - 0x30;
if (http_minor > 999)
settings.RaiseOnError(this, "ridiculous http minor", data, p_err);
break;
/* end of request line */
case State.req_line_almost_done:
{
if (ch != LF)
settings.RaiseOnError(this, "missing LF after request line", data, p_err);
state = State.header_field_start;
break;
}
/******************* HTTP 1.1 *******************/
/******************* Header *******************/
case State.header_field_start:
{
if (ch == CR) {
state = 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;
if (!headers_almost_done(ch, settings))
settings.RaiseOnError(this, "header not properly completed", data, p_err);
break;
}
c = upper(ch);
if (c < A || Z < c) {
settings.RaiseOnError(this, "invalid char in header", data, p_err);
};
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 State.header_field:
{
c = UPCASE[ch];
if (0 != c) {
switch (header_state) {
case HState.general:
break;
case HState.C:
index++;
header_state = (O == c ? HState.CO : HState.general);
break;
case HState.CO:
index++;
header_state = (N == c ? HState.CON : HState.general);
break;
case HState.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 HState.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 HState.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 HState.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 HState.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 HState.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 HState.connection:
case HState.content_length:
case HState.transfer_encoding:
case HState. upgrade:
if (SPACE != ch) header_state = HState.general;
break;
default:
settings.RaiseOnError(this, "Unknown Header State", data, p_err);
break;
} // switch: header_state
break;
} // 0 != c
if (COLON == ch) {
settings.RaiseOnHeaderField(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.RaiseOnHeaderField(this, data, header_field_mark, p-header_field_mark);
header_field_mark = -1;
break;
}
if (ch == LF) {
settings.RaiseOnHeaderField(this, data, header_field_mark, p-header_field_mark);
header_field_mark = -1;
state = State.header_field_start;
break;
}
settings.RaiseOnError(this, "invalid header field", data, p_err);
break;
}
case State.header_value_start:
{
if (SPACE == ch) break;
header_value_mark = p;
state = State.header_value;
index = 0;
c = UPCASE[ch];
if (c == 0) {
if (CR == ch) {
settings.RaiseOnHeaderValue(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.RaiseOnHeaderValue(this, data, header_value_mark, p-header_value_mark);
header_value_mark = -1;
state = State.header_field_start;
break;
}
header_state = HState.general;
break;
}
switch (header_state) {
case HState.upgrade:
flags |= F_UPGRADE;
header_state = HState.general;
break;
case HState.transfer_encoding:
/* looking for 'Transfer-Encoding: chunked' */
if (C == c) {
header_state = HState.matching_transfer_encoding_chunked;
} else {
header_state = HState.general;
}
break;
case HState.content_length:
if (!isDigit(ch)) {
settings.RaiseOnError(this, "Content-Length not numeric", data, p_err);
}
content_length = (int)ch - 0x30;
break;
case HState.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 State.header_value:
{
c = UPCASE[ch];
if (c == 0) {
if (CR == ch) {
settings.RaiseOnHeaderValue(this, data, header_value_mark, p-header_value_mark);
header_value_mark = -1;
state = State.header_almost_done;
break;
}
if (LF == ch) {
settings.RaiseOnHeaderValue(this, data, header_value_mark, p-header_value_mark);
header_value_mark = -1;
if (!header_almost_done(ch)) {
settings.RaiseOnError(this,"incorrect header ending, expection LF", data, p_err);
}
break;
}
break;
}
switch (header_state) {
case HState.general:
break;
case HState.connection:
case HState.transfer_encoding:
settings.RaiseOnError(this, "Shouldn't be here", data, p_err);
break;
case HState.content_length:
if (!isDigit(ch))
settings.RaiseOnError(this, "Content-Length not numeric", data, p_err);
content_length *= 10;
content_length += (int)ch - 0x30;
break;
/* Transfer-Encoding: chunked */
case HState.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 HState.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 HState.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 HState.transfer_encoding_chunked:
case HState.connection_keep_alive:
case HState.connection_close:
if (SPACE != ch) header_state = HState.general;
break;
default:
state = State.header_value;
header_state = HState.general;
break;
}
break;
} // header_value
case State.header_almost_done:
if (!header_almost_done(ch))
settings.RaiseOnError(this,"incorrect header ending, expection LF", data, p_err);
break;
case State.headers_almost_done:
if (!headers_almost_done(ch, settings))
settings.RaiseOnError(this, "header not properly completed", data, p_err);
break;
/******************* Header *******************/
/******************* Body *******************/
case State.body_identity:
to_read = min(pe - p, content_length); //TODO change to use buffer?
if (to_read > 0) {
settings.RaiseOnBody(this, data, p, to_read);
data.Position = p+to_read;
content_length -= to_read;
if (content_length == 0) {
settings.RaiseOnMessageComplete(this);
state = new_message();
}
}
break;
case State.body_identity_eof:
to_read = pe - p; // TODO change to use buffer ?
if (to_read > 0) {
settings.RaiseOnBody(this, data, p, to_read);
data.Position = p+to_read;
}
break;
/******************* Body *******************/
/******************* Chunk *******************/
case State.chunk_size_start:
if (0 == (flags & F_CHUNKED))
settings.RaiseOnError(this, "not chunked", data, p_err);
c = UNHEX[ch];
if (c == -1)
settings.RaiseOnError(this, "invalid hex char in chunk content length", data, p_err);
content_length = c;
state = State.chunk_size;
break;
case State.chunk_size:
if (0 == (flags & F_CHUNKED))
settings.RaiseOnError(this, "not chunked", data, p_err);
if (CR == ch) {
state = State.chunk_size_almost_done;
break;
}
c = UNHEX[ch];
if (c == -1) {
if (SEMI == ch || SPACE == ch) {
state = State.chunk_parameters;
break;
}
settings.RaiseOnError(this, "invalid hex char in chunk content length", data, p_err);
}
content_length *= 16;
content_length += c;
break;
case State.chunk_parameters:
if (0 == (flags & F_CHUNKED))
settings.RaiseOnError(this, "not chunked", data, p_err);
/* just ignore this shit. TODO check for overflow */
if (CR == ch) {
state = State.chunk_size_almost_done;
break;
}
break;
case State.chunk_size_almost_done:
if (0 == (flags & F_CHUNKED)) {
settings.RaiseOnError(this, "not chunked", data, p_err);
}
if (strict && LF != ch) {
settings.RaiseOnError(this, "expected LF at end of chunk size", data, p_err);
}
if (0 == content_length) {
flags |= F_TRAILING;
state = State.header_field_start;
} else {
state = State.chunk_data;
}
break;
case State.chunk_data:
{
if (0 == (flags & F_CHUNKED)) {
settings.RaiseOnError(this, "not chunked", data, p_err);
}
to_read = min(pe-p, content_length);
if (to_read > 0) {
settings.RaiseOnBody(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 State.chunk_data_almost_done:
if (0 == (flags & F_CHUNKED)) {
settings.RaiseOnError(this, "not chunked", data, p_err);
}
if (strict && CR != ch) {
settings.RaiseOnError(this, "chunk data terminated incorrectly, expected CR", data, p_err);
}
state = State.chunk_data_done;
break;
case State.chunk_data_done:
if (0 == (flags & F_CHUNKED)) {
settings.RaiseOnError(this, "not chunked", data, p_err);
}
if (strict && LF != ch) {
settings.RaiseOnError(this, "chunk data terminated incorrectly, expected LF", data, p_err);
}
state = State.chunk_size_start;
break;
/******************* Chunk *******************/
default:
settings.RaiseOnError(this, "unhandled state", data, p_err);
break;
} // switch
} // while
p = (int) data.Position;
/* Reaching this point assumes that we only received part of a
* message, inform the callbacks about the progress made so far*/
settings.RaiseOnHeaderField (this, data, header_field_mark, p-header_field_mark);
settings.RaiseOnHeaderValue (this, data, header_value_mark, p-header_value_mark);
settings.RaiseOnFragment (this, data, fragment_mark, p-fragment_mark);
settings.RaiseOnQueryString (this, data, query_string_mark, p-query_string_mark);
settings.RaiseOnPath (this, data, path_mark, p-path_mark);
settings.RaiseOnUrl (this, data, url_mark, p-url_mark);
} // execute