private void ParseChunkedPrefix() { var scan = _input.ConsumingStart(); var consumed = scan; try { var ch1 = scan.Take(); var ch2 = scan.Take(); if (ch1 == -1 || ch2 == -1) { return; } var chunkSize = CalculateChunkSize(ch1, 0); ch1 = ch2; do { if (ch1 == ';') { consumed = scan; _inputLength = chunkSize; _mode = Mode.Extension; return; } ch2 = scan.Take(); if (ch2 == -1) { return; } if (ch1 == '\r' && ch2 == '\n') { consumed = scan; _inputLength = chunkSize; if (chunkSize > 0) { _mode = Mode.Data; } else { _mode = Mode.Trailer; } return; } chunkSize = CalculateChunkSize(ch1, chunkSize); ch1 = ch2; } while (ch1 != -1); } finally { _input.ConsumingComplete(consumed, scan); } }
private void ParseChunkedTrailer(SocketInput input) { var scan = input.ConsumingStart(); var consumed = scan; try { var ch1 = scan.Take(); var ch2 = scan.Take(); if (ch1 == -1 || ch2 == -1) { return; } else if (ch1 == '\r' && ch2 == '\n') { consumed = scan; _mode = Mode.Complete; } else { _mode = Mode.TrailerHeaders; } } finally { input.ConsumingComplete(consumed, scan); } }
private void ParseChunkedSuffix(SocketInput input) { var scan = input.ConsumingStart(); var consumed = scan; try { var ch1 = scan.Take(); var ch2 = scan.Take(); if (ch1 == -1 || ch2 == -1) { return; } else if (ch1 == '\r' && ch2 == '\n') { consumed = scan; _mode = Mode.Prefix; } else { _context.RejectRequest("Bad chunk suffix"); } } finally { input.ConsumingComplete(consumed, scan); } }
private void ParseExtension(SocketInput input) { var scan = input.ConsumingStart(); var consumed = scan; try { // Chunk-extensions not currently parsed // Just drain the data do { if (scan.Seek(ref _vectorCRs) == -1) { // End marker not found yet consumed = scan; return; } ; var ch1 = scan.Take(); var ch2 = scan.Take(); if (ch2 == '\n') { consumed = scan; if (_inputLength > 0) { _mode = Mode.Data; } else { _mode = Mode.Trailer; } } else if (ch2 == -1) { return; } } while (_mode == Mode.Extension); } finally { input.ConsumingComplete(consumed, scan); } }
private static async Task <ArraySegment <byte> > PeekAsyncAwaited(this SocketInput input) { while (true) { await input; var fin = input.CheckFinOrThrow(); var begin = input.ConsumingStart(); var segment = begin.PeekArraySegment(); input.ConsumingComplete(begin, begin); if (segment.Count != 0 || fin) { return(segment); } } }
public static ValueTask <ArraySegment <byte> > PeekAsync(this SocketInput input) { while (input.IsCompleted) { var fin = input.CheckFinOrThrow(); var begin = input.ConsumingStart(); var segment = begin.PeekArraySegment(); input.ConsumingComplete(begin, begin); if (segment.Count != 0 || fin) { return(new ValueTask <ArraySegment <byte> >(segment)); } } return(new ValueTask <ArraySegment <byte> >(input.PeekAsyncAwaited())); }
private static async Task <int> ReadAsyncAwaited(this SocketInput input, byte[] buffer, int offset, int count) { while (true) { await input; var fin = input.CheckFinOrThrow(); var begin = input.ConsumingStart(); int actual; var end = begin.CopyTo(buffer, offset, count, out actual); input.ConsumingComplete(end, end); if (actual != 0 || fin) { return(actual); } } }
public static ValueTask <int> ReadAsync(this SocketInput input, byte[] buffer, int offset, int count) { while (input.IsCompleted) { var fin = input.CheckFinOrThrow(); var begin = input.ConsumingStart(); int actual; var end = begin.CopyTo(buffer, offset, count, out actual); input.ConsumingComplete(end, end); if (actual != 0 || fin) { return(new ValueTask <int>(actual)); } } return(new ValueTask <int>(input.ReadAsyncAwaited(buffer, offset, count))); }
private int ReadChunkedData(SocketInput input, byte[] buffer, int offset, int count) { var scan = input.ConsumingStart(); int actual; try { var limit = buffer == null ? _inputLength : Math.Min(count, _inputLength); scan = scan.CopyTo(buffer, offset, limit, out actual); _inputLength -= actual; } finally { input.ConsumingComplete(scan, scan); } if (_inputLength == 0) { _mode = Mode.Suffix; } return(actual); }
protected RequestLineStatus TakeStartLine(SocketInput input) { var scan = input.ConsumingStart(); var consumed = scan; try { // We may hit this when the client has stopped sending data but // the connection hasn't closed yet, and therefore Frame.Stop() // hasn't been called yet. if (scan.Peek() == -1) { return(RequestLineStatus.Empty); } _requestProcessingStatus = RequestProcessingStatus.RequestStarted; string method; var begin = scan; if (!begin.GetKnownMethod(out method)) { if (scan.Seek(ref _vectorSpaces) == -1) { return(RequestLineStatus.MethodIncomplete); } method = begin.GetAsciiString(scan); if (method == null) { RejectRequest("Missing method."); } // Note: We're not in the fast path any more (GetKnownMethod should have handled any HTTP Method we're aware of) // So we can be a tiny bit slower and more careful here. for (int i = 0; i < method.Length; i++) { if (!IsValidTokenChar(method[i])) { RejectRequest("Invalid method."); } } } else { scan.Skip(method.Length); } scan.Take(); begin = scan; var needDecode = false; var chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref _vectorPercentages); if (chFound == -1) { return(RequestLineStatus.TargetIncomplete); } else if (chFound == '%') { needDecode = true; chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks); if (chFound == -1) { return(RequestLineStatus.TargetIncomplete); } } var pathBegin = begin; var pathEnd = scan; var queryString = ""; if (chFound == '?') { begin = scan; if (scan.Seek(ref _vectorSpaces) == -1) { return(RequestLineStatus.TargetIncomplete); } queryString = begin.GetAsciiString(scan); } var queryEnd = scan; if (pathBegin.Peek() == ' ') { RejectRequest("Missing request target."); } scan.Take(); begin = scan; if (scan.Seek(ref _vectorCRs) == -1) { return(RequestLineStatus.VersionIncomplete); } string httpVersion; if (!begin.GetKnownVersion(out httpVersion)) { // A slower fallback is necessary since the iterator's PeekLong() method // used in GetKnownVersion() only examines two memory blocks at most. // Although unlikely, it is possible that the 8 bytes forming the version // could be spread out on more than two blocks, if the connection // happens to be unusually slow. httpVersion = begin.GetAsciiString(scan); if (httpVersion == null) { RejectRequest("Missing HTTP version."); } else if (httpVersion != "HTTP/1.0" && httpVersion != "HTTP/1.1") { RejectRequest("Unrecognized HTTP version."); } } scan.Take(); var next = scan.Take(); if (next == -1) { return(RequestLineStatus.Incomplete); } else if (next != '\n') { RejectRequest("Missing LF in request line."); } // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11 // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8; // then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs" string requestUrlPath; string rawTarget; if (needDecode) { // Read raw target before mutating memory. rawTarget = pathBegin.GetAsciiString(queryEnd); // URI was encoded, unescape and then parse as utf8 pathEnd = UrlPathDecoder.Unescape(pathBegin, pathEnd); requestUrlPath = pathBegin.GetUtf8String(pathEnd); } else { // URI wasn't encoded, parse as ASCII requestUrlPath = pathBegin.GetAsciiString(pathEnd); if (queryString.Length == 0) { // No need to allocate an extra string if the path didn't need // decoding and there's no query string following it. rawTarget = requestUrlPath; } else { rawTarget = pathBegin.GetAsciiString(queryEnd); } } var normalizedTarget = PathNormalizer.RemoveDotSegments(requestUrlPath); consumed = scan; Method = method; QueryString = queryString; RawTarget = rawTarget; HttpVersion = httpVersion; bool caseMatches; if (RequestUrlStartsWithPathBase(normalizedTarget, out caseMatches)) { PathBase = caseMatches ? _pathBase : normalizedTarget.Substring(0, _pathBase.Length); Path = normalizedTarget.Substring(_pathBase.Length); } else if (rawTarget[0] == '/') // check rawTarget since normalizedTarget can be "" or "/" after dot segment removal { Path = normalizedTarget; } else { Path = string.Empty; PathBase = string.Empty; QueryString = string.Empty; } return(RequestLineStatus.Done); } finally { input.ConsumingComplete(consumed, scan); } }
public bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders) { var scan = input.ConsumingStart(); var consumed = scan; try { while (!scan.IsEnd) { var ch = scan.Peek(); if (ch == -1) { return(false); } else if (ch == '\r') { // Check for final CRLF. scan.Take(); ch = scan.Take(); if (ch == -1) { return(false); } else if (ch == '\n') { consumed = scan; return(true); } // Headers don't end in CRLF line. RejectRequest("Headers corrupted, invalid header sequence."); } else if (ch == ' ' || ch == '\t') { RejectRequest("Header line must not start with whitespace."); } var beginName = scan; if (scan.Seek(ref _vectorColons, ref _vectorCRs) == -1) { return(false); } var endName = scan; ch = scan.Take(); if (ch != ':') { RejectRequest("No ':' character found in header line."); } var validateName = beginName; if (validateName.Seek(ref _vectorSpaces, ref _vectorTabs, ref _vectorColons) != ':') { RejectRequest("Whitespace is not allowed in header name."); } var beginValue = scan; ch = scan.Peek(); if (ch == -1) { return(false); } // Skip header value leading whitespace. while (ch == ' ' || ch == '\t') { scan.Take(); beginValue = scan; ch = scan.Peek(); if (ch == -1) { return(false); } } scan = beginValue; if (scan.Seek(ref _vectorCRs) == -1) { // no "\r" in sight, burn used bytes and go back to await more data return(false); } scan.Take(); // we know this is '\r' ch = scan.Take(); // expecting '\n' if (ch == -1) { return(false); } else if (ch != '\n') { RejectRequest("Header line must end in CRLF; only CR found."); } var next = scan.Peek(); if (next == -1) { return(false); } else if (next == ' ' || next == '\t') { // From https://tools.ietf.org/html/rfc7230#section-3.2.4: // // Historically, HTTP header field values could be extended over // multiple lines by preceding each extra line with at least one space // or horizontal tab (obs-fold). This specification deprecates such // line folding except within the message/http media type // (Section 8.3.1). A sender MUST NOT generate a message that includes // line folding (i.e., that has any field-value that contains a match to // the obs-fold rule) unless the message is intended for packaging // within the message/http media type. // // A server that receives an obs-fold in a request message that is not // within a message/http container MUST either reject the message by // sending a 400 (Bad Request), preferably with a representation // explaining that obsolete line folding is unacceptable, or replace // each received obs-fold with one or more SP octets prior to // interpreting the field value or forwarding the message downstream. RejectRequest("Header value line folding not supported."); } // Trim trailing whitespace from header value by repeatedly advancing to next // whitespace or CR. // // - If CR is found, this is the end of the header value. // - If whitespace is found, this is the _tentative_ end of the header value. // If non-whitespace is found after it and it's not CR, seek again to the next // whitespace or CR for a new (possibly tentative) end of value. var ws = beginValue; var endValue = scan; do { ws.Seek(ref _vectorSpaces, ref _vectorTabs, ref _vectorCRs); endValue = ws; ch = ws.Take(); while (ch == ' ' || ch == '\t') { ch = ws.Take(); } } while (ch != '\r'); var name = beginName.GetArraySegment(endName); var value = beginValue.GetAsciiString(endValue); consumed = scan; requestHeaders.Append(name.Array, name.Offset, name.Count, value); } return(false); } finally { input.ConsumingComplete(consumed, scan); } }