| File: RangeHeaderValue.cs | Web Access |
| Project: src\aspnetcore\src\Http\Headers\src\Microsoft.Net.Http.Headers.csproj (Microsoft.Net.Http.Headers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Text; using Microsoft.Extensions.Primitives; namespace Microsoft.Net.Http.Headers; /// <summary> /// Represents a <c>Range</c> header value. /// <para> /// The <see cref="RangeHeaderValue"/> class provides support for the Range header as defined in /// <see href="https://tools.ietf.org/html/rfc2616">RFC 2616</see>. /// </para> /// </summary> public class RangeHeaderValue { private static readonly HttpHeaderParser<RangeHeaderValue> Parser = new GenericHeaderParser<RangeHeaderValue>(false, GetRangeLength); private StringSegment _unit; private ICollection<RangeItemHeaderValue>? _ranges; /// <summary> /// Initializes a new instance of <see cref="RangeHeaderValue"/>. /// </summary> public RangeHeaderValue() { _unit = HeaderUtilities.BytesUnit; } /// <summary> /// Initializes a new instance of <see cref="RangeHeaderValue"/>. /// </summary> /// <param name="from">The position at which to start sending data.</param> /// <param name="to">The position at which to stop sending data.</param> public RangeHeaderValue(long? from, long? to) { // convenience ctor: "Range: bytes=from-to" _unit = HeaderUtilities.BytesUnit; Ranges.Add(new RangeItemHeaderValue(from, to)); } /// <summary> /// Gets or sets the unit from the header. /// </summary> /// <value>Defaults to <c>bytes</c>.</value> public StringSegment Unit { get { return _unit; } set { HeaderUtilities.CheckValidToken(value, nameof(value)); _unit = value; } } /// <summary> /// Gets the ranges specified in the header. /// </summary> public ICollection<RangeItemHeaderValue> Ranges { get { if (_ranges == null) { _ranges = new ObjectCollection<RangeItemHeaderValue>(); } return _ranges; } } /// <inheritdoc /> public override string ToString() { var sb = new StringBuilder(); sb.Append(_unit.AsSpan()); sb.Append('='); var first = true; foreach (var range in Ranges) { if (first) { first = false; } else { sb.Append(", "); } sb.Append(range.From); sb.Append('-'); sb.Append(range.To); } return sb.ToString(); } /// <inheritdoc /> public override bool Equals(object? obj) { var other = obj as RangeHeaderValue; if (other == null) { return false; } return StringSegment.Equals(_unit, other._unit, StringComparison.OrdinalIgnoreCase) && HeaderUtilities.AreEqualCollections(Ranges, other.Ranges); } /// <inheritdoc /> public override int GetHashCode() { var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_unit); foreach (var range in Ranges) { result = result ^ range.GetHashCode(); } return result; } /// <summary> /// Parses <paramref name="input"/> as a <see cref="RangeHeaderValue"/> value. /// </summary> /// <param name="input">The values to parse.</param> /// <returns>The parsed values.</returns> public static RangeHeaderValue Parse(StringSegment input) { var index = 0; return Parser.ParseValue(input, ref index)!; } /// <summary> /// Attempts to parse the specified <paramref name="input"/> as a <see cref="RangeHeaderValue"/>. /// </summary> /// <param name="input">The value to parse.</param> /// <param name="parsedValue">The parsed value.</param> /// <returns><see langword="true"/> if input is a valid <see cref="RangeHeaderValue"/>, otherwise <see langword="false"/>.</returns> public static bool TryParse(StringSegment input, [NotNullWhen(true)] out RangeHeaderValue? parsedValue) { var index = 0; return Parser.TryParseValue(input, index, out _, out parsedValue); } private static int GetRangeLength(StringSegment input, int startIndex, out RangeHeaderValue? parsedValue) { Contract.Requires(startIndex >= 0); parsedValue = null; if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length)) { return 0; } // Parse the unit string: <unit> in '<unit>=<from1>-<to1>, <from2>-<to2>' var unitLength = HttpRuleParser.GetTokenLength(input, startIndex); if (unitLength == 0) { return 0; } RangeHeaderValue result = new RangeHeaderValue(); result._unit = input.Subsegment(startIndex, unitLength); var current = startIndex + unitLength; current = current + HttpRuleParser.GetWhitespaceLength(input, current); if ((current == input.Length) || (input[current] != '=')) { return 0; } current++; // skip '=' separator current = current + HttpRuleParser.GetWhitespaceLength(input, current); var rangesLength = RangeItemHeaderValue.GetRangeItemListLength(input, current, result.Ranges); if (rangesLength == 0) { return 0; } current = current + rangesLength; Contract.Assert(current == input.Length, "GetRangeItemListLength() should consume the whole string or fail."); parsedValue = result; return current - startIndex; } }