예제 #1
0
        // 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, ByteBuffer 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 = data.Length;
            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.Length > 0) {

                p = (int) data.Position;
                int  pe = (int) data.Length;

                byte ch      = 0;          // 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(state != State.body_identity)
                    ch = data.ReadByte ();

                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 (Method.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 = Method.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_first_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 (Method.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 = MethodBytes.GetBytes (method);

                    if (SPACE == ch && index == arr.Length)
                        state = State.req_spaces_before_url;
                    else if (arr[index] == ch) {
                        // wuhu!
                    } else if (Method.Connect == method) {
                        if (1 == index && H == ch) {
                            method = Method.Checkout;
                        } else if (2 == index && P == ch) {
                            method = Method.Copy;
                        }
                    } else if (Method.MakeCollection == method) {
                        if        (1 == index && O == ch) {
                            method = Method.Move;
                        } else if (1 == index && E == ch) {
                            method = Method.Merge;
                        } else if (2 == index && A == ch) {
                            method = Method.MakeActivity;
                        }
                    } else if (1 == index && Method.Post == method && R == ch) {
                        method = Method.PropertyFind;
                    } else if (1 == index && Method.Post == method && U == ch) {
                        method = Method.Put;
                    } else if (4 == index && Method.PropertyFind == method && P == ch) {
                        method = Method.PropertyPatch;
                    } 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, data, p_err))
                            return;
                        break;
                    }

                    c = upper(ch);

                    if (c == 0) {
                        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 (ch == ' ') break;
                        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, data, p_err))
                        return;
                    break;

                //****************** Header *******************/

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

                    if (to_read > 0) {
                        settings.RaiseOnBody(this, data, p, to_read);
                        data.Skip(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;  // TODO change to use buffer ?
                    if (to_read > 0) {
                        settings.RaiseOnBody(this, data, p, to_read);
                        data.Skip(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, content_length);
                    if (to_read > 0) {
                        settings.RaiseOnBody(this, data, p, to_read);
                        data.Skip(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);
        }
예제 #2
0
        // Return true if we should continue processing
        bool headers_almost_done(int ch, ParserSettings settings, ByteBuffer data, int p_err)
        {
            if (strict && LF != ch) {
                settings.RaiseOnError (this, "header not properly completed", data, p_err);
                return false;
            }

            if (0 != (flags & F_TRAILING)) {
                // End of a chunked request

                settings.RaiseOnHeadersComplete(this);
                settings.RaiseOnMessageComplete(this);

                state = new_message();

                return true;
            }

            nread = 0;

            if (0 != (flags & F_UPGRADE) || Method.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, bool
            // parsingHeader) implementation ...

            if (null != settings.OnHeadersComplete) {
                if (settings.RaiseOnHeadersComplete (this) == 1)
                    flags |= F_SKIPBODY;
            }

            // Exit, the rest of the connect is in a different protocol.
            if (upgrade) {
                settings.RaiseOnMessageComplete(this);
                state = new_message ();
                return false;
            }

            if (0 != (flags & F_SKIPBODY)) {
                settings.RaiseOnMessageComplete(this);
                state = new_message();
            } 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
                    settings.RaiseOnMessageComplete(this);
                    state = new_message();
                } else if (content_length > 0) {
                // Content-Length header given and non-zero
                    state = State.body_identity;
                } else {
                    if (type == ParserType.HTTP_REQUEST || http_should_keep_alive()) {
                        // Assume content-length 0 - read the next
                        settings.RaiseOnMessageComplete(this);
                        state = new_message();
                    } else {
                        // Read body until EOF
                        state = State.body_identity_eof;
                    }
                }
            }
            return true;
        }