// Fast parsing for single span in ReadOnlySequence private void ParseFormValuesFast(ReadOnlySpan <byte> span, ref KeyValueAccumulator accumulator, bool isFinalBlock, out int consumed) { ReadOnlySpan <byte> key; ReadOnlySpan <byte> value; consumed = 0; var equalsDelimiter = GetEqualsForEncoding(); var andDelimiter = GetAndForEncoding(); while (span.Length > 0) { // Find the end of the key=value pair. var ampersand = span.IndexOf(andDelimiter); ReadOnlySpan <byte> keyValuePair; int equals; var foundAmpersand = ampersand != -1; if (foundAmpersand) { keyValuePair = span.Slice(0, ampersand); span = span.Slice(keyValuePair.Length + andDelimiter.Length); consumed += keyValuePair.Length + andDelimiter.Length; } else { // We can't know that what is currently read is the end of the form value, that's only the case if this is the final block // If we're not in the final block, then consume nothing if (!isFinalBlock) { // Don't buffer indefinately if (span.Length > KeyLengthLimit + ValueLengthLimit) { ThrowKeyOrValueTooLargeException(); } return; } keyValuePair = span; span = default; consumed += keyValuePair.Length; } equals = keyValuePair.IndexOf(equalsDelimiter); if (equals == -1) { // Too long for the whole segment to be a key. if (keyValuePair.Length > KeyLengthLimit) { ThrowKeyTooLargeException(); } // There is no more data, this segment must be "key" with no equals or value. key = keyValuePair; value = default; } else { key = keyValuePair.Slice(0, equals); if (key.Length > KeyLengthLimit) { ThrowKeyTooLargeException(); } value = keyValuePair.Slice(equals + equalsDelimiter.Length); if (value.Length > ValueLengthLimit) { ThrowValueTooLargeException(); } } var decodedKey = GetDecodedString(key); var decodedValue = GetDecodedString(value); AppendAndVerify(ref accumulator, decodedKey, decodedValue); } }
// For multi-segment parsing of a read only sequence private void ParseValuesSlow( ref ReadOnlySequence <byte> buffer, ref KeyValueAccumulator accumulator, bool isFinalBlock) { var sequenceReader = new SequenceReader <byte>(buffer); ReadOnlySequence <byte> keyValuePair; var consumed = sequenceReader.Position; var consumedBytes = default(long); var equalsDelimiter = GetEqualsForEncoding(); var andDelimiter = GetAndForEncoding(); while (!sequenceReader.End) { if (!sequenceReader.TryReadTo(out keyValuePair, andDelimiter)) { if (!isFinalBlock) { // Don't buffer indefinately if ((sequenceReader.Consumed - consumedBytes) > KeyLengthLimit + ValueLengthLimit) { ThrowKeyOrValueTooLargeException(); } break; } // This must be the final key=value pair keyValuePair = buffer.Slice(sequenceReader.Position); sequenceReader.Advance(keyValuePair.Length); } if (keyValuePair.IsSingleSegment) { ParseFormValuesFast(keyValuePair.FirstSpan, ref accumulator, isFinalBlock: true, out var segmentConsumed); Debug.Assert(segmentConsumed == keyValuePair.FirstSpan.Length); continue; } var keyValueReader = new SequenceReader <byte>(keyValuePair); ReadOnlySequence <byte> value; if (keyValueReader.TryReadTo(out var key, equalsDelimiter)) { if (key.Length > KeyLengthLimit) { ThrowKeyTooLargeException(); } value = keyValuePair.Slice(keyValueReader.Position); if (value.Length > ValueLengthLimit) { ThrowValueTooLargeException(); } } else { // Too long for the whole segment to be a key. if (keyValuePair.Length > KeyLengthLimit) { ThrowKeyTooLargeException(); } // There is no more data, this segment must be "key" with no equals or value. key = keyValuePair; value = default; } var decodedKey = GetDecodedStringFromReadOnlySequence(key); var decodedValue = GetDecodedStringFromReadOnlySequence(value); AppendAndVerify(ref accumulator, decodedKey, decodedValue); consumedBytes = sequenceReader.Consumed; consumed = sequenceReader.Position; } buffer = buffer.Slice(consumed); }
// Fast parsing for single span in ReadOnlySequence private void ParseFormValuesFast(ReadOnlySpan <byte> span, ref KeyValueAccumulator accumulator, bool isFinalBlock, out int consumed) { ReadOnlySpan <byte> key = default; ReadOnlySpan <byte> value = default; consumed = 0; var equalsDelimiter = GetEqualsForEncoding(); var andDelimiter = GetAndForEncoding(); while (span.Length > 0) { var equals = span.IndexOf(equalsDelimiter); if (equals == -1) { if (span.Length > KeyLengthLimit) { ThrowKeyTooLargeException(); } break; } if (equals > KeyLengthLimit) { ThrowKeyTooLargeException(); } key = span.Slice(0, equals); span = span.Slice(key.Length + equalsDelimiter.Length); value = span; var ampersand = span.IndexOf(andDelimiter); if (ampersand == -1) { if (span.Length > ValueLengthLimit) { ThrowValueTooLargeException(); return; } if (!isFinalBlock) { // We can't know that what is currently read is the end of the form value, that's only the case if this is the final block // If we're not in the final block, then consume nothing break; } // If we are on the final block, the remaining content in value is what we want to add to the KVAccumulator. // Clear out the remaining span such that the loop will exit. span = Span <byte> .Empty; } else { if (ampersand > ValueLengthLimit) { ThrowValueTooLargeException(); } value = span.Slice(0, ampersand); span = span.Slice(ampersand + andDelimiter.Length); } var decodedKey = GetDecodedString(key); var decodedValue = GetDecodedString(value); AppendAndVerify(ref accumulator, decodedKey, decodedValue); // Cover case where we don't have an ampersand at the end. consumed += key.Length + value.Length + (ampersand == -1 ? equalsDelimiter.Length : equalsDelimiter.Length + andDelimiter.Length); } }
// For multi-segment parsing of a read only sequence private void ParseValuesSlow( ref ReadOnlySequence <byte> buffer, ref KeyValueAccumulator accumulator, bool isFinalBlock) { var sequenceReader = new SequenceReader <byte>(buffer); var consumed = sequenceReader.Position; var equalsDelimiter = GetEqualsForEncoding(); var andDelimiter = GetAndForEncoding(); while (!sequenceReader.End) { // TODO seems there is a bug with TryReadTo (advancePastDelimiter: true). It isn't advancing past the delimiter on second read. if (!sequenceReader.TryReadTo(out ReadOnlySequence <byte> key, equalsDelimiter, advancePastDelimiter: false) || !sequenceReader.IsNext(equalsDelimiter, true)) { if (sequenceReader.Consumed > KeyLengthLimit) { ThrowKeyTooLargeException(); } break; } if (key.Length > KeyLengthLimit) { ThrowKeyTooLargeException(); } if (!sequenceReader.TryReadTo(out ReadOnlySequence <byte> value, andDelimiter, false) || !sequenceReader.IsNext(andDelimiter, true)) { if (!isFinalBlock) { if (sequenceReader.Consumed - key.Length > ValueLengthLimit) { ThrowValueTooLargeException(); } break; } value = buffer.Slice(sequenceReader.Position); sequenceReader.Advance(value.Length); } if (value.Length > ValueLengthLimit) { ThrowValueTooLargeException(); } // Need to call ToArray if the key/value spans multiple segments var decodedKey = GetDecodedStringFromReadOnlySequence(key); var decodedValue = GetDecodedStringFromReadOnlySequence(value); AppendAndVerify(ref accumulator, decodedKey, decodedValue); consumed = sequenceReader.Position; } buffer = buffer.Slice(consumed); }
/// <summary> /// Parse a query string into its component key and value parts. /// </summary> /// <param name="queryString">The raw query string value, with or without the leading '?'.</param> /// <returns>A collection of parsed keys and values, null if there are no entries.</returns> public static Dictionary <string, StringValues> ParseNullableQuery(string queryString) { var accumulator = new KeyValueAccumulator(); if (string.IsNullOrEmpty(queryString) || queryString == "?") { return(null); } int scanIndex = 0; if (queryString[0] == '?') { scanIndex = 1; } int textLength = queryString.Length; int equalIndex = queryString.IndexOf('='); if (equalIndex == -1) { equalIndex = textLength; } while (scanIndex < textLength) { int delimiterIndex = queryString.IndexOf('&', scanIndex); if (delimiterIndex == -1) { delimiterIndex = textLength; } if (equalIndex < delimiterIndex) { while (scanIndex != equalIndex && char.IsWhiteSpace(queryString[scanIndex])) { ++scanIndex; } string name = queryString.Substring(scanIndex, equalIndex - scanIndex); string value = queryString.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1); accumulator.Append( Uri.UnescapeDataString(name.Replace('+', ' ')), Uri.UnescapeDataString(value.Replace('+', ' '))); equalIndex = queryString.IndexOf('=', delimiterIndex); if (equalIndex == -1) { equalIndex = textLength; } } else { if (delimiterIndex > scanIndex) { accumulator.Append(queryString.Substring(scanIndex, delimiterIndex - scanIndex), string.Empty); } } scanIndex = delimiterIndex + 1; } if (!accumulator.HasValues) { return(null); } return(accumulator.GetResults()); }
private void Append(ref KeyValueAccumulator accumulator) { if (ReadSucceded()) { accumulator.Append(_currentKey, _currentValue); if (accumulator.ValueCount > ValueCountLimit) { throw new InvalidDataException($"Form value count limit {ValueCountLimit} exceeded."); } } }
/// <summary> /// Parses an HTTP form body. /// </summary> /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param> /// <returns>The collection containing the parsed HTTP form body.</returns> public async Task<Dictionary<string, StringValues>> ReadFormAsync(CancellationToken cancellationToken = new CancellationToken()) { var accumulator = new KeyValueAccumulator(); while (!_endOfStream) { await ReadNextPairAsyncImpl(cancellationToken); Append(ref accumulator); } return accumulator.GetResults(); }
/// <summary> /// Parses text from an HTTP form body. /// </summary> /// <returns>The collection containing the parsed HTTP form body.</returns> public Dictionary<string, StringValues> ReadForm() { var accumulator = new KeyValueAccumulator(); while (!_endOfStream) { ReadNextPairImpl(); Append(ref accumulator); } return accumulator.GetResults(); }
private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancellationToken) { if (!HasFormContentType) { throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType); } cancellationToken.ThrowIfCancellationRequested(); if (_options.BufferBody) { _request.EnableRewind(_options.MemoryBufferThreshold, _options.BufferBodyLengthLimit); } FormCollection formFields = null; FormFileCollection files = null; // Some of these code paths use StreamReader which does not support cancellation tokens. using (cancellationToken.Register((state) => ((HttpContext)state).Abort(), _request.HttpContext)) { var contentType = ContentType; // Check the content-type if (HasApplicationFormContentType(contentType)) { var encoding = FilterEncoding(contentType.Encoding); using (var formReader = new FormReader(_request.Body, encoding) { ValueCountLimit = _options.ValueCountLimit, KeyLengthLimit = _options.KeyLengthLimit, ValueLengthLimit = _options.ValueLengthLimit, }) { formFields = new FormCollection(await formReader.ReadFormAsync(cancellationToken)); } } else if (HasMultipartFormContentType(contentType)) { var formAccumulator = new KeyValueAccumulator(); var boundary = GetBoundary(contentType, _options.MultipartBoundaryLengthLimit); var multipartReader = new MultipartReader(boundary, _request.Body) { HeadersCountLimit = _options.MultipartHeadersCountLimit, HeadersLengthLimit = _options.MultipartHeadersLengthLimit, BodyLengthLimit = _options.MultipartBodyLengthLimit, }; var section = await multipartReader.ReadNextSectionAsync(cancellationToken); while (section != null) { ContentDispositionHeaderValue contentDisposition; ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition); if (HasFileContentDisposition(contentDisposition)) { // Enable buffering for the file if not already done for the full body section.EnableRewind(_request.HttpContext.Response.RegisterForDispose, _options.MemoryBufferThreshold, _options.MultipartBodyLengthLimit); // Find the end await section.Body.DrainAsync(cancellationToken); var name = HeaderUtilities.RemoveQuotes(contentDisposition.Name) ?? string.Empty; var fileName = HeaderUtilities.RemoveQuotes(contentDisposition.FileName) ?? string.Empty; FormFile file; if (section.BaseStreamOffset.HasValue) { // Relative reference to buffered request body file = new FormFile(_request.Body, section.BaseStreamOffset.Value, section.Body.Length, name, fileName); } else { // Individually buffered file body file = new FormFile(section.Body, 0, section.Body.Length, name, fileName); } file.Headers = new HeaderDictionary(section.Headers); if (files == null) { files = new FormFileCollection(); } if (files.Count >= _options.ValueCountLimit) { throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded."); } files.Add(file); } else if (HasFormDataContentDisposition(contentDisposition)) { // Content-Disposition: form-data; name="key" // // value // Do not limit the key name length here because the mulipart headers length limit is already in effect. var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name); MediaTypeHeaderValue mediaType; MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType); var encoding = FilterEncoding(mediaType?.Encoding); using (var reader = new StreamReader(section.Body, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true)) { // The value length limit is enforced by MultipartBodyLengthLimit var value = await reader.ReadToEndAsync(); formAccumulator.Append(key, value); if (formAccumulator.ValueCount > _options.ValueCountLimit) { throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded."); } } } else { System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition); } section = await multipartReader.ReadNextSectionAsync(cancellationToken); } if (formAccumulator.HasValues) { formFields = new FormCollection(formAccumulator.GetResults(), files); } } } // Rewind so later readers don't have to. if (_request.Body.CanSeek) { _request.Body.Seek(0, SeekOrigin.Begin); } if (formFields != null) { Form = formFields; } else if (files != null) { Form = new FormCollection(null, files); } else { Form = FormCollection.Empty; } return Form; }