internal bool Read(Stream stream, IPEndPoint endPoint) { byte[] buffer = new byte[1024]; RequestParserState state = RequestParserState.ReadMethod; string key = ""; string value = ""; MemoryStream ms = null; DateTime begin = DateTime.Now; DateTime lastByteReceived = begin; if (endPoint != null) UserHostAddress = endPoint.Address.ToString(); while (true) { if (state == RequestParserState.ReadDone) return true; int bytesRead = 0; int idx = 0; #if(MF) // set all bytes to null byte (strings are ending with null byte in MF) Array.Clear(buffer, 0, buffer.Length); #endif try { bytesRead = stream.Read(buffer, 0, buffer.Length); if (bytesRead > 0) lastByteReceived = DateTime.Now; } catch (IOException) { break; } catch (Exception) { DateTime nd = DateTime.Now; #if(MF) if((nd.Ticks - lastByteReceived.Ticks) / TimeSpan.TicksPerMillisecond > MAX_REQUEST_SLIDING_TIMEOUT) break; #else if ((nd - lastByteReceived).TotalMilliseconds > MAX_REQUEST_SLIDING_TIMEOUT) break; #endif if (HttpMethod == "POST" && (_body == null || _body.Length < ContentLength)) continue; #if(MF) if((nd.Ticks - begin.Ticks) / TimeSpan.TicksPerMillisecond < MAX_REQUEST_TIMEOUT) continue; #else if ((nd - begin).TotalMilliseconds < MAX_REQUEST_TIMEOUT) continue; #endif break; } if (bytesRead == 0) // should never happen break; totalBytes += bytesRead; #if(FILELOG && !MF && !WindowsCE) File.AppendAllText("loghttp-" + socket.RemoteEndPoint.ToString().Replace(":", "-") + ".txt", Encoding.UTF8.GetString(buffer, 0, bytesRead) + "\r\n"); #endif if (totalBytes <= idx) continue; do { switch (state) { case RequestParserState.ReadMethod: if (buffer[idx] != ' ') HttpMethod += (char)buffer[idx++]; else { // TODO: add a allowed methods list //if (HttpMethod != "POST" && HttpMethod != "GET" && HttpMethod != "OPTIONS") // throw new HttpException(HttpStatusCode.MethodNotAllowed); idx++; state = RequestParserState.ReadUrl; } break; case RequestParserState.ReadUrl: if (buffer[idx] == '?') { idx++; key = ""; _params = new NameValueCollection(); state = RequestParserState.ReadParamKey; } else if (buffer[idx] != ' ') { RawUrl += (char)buffer[idx++]; } else { idx++; RawUrl = HttpServerUtility.UrlDecode(RawUrl); state = RequestParserState.ReadVersion; } break; case RequestParserState.ReadParamKey: if (buffer[idx] == '=') { idx++; value = ""; state = RequestParserState.ReadParamValue; } else if (buffer[idx] == ' ') { idx++; RawUrl = HttpServerUtility.UrlDecode(RawUrl); state = RequestParserState.ReadVersion; } else { key += (char)buffer[idx++]; } break; case RequestParserState.ReadParamValue: if (buffer[idx] == '&') { idx++; key = HttpServerUtility.UrlDecode(key); value = HttpServerUtility.UrlDecode(value); Params[key] = (Params[key] != null ? Params[key] + ", " + value : value); key = ""; value = ""; state = RequestParserState.ReadParamKey; } else if (buffer[idx] == ' ') { idx++; key = HttpServerUtility.UrlDecode(key); value = HttpServerUtility.UrlDecode(value); Params[key] = (Params[key] != null ? Params[key] + ", " + value : value); RawUrl = HttpServerUtility.UrlDecode(RawUrl); state = RequestParserState.ReadVersion; } else { value += (char)buffer[idx++]; } break; case RequestParserState.ReadVersion: if (buffer[idx] == '\r') idx++; else if (buffer[idx] != '\n') HttpVersion += (char)buffer[idx++]; else { if (HttpVersion != "HTTP/1.1" && HttpVersion != "HTTP/1.0") throw new HttpException(HttpStatusCode.HttpVersionNotSupported); idx++; key = ""; Headers = new NameValueCollection(); state = RequestParserState.ReadHeaderKey; } break; case RequestParserState.ReadHeaderKey: if (buffer[idx] == '\r') idx++; else if (buffer[idx] == '\n') { idx++; if (HttpMethod == "POST") state = RequestParserState.ReadBody; else { state = RequestParserState.ReadDone; // well, we don't really need this return true; } } else if (buffer[idx] == ':') idx++; else if (buffer[idx] != ' ') key += (char)buffer[idx++]; else { idx++; value = ""; state = RequestParserState.ReadHeaderValue; } break; case RequestParserState.ReadHeaderValue: if (buffer[idx] == '\r') idx++; else if (buffer[idx] != '\n') value += (char)buffer[idx++]; else { idx++; Headers.Add(key, value); key = ""; state = RequestParserState.ReadHeaderKey; } break; case RequestParserState.ReadBody: if (ContentLength > MAX_CONTENT_LENGTH) { // TODO: how can I stop the client to cancel http request //throw new HttpException(HttpStatusCode.RequestEntitiyTooLarge); } if (ms == null) ms = new MemoryStream(); ms.Write(buffer, idx, bytesRead - idx); idx = bytesRead; if (ms.Length >= ContentLength) { _body = ms.ToArray(); // if using a <form/> tag with POST check if it is urlencoded or multipart boundary if (ContentType.IndexOf("application/x-www-form-urlencoded") != -1) { _form = new NameValueCollection(); key = ""; value = null; for (int i = 0; i < _body.Length; i++) { if (_body[i] == '=') value = ""; else if (_body[i] == '&') { _form.Add(key, value != null ? HttpServerUtility.UrlDecode(value) : ""); key = ""; value = null; } else if (value == null) key += (char)_body[i]; else if (value != null) value += (char)_body[i]; } if (key != null && key.Length > 0) { _form.Add(key, value != null ? HttpServerUtility.UrlDecode(value) : ""); } } else if (ContentType != null && ContentType.Length > "multipart/form-data; boundary=".Length && ContentType.Substring(0, "multipart/form-data; boundary=".Length) == "multipart/form-data; boundary=") { string boundary = ContentType.Substring("multipart/form-data; boundary=".Length); _mime = new MimeContentCollection(); MimeParser mp = new MimeParser(_body, boundary); MimeContent mime = mp.GetNextContent(); while (mime != null) { _mime.Add(mime.Name, mime); if (mime.Headers["Content-Disposition"] != null && mime.Headers["Content-Disposition"].IndexOf("form-data") >= 0) { if (_form == null) _form = new NameValueCollection(); _form.Add(mime.Name, (mime.Content != null && mime.Content.Length > 0 ? new string(Encoding.UTF8.GetChars(mime.Content)) : "")); } mime = mp.GetNextContent(); } } state = RequestParserState.ReadDone; // well, we don't really need this return true; } break; case RequestParserState.ReadDone: // well, we don't really need this return true; default: //idx++; break; } } while (idx < bytesRead); } return false; }
/// <summary> /// Parse a message /// </summary> /// <param name="buffer">bytes to parse.</param> /// <param name="offset">where in buffer that parsing should start</param> /// <param name="count">number of bytes to parse, starting on <paramref name="offset"/>.</param> /// <returns>offset (where to start parsing next).</returns> /// <exception cref="BadRequestException"><c>BadRequestException</c>.</exception> public int Parse(byte[] buffer, int offset, int count) { // add body bytes if (CurrentState == RequestParserState.Body) { // copy all remaining bytes to the beginning of the buffer. //Buffer.BlockCopy(buffer, offset + bytesUsed, buffer, 0, count - bytesUsed); return AddToBody(buffer, 0, count); } #if DEBUG string temp = Encoding.ASCII.GetString(buffer, offset, count); _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 (CurrentState == RequestParserState.HeaderValue) startPos = 0; int endOfBufferPos = offset + count; //<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 currentPos = offset; currentPos < endOfBufferPos; ++currentPos) { var ch = (char) buffer[currentPos]; char nextCh = endOfBufferPos > currentPos + 1 ? (char) buffer[currentPos + 1] : char.MinValue; if (ch == '\r') ++currentLine; switch (CurrentState) { case RequestParserState.FirstLine: if (currentPos > 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 = currentPos; 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' || ch == '\n')) { int size = GetLineBreakSize(buffer, currentPos); OnFirstLine(Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos)); CurrentState = CurrentState + 1; currentPos += size - 1; handledBytes = currentPos + size - 1; startPos = -1; } break; case RequestParserState.HeaderName: if (ch == '\r' || ch == '\n') { currentPos += GetLineBreakSize(buffer, currentPos); if (_bodyBytesLeft == 0) { CurrentState = RequestParserState.FirstLine; _log.Write(this, LogPrio.Trace, "Request parsed successfully (no content)."); OnRequestCompleted(); Clear(); return currentPos; } CurrentState = RequestParserState.Body; if (currentPos + 1 < endOfBufferPos) { _log.Write(this, LogPrio.Trace, "Adding bytes to the body"); return AddToBody(buffer, currentPos, endOfBufferPos - currentPos); } return currentPos; } 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, currentPos - startPos); handledBytes = currentPos + 1; startPos = -1; CurrentState = CurrentState + 1; if (ch == ':') CurrentState = CurrentState + 1; } else if (startPos == -1) startPos = currentPos; else if (!char.IsLetterOrDigit(ch) && ch != '-' && 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 && currentPos - startPos > 200) { _log.Write(this, LogPrio.Warning, "Invalid header name on line " + currentLine); throw new BadRequestException("Invalid header name on line " + currentLine); } break; case RequestParserState.AfterName: if (ch == ':') { handledBytes = currentPos + 1; CurrentState = CurrentState + 1; } break; case RequestParserState.Between: { if (ch == ' ' || ch == '\t') continue; int newLineSize = GetLineBreakSize(buffer, currentPos); if (newLineSize > 0 && currentPos + newLineSize < endOfBufferPos && char.IsWhiteSpace((char) buffer[currentPos + newLineSize])) { ++currentPos; continue; } startPos = currentPos; CurrentState = CurrentState + 1; handledBytes = currentPos; continue; } case RequestParserState.HeaderValue: { if (ch != '\r' && ch != '\n') continue; int newLineSize = GetLineBreakSize(buffer, currentPos); if (startPos == -1) continue; // allow new lines before start of value if (_curHeaderName == string.Empty) 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 (currentPos - startPos > 4096) { _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 (endOfBufferPos > currentPos + newLineSize && (buffer[currentPos + newLineSize] == ' ' || buffer[currentPos + newLineSize] == '\t')) { if (startPos != -1) _curHeaderValue = Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos); _log.Write(this, LogPrio.Trace, "Header value is on multiple lines."); CurrentState = RequestParserState.Between; startPos = -1; currentPos += newLineSize - 1; handledBytes = currentPos + newLineSize - 1; continue; } _curHeaderValue += Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos); _log.Write(this, LogPrio.Trace, "Header [" + _curHeaderName + ": " + _curHeaderValue + "]"); OnHeader(_curHeaderName, _curHeaderValue); startPos = -1; CurrentState = RequestParserState.HeaderName; _curHeaderValue = string.Empty; _curHeaderName = string.Empty; ++currentPos; handledBytes = currentPos + 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 = currentPos; j < endOfBufferPos; ++j) { if (buffer[j] != ':' && buffer[j] != '\r' && buffer[j] != '\n') continue; canContinue = true; break; } if (!canContinue) { _log.Write(this, LogPrio.Trace, "Cant continue, no colon."); return currentPos + 1; } } break; } } return handledBytes; }
/// <summary> /// Reades and creates a http request object from the given stream. /// </summary> /// <param name="inputStream">the stream to read the http request from</param> /// <returns></returns> public static async Task <HttpRequest> ParseFromStream(Stream inputStream) { Debug.Assert(inputStream != null); byte[] myReadBuffer = new byte[BufferSize]; RequestParserState parserState = RequestParserState.Method; HttpRequest httpRequest = new HttpRequest(); String myCompleteMessage = ""; int numberOfBytesRead = 0; string hValue = ""; string hKey = ""; // binary data buffer index int bfndx = 0; // Incoming message may be larger than the buffer size. do { numberOfBytesRead = await inputStream.ReadAsync(myReadBuffer, 0, myReadBuffer.Length); myCompleteMessage = String.Concat(myCompleteMessage, Encoding.UTF8.GetString(myReadBuffer.ToArray(), 0, numberOfBytesRead)); // read buffer index int ndx = 0; do { switch (parserState) { case RequestParserState.Method: if (myReadBuffer[ndx] != ' ') { httpRequest.Method += (char)myReadBuffer[ndx++]; } else { ndx++; parserState = RequestParserState.Url; } break; case RequestParserState.Url: if (myReadBuffer[ndx] == '?') { ndx++; hKey = ""; parserState = RequestParserState.Urlparm; } else if (myReadBuffer[ndx] != ' ') { httpRequest.Url += (char)myReadBuffer[ndx++]; } else { ndx++; httpRequest.Url = HttpUtility.UrlDecode(httpRequest.Url); parserState = RequestParserState.Version; } break; case RequestParserState.Urlparm: if (myReadBuffer[ndx] == '=') { ndx++; hValue = ""; parserState = RequestParserState.Urlvalue; } else if (myReadBuffer[ndx] == ' ') { ndx++; httpRequest.Url = HttpUtility.UrlDecode(httpRequest.Url); parserState = RequestParserState.Version; } else { hKey += (char)myReadBuffer[ndx++]; } break; case RequestParserState.Urlvalue: if (myReadBuffer[ndx] == '&') { ndx++; hKey = HttpUtility.UrlDecode(hKey); hValue = HttpUtility.UrlDecode(hValue); httpRequest.Args[hKey] = httpRequest.Args[hKey] != null ? httpRequest.Args[hKey] + ", " + hValue : hValue; hKey = ""; parserState = RequestParserState.Urlparm; } else if (myReadBuffer[ndx] == ' ') { ndx++; hKey = HttpUtility.UrlDecode(hKey); hValue = HttpUtility.UrlDecode(hValue); httpRequest.Args[hKey] = httpRequest.Args[hKey] != null ? httpRequest.Args[hKey] + ", " + hValue : hValue; httpRequest.Url = HttpUtility.UrlDecode(httpRequest.Url); parserState = RequestParserState.Version; } else { hValue += (char)myReadBuffer[ndx++]; } break; case RequestParserState.Version: if (myReadBuffer[ndx] == '\r') { ndx++; } else if (myReadBuffer[ndx] != '\n') { httpRequest.Version += (char)myReadBuffer[ndx++]; } else { ndx++; hKey = ""; parserState = RequestParserState.Headerkey; } break; case RequestParserState.Headerkey: if (myReadBuffer[ndx] == '\r') { ndx++; } else if (myReadBuffer[ndx] == '\n') { ndx++; if (httpRequest.Headers["Content-Length"] != null) { httpRequest.BodySize = Convert.ToInt32(httpRequest.Headers["Content-Length"]); httpRequest.BodyData = new byte[httpRequest.BodySize]; parserState = RequestParserState.Body; } else { parserState = RequestParserState.Ok; } } else if (myReadBuffer[ndx] == ':') { ndx++; } else if (myReadBuffer[ndx] != ' ') { hKey += (char)myReadBuffer[ndx++]; } else { ndx++; hValue = ""; parserState = RequestParserState.Headervalue; } break; case RequestParserState.Headervalue: if (myReadBuffer[ndx] == '\r') { ndx++; } else if (myReadBuffer[ndx] != '\n') { hValue += (char)myReadBuffer[ndx++]; } else { ndx++; httpRequest.Headers.Add(hKey, hValue); hKey = ""; parserState = RequestParserState.Headerkey; } break; case RequestParserState.Body: // Append to request BodyData Array.Copy(myReadBuffer, ndx, httpRequest.BodyData, bfndx, numberOfBytesRead - ndx); bfndx += numberOfBytesRead - ndx; ndx = numberOfBytesRead; if (httpRequest.BodySize <= bfndx) { parserState = RequestParserState.Ok; } break; //default: // ndx++; // break; } }while (ndx < numberOfBytesRead); }while (numberOfBytesRead == BufferSize); if (httpRequest.BodyData == null) { httpRequest.BodyData = new byte[0]; } if (parserState == RequestParserState.Ok) { httpRequest.IsValid = true; } return(httpRequest); }
public ParsedHttpRequest(NetworkStream stream, MessageLogger messageLogger, IDataLogger dataLogger) { int bytesRead; byte[] readBuffer = new byte[2048]; parserState = RequestParserState.Method; StringBuilder stringBuilder = new StringBuilder(InitialCapacityForMethod); String hValue = String.Empty; String hKey = String.Empty; String temp; Int32 bodyIndex = 0; do { switch (parserState) { case RequestParserState.Method: messageLogger.Log("Reading Request"); break; case RequestParserState.Body: messageLogger.Log("Waiting for {0} bytes for the body", body.Length - bodyIndex); break; default: messageLogger.Log("Waiting for more data (ParserState={0})...", parserState); break; } bytesRead = stream.Read(readBuffer, 0, readBuffer.Length); if (bytesRead <= 0) { break; } if (dataLogger != null) { dataLogger.LogData(readBuffer, 0, bytesRead); } int offset = 0; do { switch (parserState) { case RequestParserState.Method: if (readBuffer[offset] != ' ') { stringBuilder.Append((char)readBuffer[offset]); } else { method = stringBuilder.ToString(); stringBuilder = new StringBuilder(InitialCapacityForUrl); parserState = RequestParserState.Url; } offset++; break; case RequestParserState.Url: if (readBuffer[offset] == '?') { url = Http.UrlDecode(stringBuilder.ToString()); hKey = String.Empty; urlArguments = new Dictionary <String, String>(); parserState = RequestParserState.UrlParam; } else if (readBuffer[offset] != ' ') { stringBuilder.Append((char)readBuffer[offset]); } else { url = Http.UrlDecode(stringBuilder.ToString()); parserState = RequestParserState.Version; } offset++; break; case RequestParserState.UrlParam: if (readBuffer[offset] == '=') { offset++; hValue = String.Empty; parserState = RequestParserState.UrlParamValue; } else if (readBuffer[offset] == ' ') { offset++; url = Http.UrlDecode(url); parserState = RequestParserState.Version; } else { hKey += (char)readBuffer[offset++]; } break; case RequestParserState.UrlParamValue: if (readBuffer[offset] == '&') { offset++; hKey = Http.UrlDecode(hKey); hValue = Http.UrlDecode(hValue); if (urlArguments.TryGetValue(hKey, out temp)) { urlArguments[hKey] = String.Format("{0},{1}", temp, hValue); } else { urlArguments[hKey] = hValue; } hKey = String.Empty; parserState = RequestParserState.UrlParam; } else if (readBuffer[offset] == ' ') { offset++; hKey = Http.UrlDecode(hKey); hValue = Http.UrlDecode(hValue); if (urlArguments.TryGetValue(hKey, out temp)) { urlArguments[hKey] = String.Format("{0},{1}", temp, hValue); } else { urlArguments[hKey] = hValue; } parserState = RequestParserState.Version; } else { hValue += (char)readBuffer[offset++]; } break; case RequestParserState.Version: if (readBuffer[offset] == '\r') { offset++; } if (readBuffer[offset] != '\n') { httpVersion += (char)readBuffer[offset++]; } else { offset++; hKey = String.Empty; headers = new Dictionary <String, String>(); parserState = RequestParserState.HeaderKey; } break; case RequestParserState.HeaderKey: if (readBuffer[offset] == '\r') { offset++; } if (readBuffer[offset] == '\n') { offset++; if (headers.TryGetValue("Content-Length", out temp)) { body = new byte[Int32.Parse(temp)]; parserState = RequestParserState.Body; } else { parserState = RequestParserState.Done; } } else if (readBuffer[offset] == ':') { offset++; } else if (readBuffer[offset] != ' ') { hKey += (char)readBuffer[offset++]; } else { offset++; hValue = ""; parserState = RequestParserState.HeaderValue; } break; case RequestParserState.HeaderValue: if (readBuffer[offset] == '\r') { offset++; } if (readBuffer[offset] == '\n') { offset++; headers.Add(hKey, hValue); hKey = String.Empty; parserState = RequestParserState.HeaderKey; } else { hValue += (char)readBuffer[offset++]; } break; case RequestParserState.Body: // Append to request BodyData Array.Copy(readBuffer, offset, body, bodyIndex, bytesRead - offset); bodyIndex += bytesRead - offset; offset = bytesRead; if (bodyIndex >= ((body == null) ? 0 : body.Length)) { parserState = RequestParserState.Done; } break; default: throw new InvalidOperationException(String.Format("Unrecognized Parser State '{0}' ({1})", parserState, (int)parserState)); } }while (offset < bytesRead); } while ((parserState != RequestParserState.Done) && stream.DataAvailable); }
protected override int ProcessData(byte[] data, bool writeExcess) { ThrowIfEnded(); bool run = true; int i = 0; for (; i < data.Length && run; i++) { char c = (char)data[i]; switch (State) { case RequestParserState.Method: if (c != WHITESPACE) { StringQueue.Append(c); } else { Incoming.Method = StringQueue.Next(); StringQueue.New(); State = RequestParserState.Query; } break; case RequestParserState.Query: if (c != WHITESPACE) { StringQueue.Append(c); } else { if (!Query.TryParse(StringQueue.Next(), out Query result)) { End(); return(-1); } Incoming.Query = result; StringQueue.New(); State = RequestParserState.Version; } break; case RequestParserState.Version: if (c != CR) { StringQueue.Append(c); } else { if (!HttpVersion.TryParse(StringQueue.Next(), out HttpVersion result)) { End(); return(-1); } Incoming.Version = result; StringQueue.New(); State = RequestParserState.FirstLf; } break; case RequestParserState.FirstLf: if (c != LF) { End(); return(-1); } State = RequestParserState.HeaderName; break; case RequestParserState.HeaderName: if (c == CR) { State = RequestParserState.Lf; } else if (c != COLON) { StringQueue.Append(c); } else { StringQueue.New(); State = RequestParserState.HeaderValue; } break; case RequestParserState.HeaderValue: if (c != CR) { StringQueue.Append(c); } else { Incoming.Headers.Set(StringQueue.Next(), StringQueue.Next().Trim()); State = RequestParserState.HeaderLf; } break; case RequestParserState.HeaderLf: if (c != LF) { End(); return(-1); } else { StringQueue.New(); State = RequestParserState.HeaderName; } break; case RequestParserState.Lf: if (c != LF) { End(); return(-1); } run = false; PushIncoming(); State = RequestParserState.Method; break; } } if (writeExcess) { int len = data.Length - i; byte[] sliced = new byte[len]; System.Buffer.BlockCopy(data, i, sliced, 0, len); Bwrite(sliced); } return(i); }
/// <summary> /// Parse a message /// </summary> /// <param name="buffer">bytes to parse.</param> /// <param name="offset">where in buffer that parsing should start</param> /// <param name="count">number of bytes to parse, starting on <paramref name="offset"/>.</param> /// <returns>offset (where to start parsing next).</returns> /// <exception cref="BadRequestException"><c>BadRequestException</c>.</exception> public int Parse(byte[] buffer, int offset, int count) { // add body bytes if (CurrentState == RequestParserState.Body) { // copy all remaining bytes to the beginning of the buffer. //Buffer.BlockCopy(buffer, offset + bytesUsed, buffer, 0, count - bytesUsed); return(AddToBody(buffer, 0, count)); } #if DEBUG string temp = Encoding.ASCII.GetString(buffer, offset, count); _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 (CurrentState == RequestParserState.HeaderValue) { startPos = 0; } int endOfBufferPos = offset + count; //<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 currentPos = offset; currentPos < endOfBufferPos; ++currentPos) { var ch = (char)buffer[currentPos]; char nextCh = endOfBufferPos > currentPos + 1 ? (char)buffer[currentPos + 1] : char.MinValue; if (ch == '\r') { ++currentLine; } switch (CurrentState) { case RequestParserState.FirstLine: if (currentPos > 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 = currentPos; } 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' || ch == '\n')) { int size = GetLineBreakSize(buffer, currentPos); OnFirstLine(Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos)); CurrentState = CurrentState + 1; currentPos += size - 1; handledBytes = currentPos + size - 1; startPos = -1; } break; case RequestParserState.HeaderName: if (ch == '\r' || ch == '\n') { currentPos += GetLineBreakSize(buffer, currentPos); if (_bodyBytesLeft == 0) { CurrentState = RequestParserState.FirstLine; _log.Write(this, LogPrio.Trace, "Request parsed successfully (no content)."); OnRequestCompleted(); Clear(); return(currentPos); } CurrentState = RequestParserState.Body; if (currentPos + 1 < endOfBufferPos) { _log.Write(this, LogPrio.Trace, "Adding bytes to the body"); return(AddToBody(buffer, currentPos, endOfBufferPos - currentPos)); } return(currentPos); } 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, currentPos - startPos); handledBytes = currentPos + 1; startPos = -1; CurrentState = CurrentState + 1; if (ch == ':') { CurrentState = CurrentState + 1; } } else if (startPos == -1) { startPos = currentPos; } else if (!char.IsLetterOrDigit(ch) && ch != '-' && 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 && currentPos - startPos > 200) { _log.Write(this, LogPrio.Warning, "Invalid header name on line " + currentLine); throw new BadRequestException("Invalid header name on line " + currentLine); } break; case RequestParserState.AfterName: if (ch == ':') { handledBytes = currentPos + 1; CurrentState = CurrentState + 1; } break; case RequestParserState.Between: { if (ch == ' ' || ch == '\t') { continue; } int newLineSize = GetLineBreakSize(buffer, currentPos); if (newLineSize > 0 && currentPos + newLineSize < endOfBufferPos && char.IsWhiteSpace((char)buffer[currentPos + newLineSize])) { ++currentPos; continue; } startPos = currentPos; CurrentState = CurrentState + 1; handledBytes = currentPos; continue; } case RequestParserState.HeaderValue: { if (ch != '\r' && ch != '\n') { continue; } int newLineSize = GetLineBreakSize(buffer, currentPos); if (startPos == -1) { continue; // allow new lines before start of value } if (_curHeaderName == string.Empty) { 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 (currentPos - startPos > 4096) { _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 (endOfBufferPos > currentPos + newLineSize && (buffer[currentPos + newLineSize] == ' ' || buffer[currentPos + newLineSize] == '\t')) { if (startPos != -1) { _curHeaderValue = Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos); } _log.Write(this, LogPrio.Trace, "Header value is on multiple lines."); CurrentState = RequestParserState.Between; startPos = -1; currentPos += newLineSize - 1; handledBytes = currentPos + newLineSize - 1; continue; } _curHeaderValue += Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos); _log.Write(this, LogPrio.Trace, "Header [" + _curHeaderName + ": " + _curHeaderValue + "]"); OnHeader(_curHeaderName, _curHeaderValue); startPos = -1; CurrentState = RequestParserState.HeaderName; _curHeaderValue = string.Empty; _curHeaderName = string.Empty; ++currentPos; handledBytes = currentPos + 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 = currentPos; j < endOfBufferPos; ++j) { if (buffer[j] != ':' && buffer[j] != '\r' && buffer[j] != '\n') { continue; } canContinue = true; break; } if (!canContinue) { _log.Write(this, LogPrio.Trace, "Cant continue, no colon."); return(currentPos + 1); } } break; } } return(handledBytes); }