| File: RangeItemHeaderValue.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.Contracts; using System.Globalization; using Microsoft.Extensions.Primitives; namespace Microsoft.Net.Http.Headers; /// <summary> /// Represents a byte range in a Range header value. /// <para> /// The <see cref="RangeItemHeaderValue"/> class provides support for a byte range in a <c>Range</c> as defined /// in <see href="https://tools.ietf.org/html/rfc2616">RFC 2616</see>. /// </para> /// </summary> public class RangeItemHeaderValue { private readonly long? _from; private readonly long? _to; /// <summary> /// Initializes a new instance of the <see cref="RangeItemHeaderValue"/> class. /// </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 RangeItemHeaderValue(long? from, long? to) { if (!from.HasValue && !to.HasValue) { throw new ArgumentException("Invalid header range."); } if (from.HasValue && (from.GetValueOrDefault() < 0)) { throw new ArgumentOutOfRangeException(nameof(from)); } if (to.HasValue && (to.GetValueOrDefault() < 0)) { throw new ArgumentOutOfRangeException(nameof(to)); } if (from.HasValue && to.HasValue && (from.GetValueOrDefault() > to.GetValueOrDefault())) { throw new ArgumentOutOfRangeException(nameof(from)); } _from = from; _to = to; } /// <summary> /// Gets the position at which to start sending data. /// </summary> public long? From { get { return _from; } } /// <summary> /// Gets the position at which to stop sending data. /// </summary> public long? To { get { return _to; } } /// <inheritdoc /> public override string ToString() { if (!_from.HasValue) { return "-" + _to.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo); } else if (!_to.HasValue) { return _from.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo) + "-"; } return _from.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo) + "-" + _to.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo); } /// <inheritdoc /> public override bool Equals(object? obj) { return obj is RangeItemHeaderValue other && ((_from == other._from) && (_to == other._to)); } /// <inheritdoc /> public override int GetHashCode() { if (!_from.HasValue) { return _to.GetValueOrDefault().GetHashCode(); } else if (!_to.HasValue) { return _from.GetValueOrDefault().GetHashCode(); } return _from.GetValueOrDefault().GetHashCode() ^ _to.GetValueOrDefault().GetHashCode(); } // Returns the length of a range list. E.g. "1-2, 3-4, 5-6" adds 3 ranges to 'rangeCollection'. Note that empty // list segments are allowed, e.g. ",1-2, , 3-4,,". internal static int GetRangeItemListLength( StringSegment input, int startIndex, ICollection<RangeItemHeaderValue> rangeCollection) { Contract.Requires(startIndex >= 0); Contract.Ensures((Contract.Result<int>() == 0) || (rangeCollection.Count > 0), "If we can parse the string, then we expect to have at least one range item."); if ((StringSegment.IsNullOrEmpty(input)) || (startIndex >= input.Length)) { return 0; } // Empty segments are allowed, so skip all delimiter-only segments (e.g. ", ,"). var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(input, startIndex, true, separatorFound: out _); // It's OK if we didn't find leading separator characters. Ignore 'separatorFound'. if (current == input.Length) { return 0; } while (true) { var rangeLength = GetRangeItemLength(input, current, out var range); if (rangeLength == 0) { return 0; } rangeCollection!.Add(range!); current = current + rangeLength; current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(input, current, true, out var separatorFound); // If the string is not consumed, we must have a delimiter, otherwise the string is not a valid // range list. if ((current < input.Length) && !separatorFound) { return 0; } if (current == input.Length) { return current - startIndex; } } } internal static int GetRangeItemLength(StringSegment input, int startIndex, out RangeItemHeaderValue? parsedValue) { Contract.Requires(startIndex >= 0); // This parser parses number ranges: e.g. '1-2', '1-', '-2'. parsedValue = null; if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length)) { return 0; } // Caller must remove leading whitespaces. If not, we'll return 0. var current = startIndex; // Try parse the first value of a value pair. var fromStartIndex = current; var fromLength = HttpRuleParser.GetNumberLength(input, current, false); if (fromLength > HttpRuleParser.MaxInt64Digits) { return 0; } current = current + fromLength; current = current + HttpRuleParser.GetWhitespaceLength(input, current); // After the first value, the '-' character must follow. if ((current == input.Length) || (input[current] != '-')) { // We need a '-' character otherwise this can't be a valid range. return 0; } current++; // skip the '-' character current = current + HttpRuleParser.GetWhitespaceLength(input, current); var toStartIndex = current; var toLength = 0; // If we didn't reach the end of the string, try parse the second value of the range. if (current < input.Length) { toLength = HttpRuleParser.GetNumberLength(input, current, false); if (toLength > HttpRuleParser.MaxInt64Digits) { return 0; } current = current + toLength; current = current + HttpRuleParser.GetWhitespaceLength(input, current); } if ((fromLength == 0) && (toLength == 0)) { return 0; // At least one value must be provided in order to be a valid range. } // Try convert first value to int64 long from = 0; if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(fromStartIndex, fromLength), out from)) { return 0; } // Try convert second value to int64 long to = 0; if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(toStartIndex, toLength), out to)) { return 0; } // 'from' must not be greater than 'to' if ((fromLength > 0) && (toLength > 0) && (from > to)) { return 0; } parsedValue = new RangeItemHeaderValue((fromLength == 0 ? (long?)null : (long?)from), (toLength == 0 ? (long?)null : (long?)to)); return current - startIndex; } }