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;
}