HttpServer.HttpRequestParser.ParseMessage C# (CSharp) Method

ParseMessage() public method

Parse a message
public ParseMessage ( byte buffer, int offset, int size ) : int
buffer byte
offset int where in buffer that parsing should start
size int number of bytes to parse
return int
        public int ParseMessage(byte[] buffer, int offset, int size)
        {
            // add body bytes
            if (_curState == State.Body)
            {
                return AddToBody(buffer, 0, size);
            }

            #if DEBUG
            string temp = Encoding.ASCII.GetString(buffer, offset, size);
            _log.Write(this, LogPrio.Trace, "\r\n\r\n HTTP MESSAGE: " + temp + "\r\n");
            #endif

            int currentLine = 1;
            int startPos = -1;

            // set start pos since this is from an partial request
            if (_curState == State.HeaderValue)
                startPos = 0;

            int end = offset + size;

            //<summary>
            // Handled bytes are used to keep track of the number of bytes processed.
            // We do this since we can handle partial requests (to be able to check headers and abort
            // invalid requests directly without having to process the whole header / body).
            // </summary>
            int handledBytes = 0;

            for (int i = offset; i < end; ++i)
            {
                char ch = (char) buffer[i];
                char nextCh = end > i + 1 ? (char)buffer[i + 1] : char.MinValue;

                if (ch == '\r')
                    ++currentLine;

                if (_curState == State.FirstLine)
                {
                    if (i > 4196)
                    {
                        _log.Write(this, LogPrio.Warning, "HTTP Request is too large.");
                        throw new BadRequestException("Too large request line.");
                    }

                    if (char.IsLetterOrDigit(ch) && startPos == -1)
                        startPos = i;

                    // first line can be empty according to RFC, ignore it and move next.
                    if (startPos == -1 && (ch != '\r' || nextCh != '\n'))
                    {
                        _log.Write(this, LogPrio.Warning, "Request line is not found.");
                        throw new BadRequestException("Invalid request line.");
                    }

                    if (startPos != -1 && ch == '\r')
                    {
                        if (nextCh != '\n')
                        {
                            _log.Write(this, LogPrio.Warning, "RFC says that linebreaks should be \\r\\n, got only \\n.");
                            throw new BadRequestException("Invalid line break on request line, expected CrLf.");
                        }

                        OnFirstLine(Encoding.UTF8.GetString(buffer, startPos, i - startPos));
                        _curState = _curState + 1;
                        ++i;
                        handledBytes = i + 1;
                        startPos = -1;
                    }
                }
                    // are parsing header name
                else if (_curState == State.HeaderName)
                {
                    // Check if we got end of header
                    if (ch == '\r')
                    {
                        if (nextCh != '\n')
                        {
                            _log.Write(this, LogPrio.Warning, "Expected crlf, got only lf.");
                            throw new BadRequestException("Expected crlf on line " + currentLine);
                        }

                        ++i; //ignore \r
                        ++i; //ignore \n

                        if (CurrentRequest.ContentLength == 0)
                        {
                            _curState = State.FirstLine;
                            _log.Write(this, LogPrio.Trace, "Request parsed successfully (no content).");
                            RequestCompleted(CurrentRequest);
                            Clear();
                            return i;
                        }

                        _curState = State.Body;
                        if (i + 1 < end)
                        {
                            _log.Write(this, LogPrio.Trace, "Adding bytes to the body");
                            return AddToBody(buffer, i, end - i);
                        }

                        return i;
                    }

                    if (char.IsWhiteSpace(ch) || ch == ':')
                    {
                        if (startPos == -1)
                        {
                            _log.Write(this, LogPrio.Warning, "Expected header name, got colon on line " + currentLine);
                            throw new BadRequestException("Expected header name, got colon on line " + currentLine);
                        }
                        _curHeaderName = Encoding.UTF8.GetString(buffer, startPos, i - startPos);
                        handledBytes = i + 1;
                        startPos = -1;
                        _curState = _curState + 1;
                        if (ch == ':')
                            _curState = _curState + 1;
                    }
                    else if (startPos == -1)
                        startPos = i;
                    else if (!char.IsLetterOrDigit(ch) && ch != '-')
                    {
                        _log.Write(this, LogPrio.Warning, "Invalid character in header name on line " + currentLine);
                        throw new BadRequestException("Invalid character in header name on line " + currentLine);
                    }

                    if (startPos != -1 && i - startPos > 200)
                    {
                        _log.Write(this, LogPrio.Warning, "Invalid header name on line " + currentLine);
                        throw new BadRequestException("Invalid header name on line " + currentLine);
                    }
                }
                else if (_curState == State.AfterName)
                {
                    if (ch == ':')
                    {
                        handledBytes = i+1;
                        _curState = _curState + 1;
                    }
                }
                    // parsing chars between header and value
                else if (_curState == State.Between)
                {
                    if (ch == ' ' || ch == '\t')
                        continue;

                    // continue if we get a new line which is prepended with a whitespace
                    if (ch == '\r' && nextCh == '\n' && i + 3 < end &&
                             char.IsWhiteSpace((char) buffer[i + 2]))
                    {
                        ++i;
                        continue;
                    }

                    startPos = i;
                    _curState = _curState + 1;
                    handledBytes = i;
                    continue;
                }
                else if (_curState == State.HeaderValue)
                {
                    if (ch != '\r')
                        continue;
                    if (nextCh != '\n')
                    {
                        _log.Write(this, LogPrio.Warning, "Invalid linebreak on line " + currentLine);
                        throw new BadRequestException("Invalid linebreak on line " + currentLine);
                    }

                    if (startPos == -1)
                        continue; // allow new lines before start of value

                    //if (_curHeaderName == string.EmptyLanguageNode)
                    //    throw new BadRequestException("Missing header on line " + currentLine);
                    if (startPos == -1)
                    {
                        _log.Write(this, LogPrio.Warning, "Missing header value for '" + _curHeaderName);
                        throw new BadRequestException("Missing header value for '" + _curHeaderName);
                    }
                    if (i - startPos > 1024)
                    {
                        _log.Write(this, LogPrio.Warning, "Too large header value on line " + currentLine);
                        throw new BadRequestException("Too large header value on line " + currentLine);
                    }

                    // Header fields can be extended over multiple lines by preceding each extra line with at
                    // least one SP or HT.
                    if (end > i + 3 && (buffer[i + 2] == ' ' || buffer[i + 2] == buffer['\t']))
                    {
                        if (startPos != -1)
                            _curHeaderValue = Encoding.UTF8.GetString(buffer, startPos, i - startPos);

                        _log.Write(this, LogPrio.Trace, "Header value is on multiple lines.");
                        _curState = State.Between;
                        startPos = -1;
                        ++i;
                        handledBytes = i + 1;
                        continue;
                    }

                    _curHeaderValue += Encoding.UTF8.GetString(buffer, startPos, i - startPos);
                    _log.Write(this, LogPrio.Trace, "Header [" + _curHeaderName + ": " + _curHeaderValue + "]");
                    OnHeader(_curHeaderName, _curHeaderValue);

                    startPos = -1;
                    _curState = State.HeaderName;
                    _curHeaderValue = string.Empty;
                    _curHeaderName = string.Empty;
                    ++i;
                    handledBytes = i + 1;

                    // Check if we got a colon so we can cut header name, or crlf for end of header.
                    bool canContinue = false;
                    for (int j = i; j < end; ++j)
                    {
                        if (buffer[j] == ':' || buffer[j] == '\r')
                        {
                            canContinue = true;
                            break;
                        }
                    }
                    if (!canContinue)
                    {
                        _log.Write(this, LogPrio.Trace, "Cant continue, no colon.");
                        return i + 1;
                    }
                }
            }

            return handledBytes;
        }