aspnetcore/src/Microsoft.Net.Http.Headers/RangeItemHeaderValue.cs

229 lines
7.4 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
namespace Microsoft.Net.Http.Headers
{
public class RangeItemHeaderValue
{
private long? _from;
private long? _to;
public RangeItemHeaderValue(long? from, long? to)
{
if (!from.HasValue && !to.HasValue)
{
throw new ArgumentException("Invalid header range.");
}
if (from.HasValue && (from.Value < 0))
{
throw new ArgumentOutOfRangeException(nameof(from));
}
if (to.HasValue && (to.Value < 0))
{
throw new ArgumentOutOfRangeException(nameof(to));
}
if (from.HasValue && to.HasValue && (from.Value > to.Value))
{
throw new ArgumentOutOfRangeException(nameof(from));
}
_from = from;
_to = to;
}
public long? From
{
get { return _from; }
}
public long? To
{
get { return _to; }
}
public override string ToString()
{
if (!_from.HasValue)
{
return "-" + _to.Value.ToString(NumberFormatInfo.InvariantInfo);
}
else if (!_to.HasValue)
{
return _from.Value.ToString(NumberFormatInfo.InvariantInfo) + "-";
}
return _from.Value.ToString(NumberFormatInfo.InvariantInfo) + "-" +
_to.Value.ToString(NumberFormatInfo.InvariantInfo);
}
public override bool Equals(object obj)
{
var other = obj as RangeItemHeaderValue;
if (other == null)
{
return false;
}
return ((_from == other._from) && (_to == other._to));
}
public override int GetHashCode()
{
if (!_from.HasValue)
{
return _to.GetHashCode();
}
else if (!_to.HasValue)
{
return _from.GetHashCode();
}
return _from.GetHashCode() ^ _to.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(
string input,
int startIndex,
ICollection<RangeItemHeaderValue> rangeCollection)
{
Contract.Requires(rangeCollection != null);
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 ((string.IsNullOrEmpty(input)) || (startIndex >= input.Length))
{
return 0;
}
// Empty segments are allowed, so skip all delimiter-only segments (e.g. ", ,").
var separatorFound = false;
var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(input, startIndex, true, out separatorFound);
// It's OK if we didn't find leading separator characters. Ignore 'separatorFound'.
if (current == input.Length)
{
return 0;
}
RangeItemHeaderValue range = null;
while (true)
{
var rangeLength = GetRangeItemLength(input, current, out range);
if (rangeLength == 0)
{
return 0;
}
rangeCollection.Add(range);
current = current + rangeLength;
current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(input, current, true, out 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(string input, int startIndex, out RangeItemHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
// This parser parses number ranges: e.g. '1-2', '1-', '-2'.
parsedValue = null;
if (string.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);
// Afer 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.TryParseInt64(input.Substring(fromStartIndex, fromLength), out from))
{
return 0;
}
// Try convert second value to int64
long to = 0;
if ((toLength > 0) && !HeaderUtilities.TryParseInt64(input.Substring(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;
}
}
}