#758 Convert the header parsers to use StringSegment

This commit is contained in:
Chris R 2017-05-18 20:33:30 -07:00
parent e8123db21e
commit 2ce2d8b6c5
29 changed files with 407 additions and 420 deletions

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Http.Extensions
{
return string.Empty;
}
return HeaderUtilities.RemoveQuotes(mediaType.Boundary);
return HeaderUtilities.RemoveQuotes(mediaType.Boundary).ToString();
}
}
}

View File

@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Http.Features
@ -293,14 +294,14 @@ namespace Microsoft.AspNetCore.Http.Features
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null && contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName) && string.IsNullOrEmpty(contentDisposition.FileNameStar);
&& StringSegment.IsNullOrEmpty(contentDisposition.FileName) && StringSegment.IsNullOrEmpty(contentDisposition.FileNameStar);
}
private bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null && contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName) || !string.IsNullOrEmpty(contentDisposition.FileNameStar));
&& (!StringSegment.IsNullOrEmpty(contentDisposition.FileName) || !StringSegment.IsNullOrEmpty(contentDisposition.FileNameStar));
}
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
@ -308,7 +309,7 @@ namespace Microsoft.AspNetCore.Http.Features
private static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary);
if (string.IsNullOrWhiteSpace(boundary))
if (StringSegment.IsNullOrEmpty(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
@ -316,7 +317,7 @@ namespace Microsoft.AspNetCore.Http.Features
{
throw new InvalidDataException($"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
return boundary.ToString();
}
}
}

View File

@ -75,8 +75,8 @@ namespace Microsoft.AspNetCore.Http.Internal
for (var i = 0; i < cookies.Count; i++)
{
var cookie = cookies[i];
var name = Uri.UnescapeDataString(cookie.Name);
var value = Uri.UnescapeDataString(cookie.Value);
var name = Uri.UnescapeDataString(cookie.Name.Value);
var value = Uri.UnescapeDataString(cookie.Value.Value);
store[name] = value;
}

View File

@ -39,11 +39,11 @@ namespace Microsoft.AspNetCore.WebUtilities
Section = section;
_contentDispositionHeader = header;
Name = HeaderUtilities.RemoveQuotes(_contentDispositionHeader.Name) ?? string.Empty;
Name = HeaderUtilities.RemoveQuotes(_contentDispositionHeader.Name).ToString();
FileName = HeaderUtilities.RemoveQuotes(
_contentDispositionHeader.FileNameStar ??
_contentDispositionHeader.FileName ??
string.Empty);
_contentDispositionHeader.FileNameStar.HasValue ?
_contentDispositionHeader.FileNameStar :
_contentDispositionHeader.FileName).ToString();
}
/// <summary>

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.WebUtilities
Section = section;
_contentDispositionHeader = header;
Name = HeaderUtilities.RemoveQuotes(_contentDispositionHeader.Name);
Name = HeaderUtilities.RemoveQuotes(_contentDispositionHeader.Name).ToString();
}
/// <summary>

View File

@ -1,6 +1,8 @@
// 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 Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
internal abstract class BaseHeaderParser<T> : HttpHeaderParser<T>
@ -10,9 +12,9 @@ namespace Microsoft.Net.Http.Headers
{
}
protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue);
protected abstract int GetParsedValueLength(StringSegment value, int startIndex, out T parsedValue);
public sealed override bool TryParseValue(string value, ref int index, out T parsedValue)
public sealed override bool TryParseValue(StringSegment value, ref int index, out T parsedValue)
{
parsedValue = default(T);
@ -21,7 +23,7 @@ namespace Microsoft.Net.Http.Headers
// Accept: text/xml; q=1
// Accept:
// Accept: text/plain; q=0.2
if (string.IsNullOrEmpty(value) || (index == value.Length))
if (StringSegment.IsNullOrEmpty(value) || (index == value.Length))
{
return SupportsMultipleValues;
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -32,10 +33,10 @@ namespace Microsoft.Net.Http.Headers
private static readonly HttpHeaderParser<CacheControlHeaderValue> Parser
= new GenericHeaderParser<CacheControlHeaderValue>(true, GetCacheControlLength);
private static readonly Action<string> CheckIsValidTokenAction = CheckIsValidToken;
private static readonly Action<StringSegment> CheckIsValidTokenAction = CheckIsValidToken;
private bool _noCache;
private ICollection<string> _noCacheHeaders;
private ICollection<StringSegment> _noCacheHeaders;
private bool _noStore;
private TimeSpan? _maxAge;
private TimeSpan? _sharedMaxAge;
@ -46,7 +47,7 @@ namespace Microsoft.Net.Http.Headers
private bool _onlyIfCached;
private bool _public;
private bool _private;
private ICollection<string> _privateHeaders;
private ICollection<StringSegment> _privateHeaders;
private bool _mustRevalidate;
private bool _proxyRevalidate;
private IList<NameValueHeaderValue> _extensions;
@ -62,13 +63,13 @@ namespace Microsoft.Net.Http.Headers
set { _noCache = value; }
}
public ICollection<string> NoCacheHeaders
public ICollection<StringSegment> NoCacheHeaders
{
get
{
if (_noCacheHeaders == null)
{
_noCacheHeaders = new ObjectCollection<string>(CheckIsValidTokenAction);
_noCacheHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
}
return _noCacheHeaders;
}
@ -134,13 +135,13 @@ namespace Microsoft.Net.Http.Headers
set { _private = value; }
}
public ICollection<string> PrivateHeaders
public ICollection<StringSegment> PrivateHeaders
{
get
{
if (_privateHeaders == null)
{
_privateHeaders = new ObjectCollection<string>(CheckIsValidTokenAction);
_privateHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
}
return _privateHeaders;
}
@ -259,13 +260,13 @@ namespace Microsoft.Net.Http.Headers
}
if (!HeaderUtilities.AreEqualCollections(_noCacheHeaders, other._noCacheHeaders,
StringComparer.OrdinalIgnoreCase))
StringSegmentComparer.OrdinalIgnoreCase))
{
return false;
}
if (!HeaderUtilities.AreEqualCollections(_privateHeaders, other._privateHeaders,
StringComparer.OrdinalIgnoreCase))
StringSegmentComparer.OrdinalIgnoreCase))
{
return false;
}
@ -299,7 +300,7 @@ namespace Microsoft.Net.Http.Headers
{
foreach (var noCacheHeader in _noCacheHeaders)
{
result = result ^ StringComparer.OrdinalIgnoreCase.GetHashCode(noCacheHeader);
result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(noCacheHeader);
}
}
@ -307,7 +308,7 @@ namespace Microsoft.Net.Http.Headers
{
foreach (var privateHeader in _privateHeaders)
{
result = result ^ StringComparer.OrdinalIgnoreCase.GetHashCode(privateHeader);
result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(privateHeader);
}
}
@ -322,7 +323,7 @@ namespace Microsoft.Net.Http.Headers
return result;
}
public static CacheControlHeaderValue Parse(string input)
public static CacheControlHeaderValue Parse(StringSegment input)
{
int index = 0;
// Cache-Control is unusual because there are no required values so the parser will succeed for an empty string, but still return null.
@ -334,7 +335,7 @@ namespace Microsoft.Net.Http.Headers
return result;
}
public static bool TryParse(string input, out CacheControlHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out CacheControlHeaderValue parsedValue)
{
int index = 0;
// Cache-Control is unusual because there are no required values so the parser will succeed for an empty string, but still return null.
@ -346,13 +347,13 @@ namespace Microsoft.Net.Http.Headers
return false;
}
private static int GetCacheControlLength(string input, int startIndex, out CacheControlHeaderValue parsedValue)
private static int GetCacheControlLength(StringSegment input, int startIndex, out CacheControlHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
@ -403,7 +404,7 @@ namespace Microsoft.Net.Http.Headers
switch (name.Length)
{
case 6:
if (string.Equals(PublicString, name, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(PublicString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._public);
}
@ -414,11 +415,11 @@ namespace Microsoft.Net.Http.Headers
break;
case 7:
if (string.Equals(MaxAgeString, name, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(MaxAgeString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTimeSpan(nameValue, ref cc._maxAge);
}
else if(string.Equals(PrivateString, name, StringComparison.OrdinalIgnoreCase))
else if(StringSegment.Equals(PrivateString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetOptionalTokenList(nameValue, ref cc._private, ref cc._privateHeaders);
}
@ -429,15 +430,15 @@ namespace Microsoft.Net.Http.Headers
break;
case 8:
if (string.Equals(NoCacheString, name, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(NoCacheString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetOptionalTokenList(nameValue, ref cc._noCache, ref cc._noCacheHeaders);
}
else if (string.Equals(NoStoreString, name, StringComparison.OrdinalIgnoreCase))
else if (StringSegment.Equals(NoStoreString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._noStore);
}
else if (string.Equals(SharedMaxAgeString, name, StringComparison.OrdinalIgnoreCase))
else if (StringSegment.Equals(SharedMaxAgeString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTimeSpan(nameValue, ref cc._sharedMaxAge);
}
@ -448,7 +449,7 @@ namespace Microsoft.Net.Http.Headers
break;
case 9:
if (string.Equals(MaxStaleString, name, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(MaxStaleString, name, StringComparison.OrdinalIgnoreCase))
{
success = ((nameValue.Value == null) || TrySetTimeSpan(nameValue, ref cc._maxStaleLimit));
if (success)
@ -456,7 +457,7 @@ namespace Microsoft.Net.Http.Headers
cc._maxStale = true;
}
}
else if (string.Equals(MinFreshString, name, StringComparison.OrdinalIgnoreCase))
else if (StringSegment.Equals(MinFreshString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTimeSpan(nameValue, ref cc._minFresh);
}
@ -467,7 +468,7 @@ namespace Microsoft.Net.Http.Headers
break;
case 12:
if (string.Equals(NoTransformString, name, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(NoTransformString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._noTransform);
}
@ -478,7 +479,7 @@ namespace Microsoft.Net.Http.Headers
break;
case 14:
if (string.Equals(OnlyIfCachedString, name, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(OnlyIfCachedString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._onlyIfCached);
}
@ -489,7 +490,7 @@ namespace Microsoft.Net.Http.Headers
break;
case 15:
if (string.Equals(MustRevalidateString, name, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(MustRevalidateString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._mustRevalidate);
}
@ -500,7 +501,7 @@ namespace Microsoft.Net.Http.Headers
break;
case 16:
if (string.Equals(ProxyRevalidateString, name, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(ProxyRevalidateString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._proxyRevalidate);
}
@ -538,7 +539,7 @@ namespace Microsoft.Net.Http.Headers
private static bool TrySetOptionalTokenList(
NameValueHeaderValue nameValue,
ref bool boolField,
ref ICollection<string> destination)
ref ICollection<StringSegment> destination)
{
Contract.Requires(nameValue != null);
@ -582,10 +583,10 @@ namespace Microsoft.Net.Http.Headers
if (destination == null)
{
destination = new ObjectCollection<string>(CheckIsValidTokenAction);
destination = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
}
destination.Add(valueString.Substring(current, tokenLength));
destination.Add(valueString.Subsegment(current, tokenLength));
current = current + tokenLength;
}
@ -637,10 +638,10 @@ namespace Microsoft.Net.Http.Headers
sb.Append(value);
}
private static void AppendValues(StringBuilder sb, IEnumerable<string> values)
private static void AppendValues(StringBuilder sb, IEnumerable<StringSegment> values)
{
var first = true;
foreach (string value in values)
foreach (var value in values)
{
if (first)
{
@ -655,7 +656,7 @@ namespace Microsoft.Net.Http.Headers
}
}
private static void CheckIsValidToken(string item)
private static void CheckIsValidToken(StringSegment item)
{
HeaderUtilities.CheckValidToken(item, nameof(item));
}

View File

@ -6,7 +6,9 @@ using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -20,26 +22,28 @@ namespace Microsoft.Net.Http.Headers
private const string ModificationDateString = "modification-date";
private const string ReadDateString = "read-date";
private const string SizeString = "size";
private static readonly char[] QuestionMark = new char[] { '?' };
private static readonly char[] SingleQuote = new char[] { '\'' };
private static readonly HttpHeaderParser<ContentDispositionHeaderValue> Parser
= new GenericHeaderParser<ContentDispositionHeaderValue>(false, GetDispositionTypeLength);
// Use list instead of dictionary since we may have multiple parameters with the same name.
private ObjectCollection<NameValueHeaderValue> _parameters;
private string _dispositionType;
private StringSegment _dispositionType;
private ContentDispositionHeaderValue()
{
// Used by the parser to create a new instance of this type.
}
public ContentDispositionHeaderValue(string dispositionType)
public ContentDispositionHeaderValue(StringSegment dispositionType)
{
CheckDispositionTypeFormat(dispositionType, "dispositionType");
_dispositionType = dispositionType;
}
public string DispositionType
public StringSegment DispositionType
{
get { return _dispositionType; }
set
@ -63,19 +67,19 @@ namespace Microsoft.Net.Http.Headers
// Helpers to access specific parameters in the list
public string Name
public StringSegment Name
{
get { return GetName(NameString); }
set { SetName(NameString, value); }
}
public string FileName
public StringSegment FileName
{
get { return GetName(FileNameString); }
set { SetName(FileNameString, value); }
}
public string FileNameStar
public StringSegment FileNameStar
{
get { return GetName(FileNameStarString); }
set { SetName(FileNameStarString, value); }
@ -146,9 +150,9 @@ namespace Microsoft.Net.Http.Headers
/// Sets both FileName and FileNameStar using encodings appropriate for HTTP headers.
/// </summary>
/// <param name="fileName"></param>
public void SetHttpFileName(string fileName)
public void SetHttpFileName(StringSegment fileName)
{
if (!string.IsNullOrEmpty(fileName))
if (!StringSegment.IsNullOrEmpty(fileName))
{
FileName = Sanatize(fileName);
}
@ -164,7 +168,7 @@ namespace Microsoft.Net.Http.Headers
/// The FileNameStar paraemter is removed.
/// </summary>
/// <param name="fileName"></param>
public void SetMimeFileName(string fileName)
public void SetMimeFileName(StringSegment fileName)
{
FileNameStar = null;
FileName = fileName;
@ -184,42 +188,41 @@ namespace Microsoft.Net.Http.Headers
return false;
}
return (string.Compare(_dispositionType, other._dispositionType, StringComparison.OrdinalIgnoreCase) == 0) &&
return _dispositionType.Equals(other._dispositionType, StringComparison.OrdinalIgnoreCase) &&
HeaderUtilities.AreEqualCollections(_parameters, other._parameters);
}
public override int GetHashCode()
{
// The dispositionType string is case-insensitive.
return StringComparer.OrdinalIgnoreCase.GetHashCode(_dispositionType) ^ NameValueHeaderValue.GetHashCode(_parameters);
return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_dispositionType) ^ NameValueHeaderValue.GetHashCode(_parameters);
}
public static ContentDispositionHeaderValue Parse(string input)
public static ContentDispositionHeaderValue Parse(StringSegment input)
{
var index = 0;
return Parser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out ContentDispositionHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out ContentDispositionHeaderValue parsedValue)
{
var index = 0;
return Parser.TryParseValue(input, ref index, out parsedValue);
}
private static int GetDispositionTypeLength(string input, int startIndex, out ContentDispositionHeaderValue parsedValue)
private static int GetDispositionTypeLength(StringSegment input, int startIndex, out ContentDispositionHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
// Caller must remove leading whitespaces. If not, we'll return 0.
string dispositionType = null;
var dispositionTypeLength = GetDispositionTypeExpressionLength(input, startIndex, out dispositionType);
var dispositionTypeLength = GetDispositionTypeExpressionLength(input, startIndex, out var dispositionType);
if (dispositionTypeLength == 0)
{
@ -247,7 +250,7 @@ namespace Microsoft.Net.Http.Headers
return current - startIndex;
}
private static int GetDispositionTypeExpressionLength(string input, int startIndex, out string dispositionType)
private static int GetDispositionTypeExpressionLength(StringSegment input, int startIndex, out StringSegment dispositionType)
{
Contract.Requires((input != null) && (input.Length > 0) && (startIndex < input.Length));
@ -263,20 +266,19 @@ namespace Microsoft.Net.Http.Headers
return 0;
}
dispositionType = input.Substring(startIndex, typeLength);
dispositionType = input.Subsegment(startIndex, typeLength);
return typeLength;
}
private static void CheckDispositionTypeFormat(string dispositionType, string parameterName)
private static void CheckDispositionTypeFormat(StringSegment dispositionType, string parameterName)
{
if (string.IsNullOrEmpty(dispositionType))
if (StringSegment.IsNullOrEmpty(dispositionType))
{
throw new ArgumentException("An empty string is not allowed.", parameterName);
}
// When adding values using strongly typed objects, no leading/trailing LWS (whitespaces) are allowed.
string tempDispositionType;
var dispositionTypeLength = GetDispositionTypeExpressionLength(dispositionType, 0, out tempDispositionType);
var dispositionTypeLength = GetDispositionTypeExpressionLength(dispositionType, 0, out var tempDispositionType);
if ((dispositionTypeLength == 0) || (tempDispositionType.Length != dispositionType.Length))
{
throw new FormatException(string.Format(CultureInfo.InvariantCulture,
@ -291,11 +293,11 @@ namespace Microsoft.Net.Http.Headers
var dateParameter = NameValueHeaderValue.Find(_parameters, parameter);
if (dateParameter != null)
{
string dateString = dateParameter.Value;
var dateString = dateParameter.Value;
// Should have quotes, remove them.
if (IsQuoted(dateString))
{
dateString = dateString.Substring(1, dateString.Length - 2);
dateString = dateString.Subsegment(1, dateString.Length - 2);
}
DateTimeOffset date;
if (HttpRuleParser.TryStringToDate(dateString, out date))
@ -357,17 +359,17 @@ namespace Microsoft.Net.Http.Headers
return result;
}
// May not have been encoded
return nameParameter.Value;
return nameParameter.Value.ToString();
}
return null;
}
// Add/update the given parameter in the list, encoding if necessary.
// Remove if value is null/Empty
private void SetName(string parameter, string value)
private void SetName(StringSegment parameter, StringSegment value)
{
var nameParameter = NameValueHeaderValue.Find(_parameters, parameter);
if (string.IsNullOrEmpty(value))
if (StringSegment.IsNullOrEmpty(value))
{
// Remove parameter
if (nameParameter != null)
@ -377,7 +379,7 @@ namespace Microsoft.Net.Http.Headers
}
else
{
var processedValue = string.Empty;
var processedValue = StringSegment.Empty;
if (parameter.EndsWith("*", StringComparison.Ordinal))
{
processedValue = Encode5987(value);
@ -399,14 +401,14 @@ namespace Microsoft.Net.Http.Headers
}
// Returns input for decoding failures, as the content might not be encoded
private string EncodeAndQuoteMime(string input)
private StringSegment EncodeAndQuoteMime(StringSegment input)
{
var result = input;
var needsQuotes = false;
// Remove bounding quotes, they'll get re-added later
if (IsQuoted(result))
{
result = result.Substring(1, result.Length - 2);
result = result.Subsegment(1, result.Length - 2);
needsQuotes = true;
}
@ -423,8 +425,7 @@ namespace Microsoft.Net.Http.Headers
if (needsQuotes)
{
// '\' and '"' must be escaped in a quoted string
result = result.Replace(@"\", @"\\");
result = result.Replace(@"""", @"\""");
result = result.ToString().Replace(@"\", @"\\").Replace(@"""", @"\""");
// Re-add quotes "value"
result = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", result);
}
@ -432,7 +433,7 @@ namespace Microsoft.Net.Http.Headers
}
// Replaces characters not suitable for HTTP headers with '_' rather than MIME encoding them.
private string Sanatize(string input)
private StringSegment Sanatize(StringSegment input)
{
var result = input;
@ -455,7 +456,7 @@ namespace Microsoft.Net.Http.Headers
}
// Returns true if the value starts and ends with a quote
private bool IsQuoted(string value)
private bool IsQuoted(StringSegment value)
{
Contract.Assert(value != null);
@ -464,13 +465,13 @@ namespace Microsoft.Net.Http.Headers
}
// tspecials are required to be in a quoted string. Only non-ascii needs to be encoded.
private bool RequiresEncoding(string input)
private bool RequiresEncoding(StringSegment input)
{
Contract.Assert(input != null);
foreach (char c in input)
for (int i = 0; i < input.Length; i++)
{
if ((int)c > 0x7f)
if ((int)input[i] > 0x7f)
{
return true;
}
@ -479,15 +480,23 @@ namespace Microsoft.Net.Http.Headers
}
// Encode using MIME encoding
private string EncodeMime(string input)
private unsafe string EncodeMime(StringSegment input)
{
var buffer = Encoding.UTF8.GetBytes(input);
var encodedName = Convert.ToBase64String(buffer);
return string.Format(CultureInfo.InvariantCulture, "=?utf-8?B?{0}?=", encodedName);
fixed (char* chars = input.Buffer)
{
var byteCount = Encoding.UTF8.GetByteCount(chars + input.Offset, input.Length);
var buffer = new byte[byteCount];
fixed (byte* bytes = buffer)
{
Encoding.UTF8.GetBytes(chars + input.Offset, input.Length, bytes, byteCount);
}
var encodedName = Convert.ToBase64String(buffer);
return "=?utf-8?B?" + encodedName + "?=";
}
}
// Attempt to decode MIME encoded strings
private bool TryDecodeMime(string input, out string output)
private bool TryDecodeMime(StringSegment input, out string output)
{
Contract.Assert(input != null);
@ -498,9 +507,11 @@ namespace Microsoft.Net.Http.Headers
{
return false;
}
var parts = processedInput.Split('?');
var parts = processedInput.Split(QuestionMark).ToArray();
// "=, encodingName, encodingType, encodedData, ="
if (parts.Length != 5 || parts[0] != "\"=" || parts[4] != "=\"" || parts[2].ToLowerInvariant() != "b")
if (parts.Length != 5 || parts[0] != "\"=" || parts[4] != "=\""
|| !parts[2].Equals("b", StringComparison.OrdinalIgnoreCase))
{
// Not encoded.
// This does not support multi-line encoding.
@ -510,8 +521,8 @@ namespace Microsoft.Net.Http.Headers
try
{
var encoding = Encoding.GetEncoding(parts[1]);
var bytes = Convert.FromBase64String(parts[3]);
var encoding = Encoding.GetEncoding(parts[1].ToString());
var bytes = Convert.FromBase64String(parts[3].ToString());
output = encoding.GetString(bytes, 0, bytes.Length);
return true;
}
@ -528,11 +539,12 @@ namespace Microsoft.Net.Http.Headers
// Encode a string using RFC 5987 encoding
// encoding'lang'PercentEncodedSpecials
private string Encode5987(string input)
private string Encode5987(StringSegment input)
{
var builder = new StringBuilder("UTF-8\'\'");
foreach (char c in input)
for (int i = 0; i < input.Length; i++)
{
var c = input[i];
// attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
// ; token except ( "*" / "'" / "%" )
if (c > 0x7F) // Encodes as multiple utf-8 bytes
@ -569,10 +581,11 @@ namespace Microsoft.Net.Http.Headers
// Attempt to decode using RFC 5987 encoding.
// encoding'language'my%20string
private bool TryDecode5987(string input, out string output)
private bool TryDecode5987(StringSegment input, out string output)
{
output = null;
var parts = input.Split('\'');
var parts = input.Split(SingleQuote).ToArray();
if (parts.Length != 3)
{
return false;
@ -582,7 +595,7 @@ namespace Microsoft.Net.Http.Headers
byte[] unescapedBytes = null;
try
{
var encoding = Encoding.GetEncoding(parts[0]);
var encoding = Encoding.GetEncoding(parts[0].ToString());
var dataString = parts[2];
unescapedBytes = ArrayPool<byte>.Shared.Rent(dataString.Length);
@ -629,7 +642,7 @@ namespace Microsoft.Net.Http.Headers
return true;
}
private static bool IsHexEncoding(string pattern, int index)
private static bool IsHexEncoding(StringSegment pattern, int index)
{
if ((pattern.Length - index) < 3)
{
@ -661,7 +674,7 @@ namespace Microsoft.Net.Http.Headers
return true;
}
private static byte HexUnescape(string pattern, ref int index)
private static byte HexUnescape(StringSegment pattern, ref int index)
{
if ((index < 0) || (index >= pattern.Length))
{

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -23,7 +24,7 @@ namespace Microsoft.Net.Http.Headers
}
return header.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(header.FileName) || !string.IsNullOrEmpty(header.FileNameStar));
&& (!StringSegment.IsNullOrEmpty(header.FileName) || !StringSegment.IsNullOrEmpty(header.FileNameStar));
}
/// <summary>
@ -39,7 +40,7 @@ namespace Microsoft.Net.Http.Headers
}
return header.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(header.FileName) && string.IsNullOrEmpty(header.FileNameStar);
&& StringSegment.IsNullOrEmpty(header.FileName) && StringSegment.IsNullOrEmpty(header.FileNameStar);
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -13,7 +14,7 @@ namespace Microsoft.Net.Http.Headers
private static readonly HttpHeaderParser<ContentRangeHeaderValue> Parser
= new GenericHeaderParser<ContentRangeHeaderValue>(false, GetContentRangeLength);
private string _unit;
private StringSegment _unit;
private long? _from;
private long? _to;
private long? _length;
@ -77,7 +78,7 @@ namespace Microsoft.Net.Http.Headers
_unit = HeaderUtilities.BytesUnit;
}
public string Unit
public StringSegment Unit
{
get { return _unit; }
set
@ -122,12 +123,12 @@ namespace Microsoft.Net.Http.Headers
}
return ((_from == other._from) && (_to == other._to) && (_length == other._length) &&
(string.Compare(_unit, other._unit, StringComparison.OrdinalIgnoreCase) == 0));
StringSegment.Equals(_unit, other._unit, StringComparison.OrdinalIgnoreCase));
}
public override int GetHashCode()
{
var result = StringComparer.OrdinalIgnoreCase.GetHashCode(_unit);
var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_unit);
if (HasRange)
{
@ -144,7 +145,8 @@ namespace Microsoft.Net.Http.Headers
public override string ToString()
{
var sb = new StringBuilder(_unit);
var sb = new StringBuilder();
sb.Append(_unit);
sb.Append(' ');
if (HasRange)
@ -171,25 +173,25 @@ namespace Microsoft.Net.Http.Headers
return sb.ToString();
}
public static ContentRangeHeaderValue Parse(string input)
public static ContentRangeHeaderValue Parse(StringSegment input)
{
var index = 0;
return Parser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out ContentRangeHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out ContentRangeHeaderValue parsedValue)
{
var index = 0;
return Parser.TryParseValue(input, ref index, out parsedValue);
}
private static int GetContentRangeLength(string input, int startIndex, out ContentRangeHeaderValue parsedValue)
private static int GetContentRangeLength(StringSegment input, int startIndex, out ContentRangeHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
@ -202,7 +204,7 @@ namespace Microsoft.Net.Http.Headers
return 0;
}
var unit = input.Substring(startIndex, unitLength);
var unit = input.Subsegment(startIndex, unitLength);
var current = startIndex + unitLength;
var separatorLength = HttpRuleParser.GetWhitespaceLength(input, current);
@ -259,7 +261,7 @@ namespace Microsoft.Net.Http.Headers
return current - startIndex;
}
private static bool TryGetLengthLength(string input, ref int current, out int lengthLength)
private static bool TryGetLengthLength(StringSegment input, ref int current, out int lengthLength)
{
lengthLength = 0;
@ -284,7 +286,7 @@ namespace Microsoft.Net.Http.Headers
return true;
}
private static bool TryGetRangeLength(string input, ref int current, out int fromLength, out int toStartIndex, out int toLength)
private static bool TryGetRangeLength(StringSegment input, ref int current, out int fromLength, out int toStartIndex, out int toLength)
{
fromLength = 0;
toStartIndex = 0;
@ -341,8 +343,8 @@ namespace Microsoft.Net.Http.Headers
}
private static bool TryCreateContentRange(
string input,
string unit,
StringSegment input,
StringSegment unit,
int fromStartIndex,
int fromLength,
int toStartIndex,
@ -354,13 +356,13 @@ namespace Microsoft.Net.Http.Headers
parsedValue = null;
long from = 0;
if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(fromStartIndex, fromLength), out from))
if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(fromStartIndex, fromLength), out from))
{
return false;
}
long to = 0;
if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(toStartIndex, toLength), out to))
if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(toStartIndex, toLength), out to))
{
return false;
}
@ -372,7 +374,7 @@ namespace Microsoft.Net.Http.Headers
}
long length = 0;
if ((lengthLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(lengthStartIndex, lengthLength),
if ((lengthLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(lengthStartIndex, lengthLength),
out length))
{
return false;

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics.Contracts;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -12,7 +13,7 @@ namespace Microsoft.Net.Http.Headers
{
}
public sealed override bool TryParseValue(string value, ref int index, out CookieHeaderValue parsedValue)
public sealed override bool TryParseValue(StringSegment value, ref int index, out CookieHeaderValue parsedValue)
{
parsedValue = null;
@ -21,7 +22,7 @@ namespace Microsoft.Net.Http.Headers
// Accept: text/xml; q=1
// Accept:
// Accept: text/plain; q=0.2
if (string.IsNullOrEmpty(value) || (index == value.Length))
if (StringSegment.IsNullOrEmpty(value) || (index == value.Length))
{
return SupportsMultipleValues;
}
@ -62,7 +63,7 @@ namespace Microsoft.Net.Http.Headers
return true;
}
private static int GetNextNonEmptyOrWhitespaceIndex(string input, int startIndex, bool skipEmptyValues, out bool separatorFound)
private static int GetNextNonEmptyOrWhitespaceIndex(StringSegment input, int startIndex, bool skipEmptyValues, out bool separatorFound)
{
Contract.Requires(input != null);
Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length.

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -14,16 +15,16 @@ namespace Microsoft.Net.Http.Headers
private static readonly CookieHeaderParser SingleValueParser = new CookieHeaderParser(supportsMultipleValues: false);
private static readonly CookieHeaderParser MultipleValueParser = new CookieHeaderParser(supportsMultipleValues: true);
private string _name;
private string _value;
private StringSegment _name;
private StringSegment _value;
private CookieHeaderValue()
{
// Used by the parser to create a new instance of this type.
}
public CookieHeaderValue(string name)
: this(name, string.Empty)
public CookieHeaderValue(StringSegment name)
: this(name, StringSegment.Empty)
{
if (name == null)
{
@ -31,7 +32,7 @@ namespace Microsoft.Net.Http.Headers
}
}
public CookieHeaderValue(string name, string value)
public CookieHeaderValue(StringSegment name, StringSegment value)
{
if (name == null)
{
@ -47,7 +48,7 @@ namespace Microsoft.Net.Http.Headers
Value = value;
}
public string Name
public StringSegment Name
{
get { return _name; }
set
@ -57,7 +58,7 @@ namespace Microsoft.Net.Http.Headers
}
}
public string Value
public StringSegment Value
{
get { return _value; }
set
@ -79,24 +80,13 @@ namespace Microsoft.Net.Http.Headers
return header.ToString();
}
private static void AppendSegment(StringBuilder builder, string name, string value)
{
builder.Append("; ");
builder.Append(name);
if (value != null)
{
builder.Append("=");
builder.Append(value);
}
}
public static CookieHeaderValue Parse(string input)
public static CookieHeaderValue Parse(StringSegment input)
{
var index = 0;
return SingleValueParser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out CookieHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out CookieHeaderValue parsedValue)
{
var index = 0;
return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
@ -123,13 +113,13 @@ namespace Microsoft.Net.Http.Headers
}
// name=value; name="value"
internal static bool TryGetCookieLength(string input, ref int offset, out CookieHeaderValue parsedValue)
internal static bool TryGetCookieLength(StringSegment input, ref int offset, out CookieHeaderValue parsedValue)
{
Contract.Requires(offset >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (offset >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length))
{
return false;
}
@ -146,7 +136,7 @@ namespace Microsoft.Net.Http.Headers
{
return false;
}
result._name = input.Substring(offset, itemLength);
result._name = input.Subsegment(offset, itemLength);
offset += itemLength;
// = (no spaces)
@ -166,7 +156,7 @@ namespace Microsoft.Net.Http.Headers
// cookie-value = *cookie-octet / ( DQUOTE* cookie-octet DQUOTE )
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
// ; US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash
internal static string GetCookieValue(string input, ref int offset)
internal static StringSegment GetCookieValue(StringSegment input, ref int offset)
{
Contract.Requires(input != null);
Contract.Requires(offset >= 0);
@ -176,7 +166,7 @@ namespace Microsoft.Net.Http.Headers
if (offset >= input.Length)
{
return string.Empty;
return StringSegment.Empty;
}
var inQuotes = false;
@ -202,7 +192,7 @@ namespace Microsoft.Net.Http.Headers
if (offset == input.Length || input[offset] != '"')
{
// Missing final quote
return string.Empty;
return StringSegment.Empty;
}
offset++;
}
@ -210,13 +200,13 @@ namespace Microsoft.Net.Http.Headers
int length = offset - startIndex;
if (offset > startIndex)
{
return input.Substring(startIndex, length);
return input.Subsegment(startIndex, length);
}
return string.Empty;
return StringSegment.Empty;
}
private static bool ReadEqualsSign(string input, ref int offset)
private static bool ReadEqualsSign(StringSegment input, ref int offset)
{
// = (no spaces)
if (offset >= input.Length || input[offset] != '=')
@ -238,7 +228,7 @@ namespace Microsoft.Net.Http.Headers
return !(c == '"' || c == ',' || c == ';' || c == '\\');
}
internal static void CheckNameFormat(string name, string parameterName)
internal static void CheckNameFormat(StringSegment name, string parameterName)
{
if (name == null)
{
@ -251,7 +241,7 @@ namespace Microsoft.Net.Http.Headers
}
}
internal static void CheckValueFormat(string value, string parameterName)
internal static void CheckValueFormat(StringSegment value, string parameterName)
{
if (value == null)
{
@ -275,8 +265,8 @@ namespace Microsoft.Net.Http.Headers
return false;
}
return string.Equals(_name, other._name, StringComparison.OrdinalIgnoreCase)
&& string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase);
return StringSegment.Equals(_name, other._name, StringComparison.OrdinalIgnoreCase)
&& StringSegment.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode()

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -22,7 +23,7 @@ namespace Microsoft.Net.Http.Headers
private static EntityTagHeaderValue AnyType;
private string _tag;
private StringSegment _tag;
private bool _isWeak;
private EntityTagHeaderValue()
@ -30,20 +31,20 @@ namespace Microsoft.Net.Http.Headers
// Used by the parser to create a new instance of this type.
}
public EntityTagHeaderValue(string tag)
public EntityTagHeaderValue(StringSegment tag)
: this(tag, false)
{
}
public EntityTagHeaderValue(string tag, bool isWeak)
public EntityTagHeaderValue(StringSegment tag, bool isWeak)
{
if (string.IsNullOrEmpty(tag))
if (StringSegment.IsNullOrEmpty(tag))
{
throw new ArgumentException("An empty string is not allowed.", nameof(tag));
}
int length = 0;
if (!isWeak && string.Equals(tag, "*", StringComparison.Ordinal))
if (!isWeak && StringSegment.Equals(tag, "*", StringComparison.Ordinal))
{
// * is valid, but W/* isn't.
_tag = tag;
@ -74,7 +75,7 @@ namespace Microsoft.Net.Http.Headers
}
}
public string Tag
public StringSegment Tag
{
get { return _tag; }
}
@ -88,9 +89,9 @@ namespace Microsoft.Net.Http.Headers
{
if (_isWeak)
{
return "W/" + _tag;
return "W/" + _tag.ToString();
}
return _tag;
return _tag.ToString();
}
/// <summary>
@ -112,7 +113,7 @@ namespace Microsoft.Net.Http.Headers
}
// Since the tag is a quoted-string we treat it case-sensitive.
return _isWeak == other._isWeak && string.Equals(_tag, other._tag, StringComparison.Ordinal);
return _isWeak == other._isWeak && StringSegment.Equals(_tag, other._tag, StringComparison.Ordinal);
}
public override int GetHashCode()
@ -139,21 +140,21 @@ namespace Microsoft.Net.Http.Headers
if (useStrongComparison)
{
return !IsWeak && !other.IsWeak && string.Equals(Tag, other.Tag, StringComparison.Ordinal);
return !IsWeak && !other.IsWeak && StringSegment.Equals(Tag, other.Tag, StringComparison.Ordinal);
}
else
{
return string.Equals(Tag, other.Tag, StringComparison.Ordinal);
return StringSegment.Equals(Tag, other.Tag, StringComparison.Ordinal);
}
}
public static EntityTagHeaderValue Parse(string input)
public static EntityTagHeaderValue Parse(StringSegment input)
{
var index = 0;
return SingleValueParser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out EntityTagHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out EntityTagHeaderValue parsedValue)
{
var index = 0;
return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
@ -179,13 +180,13 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
}
internal static int GetEntityTagLength(string input, int startIndex, out EntityTagHeaderValue parsedValue)
internal static int GetEntityTagLength(StringSegment input, int startIndex, out EntityTagHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
@ -235,7 +236,7 @@ namespace Microsoft.Net.Http.Headers
}
else
{
parsedValue._tag = input.Substring(tagStartIndex, tagLength);
parsedValue._tag = input.Subsegment(tagStartIndex, tagLength);
parsedValue._isWeak = isWeak;
}

View File

@ -2,12 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
internal sealed class GenericHeaderParser<T> : BaseHeaderParser<T>
{
internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue);
internal delegate int GetParsedValueLengthDelegate(StringSegment value, int startIndex, out T parsedValue);
private GetParsedValueLengthDelegate _getParsedValueLength;
@ -22,7 +23,7 @@ namespace Microsoft.Net.Http.Headers
_getParsedValueLength = getParsedValueLength;
}
protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue)
protected override int GetParsedValueLength(StringSegment value, int startIndex, out T parsedValue)
{
return _getParsedValueLength(value, startIndex, out parsedValue);
}

View File

@ -72,9 +72,9 @@ namespace Microsoft.Net.Http.Headers
return null;
}
internal static void CheckValidToken(string value, string parameterName)
internal static void CheckValidToken(StringSegment value, string parameterName)
{
if (string.IsNullOrEmpty(value))
if (StringSegment.IsNullOrEmpty(value))
{
throw new ArgumentException("An empty string is not allowed.", parameterName);
}
@ -85,21 +85,6 @@ namespace Microsoft.Net.Http.Headers
}
}
internal static void CheckValidQuotedString(string value, string parameterName)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("An empty string is not allowed.", parameterName);
}
int length;
if ((HttpRuleParser.GetQuotedStringLength(value, 0, out length) != HttpParseResult.Parsed) ||
(length != value.Length)) // no trailing spaces allowed
{
throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid quoted string '{0}'.", value));
}
}
internal static bool AreEqualCollections<T>(ICollection<T> x, ICollection<T> y)
{
return AreEqualCollections(x, y, null);
@ -167,7 +152,7 @@ namespace Microsoft.Net.Http.Headers
}
internal static int GetNextNonEmptyOrWhitespaceIndex(
string input,
StringSegment input,
int startIndex,
bool skipEmptyValues,
out bool separatorFound)
@ -368,11 +353,6 @@ namespace Microsoft.Net.Http.Headers
return false;
}
internal static bool TryParseNonNegativeInt32(string value, out int result)
{
return TryParseNonNegativeInt32(new StringSegment(value), out result);
}
/// <summary>
/// Try to convert a string representation of a positive number to its 64-bit signed integer equivalent.
/// A return value indicates whether the conversion succeeded or failed.
@ -388,12 +368,7 @@ namespace Microsoft.Net.Http.Headers
/// result will be overwritten.
/// </param>
/// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns>
public static bool TryParseNonNegativeInt64(string value, out long result)
{
return TryParseNonNegativeInt64(new StringSegment(value), out result);
}
internal static unsafe bool TryParseNonNegativeInt32(StringSegment value, out int result)
public static unsafe bool TryParseNonNegativeInt32(StringSegment value, out int result)
{
if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
{
@ -483,7 +458,7 @@ namespace Microsoft.Net.Http.Headers
// Strict and fast RFC7231 5.3.1 Quality value parser (and without memory allocation)
// See https://tools.ietf.org/html/rfc7231#section-5.3.1
// Check is made to verify if the value is between 0 and 1 (and it returns False if the check fails).
internal static bool TryParseQualityDouble(string input, int startIndex, out double quality, out int length)
internal static bool TryParseQualityDouble(StringSegment input, int startIndex, out double quality, out int length)
{
quality = 0;
length = 0;
@ -602,7 +577,7 @@ namespace Microsoft.Net.Http.Headers
return new string(charBuffer, position, _int64MaxStringLength - position);
}
public static bool TryParseDate(string input, out DateTimeOffset result)
public static bool TryParseDate(StringSegment input, out DateTimeOffset result)
{
return HttpRuleParser.TryStringToDate(input, out result);
}
@ -617,11 +592,11 @@ namespace Microsoft.Net.Http.Headers
return dateTime.ToRfc1123String(quoted);
}
public static string RemoveQuotes(string input)
public static StringSegment RemoveQuotes(StringSegment input)
{
if (!string.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"')
if (!StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"')
{
input = input.Substring(1, input.Length - 2);
input = input.Subsegment(1, input.Length - 2);
}
return input;
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -26,9 +27,9 @@ namespace Microsoft.Net.Http.Headers
// pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0
// for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first
// non-whitespace after the separator ','.
public abstract bool TryParseValue(string value, ref int index, out T parsedValue);
public abstract bool TryParseValue(StringSegment value, ref int index, out T parsedValue);
public T ParseValue(string value, ref int index)
public T ParseValue(StringSegment value, ref int index)
{
// Index may be value.Length (e.g. both 0). This may be allowed for some headers (e.g. Accept but not
// allowed by others (e.g. Content-Length). The parser has to decide if this is valid or not.
@ -40,7 +41,7 @@ namespace Microsoft.Net.Http.Headers
if (!TryParseValue(value, ref index, out result))
{
throw new FormatException(string.Format(CultureInfo.InvariantCulture,
"The header contains invalid values at index {0}: '{1}'", index, value ?? "<null>"));
"The header contains invalid values at index {0}: '{1}'", index, value.Value ?? "<null>"));
}
return result;
}

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -89,7 +90,7 @@ namespace Microsoft.Net.Http.Headers
}
[Pure]
internal static int GetTokenLength(string input, int startIndex)
internal static int GetTokenLength(StringSegment input, int startIndex)
{
Contract.Requires(input != null);
Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
@ -112,7 +113,7 @@ namespace Microsoft.Net.Http.Headers
return input.Length - startIndex;
}
internal static int GetWhitespaceLength(string input, int startIndex)
internal static int GetWhitespaceLength(StringSegment input, int startIndex)
{
Contract.Requires(input != null);
Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
@ -156,7 +157,7 @@ namespace Microsoft.Net.Http.Headers
return input.Length - startIndex;
}
internal static int GetNumberLength(string input, int startIndex, bool allowDecimal)
internal static int GetNumberLength(StringSegment input, int startIndex, bool allowDecimal)
{
Contract.Requires(input != null);
Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
@ -201,7 +202,7 @@ namespace Microsoft.Net.Http.Headers
return current - startIndex;
}
internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length)
internal static HttpParseResult GetQuotedStringLength(StringSegment input, int startIndex, out int length)
{
var nestedCount = 0;
return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length);
@ -209,7 +210,7 @@ namespace Microsoft.Net.Http.Headers
// quoted-pair = "\" CHAR
// CHAR = <any US-ASCII character (octets 0 - 127)>
internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length)
internal static HttpParseResult GetQuotedPairLength(StringSegment input, int startIndex, out int length)
{
Contract.Requires(input != null);
Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
@ -237,8 +238,8 @@ namespace Microsoft.Net.Http.Headers
// Try the various date formats in the order listed above.
// We should accept a wide verity of common formats, but only output RFC 1123 style dates.
internal static bool TryStringToDate(string input, out DateTimeOffset result) =>
DateTimeOffset.TryParseExact(input, DateFormats, DateTimeFormatInfo.InvariantInfo,
internal static bool TryStringToDate(StringSegment input, out DateTimeOffset result) =>
DateTimeOffset.TryParseExact(input.ToString(), DateFormats, DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal, out result);
// TEXT = <any OCTET except CTLs, but including LWS>
@ -253,7 +254,7 @@ namespace Microsoft.Net.Http.Headers
// comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any)
// is unusual.
private static HttpParseResult GetExpressionLength(
string input,
StringSegment input,
int startIndex,
char openChar,
char closeChar,

View File

@ -7,6 +7,7 @@ using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -22,7 +23,7 @@ namespace Microsoft.Net.Http.Headers
// Use a collection instead of a dictionary since we may have multiple parameters with the same name.
private ObjectCollection<NameValueHeaderValue> _parameters;
private string _mediaType;
private StringSegment _mediaType;
private bool _isReadOnly;
private MediaTypeHeaderValue()
@ -30,23 +31,23 @@ namespace Microsoft.Net.Http.Headers
// Used by the parser to create a new instance of this type.
}
public MediaTypeHeaderValue(string mediaType)
public MediaTypeHeaderValue(StringSegment mediaType)
{
CheckMediaTypeFormat(mediaType, "mediaType");
_mediaType = mediaType;
}
public MediaTypeHeaderValue(string mediaType, double quality)
public MediaTypeHeaderValue(StringSegment mediaType, double quality)
: this(mediaType)
{
Quality = quality;
}
public string Charset
public StringSegment Charset
{
get
{
return NameValueHeaderValue.Find(_parameters, CharsetString)?.Value;
return NameValueHeaderValue.Find(_parameters, CharsetString)?.Value.Value;
}
set
{
@ -54,7 +55,7 @@ namespace Microsoft.Net.Http.Headers
// We don't prevent a user from setting whitespace-only charsets. Like we can't prevent a user from
// setting a non-existing charset.
var charsetParameter = NameValueHeaderValue.Find(_parameters, CharsetString);
if (string.IsNullOrEmpty(value))
if (StringSegment.IsNullOrEmpty(value))
{
// Remove charset parameter
if (charsetParameter != null)
@ -81,11 +82,11 @@ namespace Microsoft.Net.Http.Headers
get
{
var charset = Charset;
if (!string.IsNullOrWhiteSpace(charset))
if (!StringSegment.IsNullOrEmpty(charset))
{
try
{
return Encoding.GetEncoding(charset);
return Encoding.GetEncoding(charset.Value);
}
catch (ArgumentException)
{
@ -108,17 +109,17 @@ namespace Microsoft.Net.Http.Headers
}
}
public string Boundary
public StringSegment Boundary
{
get
{
return NameValueHeaderValue.Find(_parameters, BoundaryString)?.Value;
return NameValueHeaderValue.Find(_parameters, BoundaryString)?.Value ?? default(StringSegment);
}
set
{
HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
var boundaryParameter = NameValueHeaderValue.Find(_parameters, BoundaryString);
if (string.IsNullOrEmpty(value))
if (StringSegment.IsNullOrEmpty(value))
{
// Remove charset parameter
if (boundaryParameter != null)
@ -169,7 +170,7 @@ namespace Microsoft.Net.Http.Headers
}
}
public string MediaType
public StringSegment MediaType
{
get { return _mediaType; }
set
@ -180,19 +181,19 @@ namespace Microsoft.Net.Http.Headers
}
}
public string Type
public StringSegment Type
{
get
{
return _mediaType.Substring(0, _mediaType.IndexOf('/'));
return _mediaType.Subsegment(0, _mediaType.IndexOf('/'));
}
}
public string SubType
public StringSegment SubType
{
get
{
return _mediaType.Substring(_mediaType.IndexOf('/') + 1);
return _mediaType.Subsegment(_mediaType.IndexOf('/') + 1);
}
}
@ -214,7 +215,7 @@ namespace Microsoft.Net.Http.Headers
{
get
{
return string.Compare(_mediaType, _mediaType.IndexOf('/') + 1, "*", 0, 1, StringComparison.Ordinal) == 0;
return SubType.Equals("*", StringComparison.Ordinal);
}
}
@ -245,31 +246,15 @@ namespace Microsoft.Net.Http.Headers
return false;
}
// PERF: Avoid doing anything here that allocates a substring, this is a very hot path
// for content-negotiation.
var indexOfSlash = _mediaType.IndexOf('/');
// "text/plain" is a subset of "text/plain", "text/*" and "*/*". "*/*" is a subset only of "*/*".
if (string.Compare(
strA: _mediaType,
indexA: 0,
strB: otherMediaType._mediaType,
indexB: 0,
length: indexOfSlash,
comparisonType: StringComparison.OrdinalIgnoreCase) != 0)
if (!Type.Equals(otherMediaType.Type, comparisonType: StringComparison.OrdinalIgnoreCase))
{
if (!otherMediaType.MatchesAllTypes)
{
return false;
}
}
else if (string.Compare(
strA: MediaType,
indexA: indexOfSlash + 1,
strB: otherMediaType._mediaType,
indexB: indexOfSlash + 1, // We know the Type is equal, so the index of '/' is the same in both strings.
length: _mediaType.Length - indexOfSlash,
comparisonType: StringComparison.OrdinalIgnoreCase) != 0)
else if (!SubType.Equals(otherMediaType.SubType, comparisonType: StringComparison.OrdinalIgnoreCase))
{
if (!otherMediaType.MatchesAllSubTypes)
{
@ -285,7 +270,7 @@ namespace Microsoft.Net.Http.Headers
// parameters locally; they make this one more specific.
foreach (var parameter in otherMediaType._parameters)
{
if (string.Equals(parameter.Name, "q", StringComparison.OrdinalIgnoreCase))
if (parameter.Name.Equals("q", StringComparison.OrdinalIgnoreCase))
{
// "q" and later parameters are not involved in media type matching. Quoting the RFC: The first
// "q" parameter (if any) separates the media-range parameter(s) from the accept-params.
@ -299,7 +284,7 @@ namespace Microsoft.Net.Http.Headers
return false;
}
if (!string.Equals(parameter.Value, localParameter.Value, StringComparison.OrdinalIgnoreCase))
if (!StringSegment.Equals(parameter.Value, localParameter.Value, StringComparison.OrdinalIgnoreCase))
{
return false;
}
@ -352,7 +337,10 @@ namespace Microsoft.Net.Http.Headers
public override string ToString()
{
return _mediaType + NameValueHeaderValue.ToString(_parameters, ';', true);
var builder = new StringBuilder();
builder.Append(_mediaType);
NameValueHeaderValue.ToString(_parameters, separator: ';', leadingSeparator: true, destination: builder);
return builder.ToString();
}
public override bool Equals(object obj)
@ -364,23 +352,23 @@ namespace Microsoft.Net.Http.Headers
return false;
}
return (string.Compare(_mediaType, other._mediaType, StringComparison.OrdinalIgnoreCase) == 0) &&
return _mediaType.Equals(other._mediaType, StringComparison.OrdinalIgnoreCase) &&
HeaderUtilities.AreEqualCollections(_parameters, other._parameters);
}
public override int GetHashCode()
{
// The media-type string is case-insensitive.
return StringComparer.OrdinalIgnoreCase.GetHashCode(_mediaType) ^ NameValueHeaderValue.GetHashCode(_parameters);
return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_mediaType) ^ NameValueHeaderValue.GetHashCode(_parameters);
}
public static MediaTypeHeaderValue Parse(string input)
public static MediaTypeHeaderValue Parse(StringSegment input)
{
var index = 0;
return SingleValueParser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out MediaTypeHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out MediaTypeHeaderValue parsedValue)
{
var index = 0;
return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
@ -406,20 +394,19 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
}
private static int GetMediaTypeLength(string input, int startIndex, out MediaTypeHeaderValue parsedValue)
private static int GetMediaTypeLength(StringSegment input, int startIndex, out MediaTypeHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
// Caller must remove leading whitespace. If not, we'll return 0.
string mediaType = null;
var mediaTypeLength = MediaTypeHeaderValue.GetMediaTypeExpressionLength(input, startIndex, out mediaType);
var mediaTypeLength = MediaTypeHeaderValue.GetMediaTypeExpressionLength(input, startIndex, out var mediaType);
if (mediaTypeLength == 0)
{
@ -451,7 +438,7 @@ namespace Microsoft.Net.Http.Headers
return current - startIndex;
}
private static int GetMediaTypeExpressionLength(string input, int startIndex, out string mediaType)
private static int GetMediaTypeExpressionLength(StringSegment input, int startIndex, out StringSegment mediaType)
{
Contract.Requires((input != null) && (input.Length > 0) && (startIndex < input.Length));
@ -490,7 +477,7 @@ namespace Microsoft.Net.Http.Headers
var mediaTypeLength = current + subtypeLength - startIndex;
if (typeLength + subtypeLength + 1 == mediaTypeLength)
{
mediaType = input.Substring(startIndex, mediaTypeLength);
mediaType = input.Subsegment(startIndex, mediaTypeLength);
}
else
{
@ -500,17 +487,16 @@ namespace Microsoft.Net.Http.Headers
return mediaTypeLength;
}
private static void CheckMediaTypeFormat(string mediaType, string parameterName)
private static void CheckMediaTypeFormat(StringSegment mediaType, string parameterName)
{
if (string.IsNullOrEmpty(mediaType))
if (StringSegment.IsNullOrEmpty(mediaType))
{
throw new ArgumentException("An empty string is not allowed.", parameterName);
}
// When adding values using strongly typed objects, no leading/trailing LWS (whitespace) is allowed.
// Also no LWS between type and subtype is allowed.
string tempMediaType;
var mediaTypeLength = GetMediaTypeExpressionLength(mediaType, 0, out tempMediaType);
var mediaTypeLength = GetMediaTypeExpressionLength(mediaType, 0, out var tempMediaType);
if ((mediaTypeLength == 0) || (tempMediaType.Length != mediaType.Length))
{
throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid media type '{0}'.", mediaType));

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -18,8 +19,8 @@ namespace Microsoft.Net.Http.Headers
internal static readonly HttpHeaderParser<NameValueHeaderValue> MultipleValueParser
= new GenericHeaderParser<NameValueHeaderValue>(true, GetNameValueLength);
private string _name;
private string _value;
private StringSegment _name;
private StringSegment _value;
private bool _isReadOnly;
private NameValueHeaderValue()
@ -27,12 +28,12 @@ namespace Microsoft.Net.Http.Headers
// Used by the parser to create a new instance of this type.
}
public NameValueHeaderValue(string name)
public NameValueHeaderValue(StringSegment name)
: this(name, null)
{
}
public NameValueHeaderValue(string name, string value)
public NameValueHeaderValue(StringSegment name, StringSegment value)
{
CheckNameValueFormat(name, value);
@ -40,12 +41,12 @@ namespace Microsoft.Net.Http.Headers
_value = value;
}
public string Name
public StringSegment Name
{
get { return _name; }
}
public string Value
public StringSegment Value
{
get { return _value; }
set
@ -90,9 +91,9 @@ namespace Microsoft.Net.Http.Headers
{
Contract.Assert(_name != null);
var nameHashCode = StringComparer.OrdinalIgnoreCase.GetHashCode(_name);
var nameHashCode = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_name);
if (!string.IsNullOrEmpty(_value))
if (!StringSegment.IsNullOrEmpty(_value))
{
// If we have a quoted-string, then just use the hash code. If we have a token, convert to lowercase
// and retrieve the hash code.
@ -101,7 +102,7 @@ namespace Microsoft.Net.Http.Headers
return nameHashCode ^ _value.GetHashCode();
}
return nameHashCode ^ StringComparer.OrdinalIgnoreCase.GetHashCode(_value);
return nameHashCode ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_value);
}
return nameHashCode;
@ -116,7 +117,7 @@ namespace Microsoft.Net.Http.Headers
return false;
}
if (string.Compare(_name, other._name, StringComparison.OrdinalIgnoreCase) != 0)
if (!_name.Equals(other._name, StringComparison.OrdinalIgnoreCase))
{
return false;
}
@ -125,29 +126,29 @@ namespace Microsoft.Net.Http.Headers
// case-sensitive comparison. The RFC doesn't mention how to compare quoted-strings outside the "Expect"
// header. We treat all quoted-strings the same: case-sensitive comparison.
if (string.IsNullOrEmpty(_value))
if (StringSegment.IsNullOrEmpty(_value))
{
return string.IsNullOrEmpty(other._value);
return StringSegment.IsNullOrEmpty(other._value);
}
if (_value[0] == '"')
{
// We have a quoted string, so we need to do case-sensitive comparison.
return (string.CompareOrdinal(_value, other._value) == 0);
return (_value.Equals(other._value, StringComparison.Ordinal));
}
else
{
return (string.Compare(_value, other._value, StringComparison.OrdinalIgnoreCase) == 0);
return (_value.Equals(other._value, StringComparison.OrdinalIgnoreCase));
}
}
public static NameValueHeaderValue Parse(string input)
public static NameValueHeaderValue Parse(StringSegment input)
{
var index = 0;
return SingleValueParser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out NameValueHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out NameValueHeaderValue parsedValue)
{
var index = 0;
return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
@ -175,11 +176,11 @@ namespace Microsoft.Net.Http.Headers
public override string ToString()
{
if (!string.IsNullOrEmpty(_value))
if (!StringSegment.IsNullOrEmpty(_value))
{
return _name + "=" + _value;
}
return _name;
return _name.ToString();
}
internal static void ToString(
@ -202,7 +203,12 @@ namespace Microsoft.Net.Http.Headers
destination.Append(separator);
destination.Append(' ');
}
destination.Append(values[i].ToString());
destination.Append(values[i].Name);
if (!StringSegment.IsNullOrEmpty(values[i].Value))
{
destination.Append('=');
destination.Append(values[i].Value);
}
}
}
@ -235,14 +241,14 @@ namespace Microsoft.Net.Http.Headers
return result;
}
private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue)
private static int GetNameValueLength(StringSegment input, int startIndex, out NameValueHeaderValue parsedValue)
{
Contract.Requires(input != null);
Contract.Requires(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
@ -256,7 +262,7 @@ namespace Microsoft.Net.Http.Headers
return 0;
}
var name = input.Substring(startIndex, nameLength);
var name = input.Subsegment(startIndex, nameLength);
var current = startIndex + nameLength;
current = current + HttpRuleParser.GetWhitespaceLength(input, current);
@ -280,7 +286,7 @@ namespace Microsoft.Net.Http.Headers
// Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation.
parsedValue = new NameValueHeaderValue();
parsedValue._name = name;
parsedValue._value = input.Substring(current, valueLength);
parsedValue._value = input.Subsegment(current, valueLength);
current = current + valueLength;
current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces
return current - startIndex;
@ -289,7 +295,7 @@ namespace Microsoft.Net.Http.Headers
// Returns the length of a name/value list, separated by 'delimiter'. E.g. "a=b, c=d, e=f" adds 3
// name/value pairs to 'nameValueCollection' if 'delimiter' equals ','.
internal static int GetNameValueListLength(
string input,
StringSegment input,
int startIndex,
char delimiter,
IList<NameValueHeaderValue> nameValueCollection)
@ -297,7 +303,7 @@ namespace Microsoft.Net.Http.Headers
Contract.Requires(nameValueCollection != null);
Contract.Requires(startIndex >= 0);
if ((string.IsNullOrEmpty(input)) || (startIndex >= input.Length))
if ((StringSegment.IsNullOrEmpty(input)) || (startIndex >= input.Length))
{
return 0;
}
@ -330,7 +336,7 @@ namespace Microsoft.Net.Http.Headers
}
}
public static NameValueHeaderValue Find(IList<NameValueHeaderValue> values, string name)
public static NameValueHeaderValue Find(IList<NameValueHeaderValue> values, StringSegment name)
{
Contract.Requires((name != null) && (name.Length > 0));
@ -342,7 +348,7 @@ namespace Microsoft.Net.Http.Headers
for (var i = 0; i < values.Count; i++)
{
var value = values[i];
if (string.Compare(value.Name, name, StringComparison.OrdinalIgnoreCase) == 0)
if (value.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
{
return value;
}
@ -350,7 +356,7 @@ namespace Microsoft.Net.Http.Headers
return null;
}
internal static int GetValueLength(string input, int startIndex)
internal static int GetValueLength(StringSegment input, int startIndex)
{
Contract.Requires(input != null);
@ -373,16 +379,16 @@ namespace Microsoft.Net.Http.Headers
return valueLength;
}
private static void CheckNameValueFormat(string name, string value)
private static void CheckNameValueFormat(StringSegment name, StringSegment value)
{
HeaderUtilities.CheckValidToken(name, nameof(name));
CheckValueFormat(value);
}
private static void CheckValueFormat(string value)
private static void CheckValueFormat(StringSegment value)
{
// Either value is null/empty or a valid token/quoted string
if (!(string.IsNullOrEmpty(value) || (GetValueLength(value, 0) == value.Length)))
if (!(StringSegment.IsNullOrEmpty(value) || (GetValueLength(value, 0) == value.Length)))
{
throw new FormatException(string.Format(System.Globalization.CultureInfo.InvariantCulture, "The header value is invalid: '{0}'", value));
}

View File

@ -11,7 +11,7 @@ namespace Microsoft.Net.Http.Headers
// type to throw if 'null' gets added. Collection<T> internally uses List<T> which comes at some cost. In addition
// Collection<T>.Add() calls List<T>.InsertItem() which is an O(n) operation (compared to O(1) for List<T>.Add()).
// This type is only used for very small collections (1-2 items) to keep the impact of using Collection<T> small.
internal class ObjectCollection<T> : Collection<T> where T : class
internal class ObjectCollection<T> : Collection<T>
{
internal static readonly Action<T> DefaultValidator = CheckNotNull;
internal static readonly ObjectCollection<T> EmptyReadOnlyCollection

View File

@ -3,6 +3,7 @@
using System;
using System.Diagnostics.Contracts;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -85,26 +86,26 @@ namespace Microsoft.Net.Http.Headers
return _entityTag.GetHashCode();
}
public static RangeConditionHeaderValue Parse(string input)
public static RangeConditionHeaderValue Parse(StringSegment input)
{
var index = 0;
return Parser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out RangeConditionHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out RangeConditionHeaderValue parsedValue)
{
var index = 0;
return Parser.TryParseValue(input, ref index, out parsedValue);
}
private static int GetRangeConditionLength(string input, int startIndex, out RangeConditionHeaderValue parsedValue)
private static int GetRangeConditionLength(StringSegment input, int startIndex, out RangeConditionHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
parsedValue = null;
// Make sure we have at least 2 characters
if (string.IsNullOrEmpty(input) || (startIndex + 1 >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex + 1 >= input.Length))
{
return 0;
}
@ -141,7 +142,7 @@ namespace Microsoft.Net.Http.Headers
}
else
{
if (!HttpRuleParser.TryStringToDate(input.Substring(current), out date))
if (!HttpRuleParser.TryStringToDate(input.Subsegment(current), out date))
{
return 0;
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -13,7 +14,7 @@ namespace Microsoft.Net.Http.Headers
private static readonly HttpHeaderParser<RangeHeaderValue> Parser
= new GenericHeaderParser<RangeHeaderValue>(false, GetRangeLength);
private string _unit;
private StringSegment _unit;
private ICollection<RangeItemHeaderValue> _ranges;
public RangeHeaderValue()
@ -28,7 +29,7 @@ namespace Microsoft.Net.Http.Headers
Ranges.Add(new RangeItemHeaderValue(from, to));
}
public string Unit
public StringSegment Unit
{
get { return _unit; }
set
@ -52,7 +53,8 @@ namespace Microsoft.Net.Http.Headers
public override string ToString()
{
var sb = new StringBuilder(_unit);
var sb = new StringBuilder();
sb.Append(_unit);
sb.Append('=');
var first = true;
@ -84,13 +86,13 @@ namespace Microsoft.Net.Http.Headers
return false;
}
return (string.Compare(_unit, other._unit, StringComparison.OrdinalIgnoreCase) == 0) &&
return StringSegment.Equals(_unit, other._unit, StringComparison.OrdinalIgnoreCase) &&
HeaderUtilities.AreEqualCollections(Ranges, other.Ranges);
}
public override int GetHashCode()
{
var result = StringComparer.OrdinalIgnoreCase.GetHashCode(_unit);
var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_unit);
foreach (var range in Ranges)
{
@ -100,25 +102,25 @@ namespace Microsoft.Net.Http.Headers
return result;
}
public static RangeHeaderValue Parse(string input)
public static RangeHeaderValue Parse(StringSegment input)
{
var index = 0;
return Parser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out RangeHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out RangeHeaderValue parsedValue)
{
var index = 0;
return Parser.TryParseValue(input, ref index, out parsedValue);
}
private static int GetRangeLength(string input, int startIndex, out RangeHeaderValue parsedValue)
private static int GetRangeLength(StringSegment input, int startIndex, out RangeHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
@ -132,7 +134,7 @@ namespace Microsoft.Net.Http.Headers
}
RangeHeaderValue result = new RangeHeaderValue();
result._unit = input.Substring(startIndex, unitLength);
result._unit = input.Subsegment(startIndex, unitLength);
var current = startIndex + unitLength;
current = current + HttpRuleParser.GetWhitespaceLength(input, current);

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -87,7 +88,7 @@ namespace Microsoft.Net.Http.Headers
// 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,
StringSegment input,
int startIndex,
ICollection<RangeItemHeaderValue> rangeCollection)
{
@ -96,7 +97,7 @@ namespace Microsoft.Net.Http.Headers
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))
if ((StringSegment.IsNullOrEmpty(input)) || (startIndex >= input.Length))
{
return 0;
}
@ -140,7 +141,7 @@ namespace Microsoft.Net.Http.Headers
}
}
internal static int GetRangeItemLength(string input, int startIndex, out RangeItemHeaderValue parsedValue)
internal static int GetRangeItemLength(StringSegment input, int startIndex, out RangeItemHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
@ -148,7 +149,7 @@ namespace Microsoft.Net.Http.Headers
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
@ -202,14 +203,14 @@ namespace Microsoft.Net.Http.Headers
// Try convert first value to int64
long from = 0;
if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(fromStartIndex, fromLength), out from))
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.Substring(toStartIndex, toLength), out to))
if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(toStartIndex, toLength), out to))
{
return 0;
}

View File

@ -31,20 +31,20 @@ namespace Microsoft.Net.Http.Headers
private static readonly HttpHeaderParser<SetCookieHeaderValue> MultipleValueParser
= new GenericHeaderParser<SetCookieHeaderValue>(true, GetSetCookieLength);
private string _name;
private string _value;
private StringSegment _name;
private StringSegment _value;
private SetCookieHeaderValue()
{
// Used by the parser to create a new instance of this type.
}
public SetCookieHeaderValue(string name)
: this(name, string.Empty)
public SetCookieHeaderValue(StringSegment name)
: this(name, StringSegment.Empty)
{
}
public SetCookieHeaderValue(string name, string value)
public SetCookieHeaderValue(StringSegment name, StringSegment value)
{
if (name == null)
{
@ -60,7 +60,7 @@ namespace Microsoft.Net.Http.Headers
Value = value;
}
public string Name
public StringSegment Name
{
get { return _name; }
set
@ -70,7 +70,7 @@ namespace Microsoft.Net.Http.Headers
}
}
public string Value
public StringSegment Value
{
get { return _value; }
set
@ -84,10 +84,9 @@ namespace Microsoft.Net.Http.Headers
public TimeSpan? MaxAge { get; set; }
public string Domain { get; set; }
public StringSegment Domain { get; set; }
// TODO: PathString?
public string Path { get; set; }
public StringSegment Path { get; set; }
public bool Secure { get; set; }
@ -186,7 +185,7 @@ namespace Microsoft.Net.Http.Headers
return sb.ToString();
}
private static void AppendSegment(ref InplaceStringBuilder builder, string name, string value)
private static void AppendSegment(ref InplaceStringBuilder builder, StringSegment name, StringSegment value)
{
builder.Append(SeparatorToken);
builder.Append(name);
@ -247,7 +246,7 @@ namespace Microsoft.Net.Http.Headers
}
}
private static void AppendSegment(StringBuilder builder, string name, string value)
private static void AppendSegment(StringBuilder builder, StringSegment name, StringSegment value)
{
builder.Append("; ");
builder.Append(name);
@ -258,13 +257,13 @@ namespace Microsoft.Net.Http.Headers
}
}
public static SetCookieHeaderValue Parse(string input)
public static SetCookieHeaderValue Parse(StringSegment input)
{
var index = 0;
return SingleValueParser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out SetCookieHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out SetCookieHeaderValue parsedValue)
{
var index = 0;
return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
@ -291,14 +290,14 @@ namespace Microsoft.Net.Http.Headers
}
// name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax}; httponly
private static int GetSetCookieLength(string input, int startIndex, out SetCookieHeaderValue parsedValue)
private static int GetSetCookieLength(StringSegment input, int startIndex, out SetCookieHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
var offset = startIndex;
parsedValue = null;
if (string.IsNullOrEmpty(input) || (offset >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length))
{
return 0;
}
@ -315,7 +314,7 @@ namespace Microsoft.Net.Http.Headers
{
return 0;
}
result._name = input.Substring(offset, itemLength);
result._name = input.Subsegment(offset, itemLength);
offset += itemLength;
// = (no spaces)
@ -352,11 +351,11 @@ namespace Microsoft.Net.Http.Headers
// Trailing ';' or leading into garbage. Let the next parser fail.
break;
}
var token = input.Substring(offset, itemLength);
var token = input.Subsegment(offset, itemLength);
offset += itemLength;
// expires-av = "Expires=" sane-cookie-date
if (string.Equals(token, ExpiresToken, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(token, ExpiresToken, StringComparison.OrdinalIgnoreCase))
{
// = (no spaces)
if (!ReadEqualsSign(input, ref offset))
@ -373,7 +372,7 @@ namespace Microsoft.Net.Http.Headers
result.Expires = expirationDate;
}
// max-age-av = "Max-Age=" non-zero-digit *DIGIT
else if (string.Equals(token, MaxAgeToken, StringComparison.OrdinalIgnoreCase))
else if (StringSegment.Equals(token, MaxAgeToken, StringComparison.OrdinalIgnoreCase))
{
// = (no spaces)
if (!ReadEqualsSign(input, ref offset))
@ -386,7 +385,7 @@ namespace Microsoft.Net.Http.Headers
{
return 0;
}
var numberString = input.Substring(offset, itemLength);
var numberString = input.Subsegment(offset, itemLength);
long maxAge;
if (!HeaderUtilities.TryParseNonNegativeInt64(numberString, out maxAge))
{
@ -398,7 +397,7 @@ namespace Microsoft.Net.Http.Headers
}
// domain-av = "Domain=" domain-value
// domain-value = <subdomain> ; defined in [RFC1034], Section 3.5, as enhanced by [RFC1123], Section 2.1
else if (string.Equals(token, DomainToken, StringComparison.OrdinalIgnoreCase))
else if (StringSegment.Equals(token, DomainToken, StringComparison.OrdinalIgnoreCase))
{
// = (no spaces)
if (!ReadEqualsSign(input, ref offset))
@ -410,7 +409,7 @@ namespace Microsoft.Net.Http.Headers
}
// path-av = "Path=" path-value
// path-value = <any CHAR except CTLs or ";">
else if (string.Equals(token, PathToken, StringComparison.OrdinalIgnoreCase))
else if (StringSegment.Equals(token, PathToken, StringComparison.OrdinalIgnoreCase))
{
// = (no spaces)
if (!ReadEqualsSign(input, ref offset))
@ -421,13 +420,13 @@ namespace Microsoft.Net.Http.Headers
result.Path = ReadToSemicolonOrEnd(input, ref offset);
}
// secure-av = "Secure"
else if (string.Equals(token, SecureToken, StringComparison.OrdinalIgnoreCase))
else if (StringSegment.Equals(token, SecureToken, StringComparison.OrdinalIgnoreCase))
{
result.Secure = true;
}
// samesite-av = "SameSite" / "SameSite=" samesite-value
// samesite-value = "Strict" / "Lax"
else if (string.Equals(token, SameSiteToken, StringComparison.OrdinalIgnoreCase))
else if (StringSegment.Equals(token, SameSiteToken, StringComparison.OrdinalIgnoreCase))
{
if (!ReadEqualsSign(input, ref offset))
{
@ -437,7 +436,7 @@ namespace Microsoft.Net.Http.Headers
{
var enforcementMode = ReadToSemicolonOrEnd(input, ref offset);
if (string.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase))
{
result.SameSite = SameSiteMode.Lax;
}
@ -448,7 +447,7 @@ namespace Microsoft.Net.Http.Headers
}
}
// httponly-av = "HttpOnly"
else if (string.Equals(token, HttpOnlyToken, StringComparison.OrdinalIgnoreCase))
else if (StringSegment.Equals(token, HttpOnlyToken, StringComparison.OrdinalIgnoreCase))
{
result.HttpOnly = true;
}
@ -463,7 +462,7 @@ namespace Microsoft.Net.Http.Headers
return offset - startIndex;
}
private static bool ReadEqualsSign(string input, ref int offset)
private static bool ReadEqualsSign(StringSegment input, ref int offset)
{
// = (no spaces)
if (offset >= input.Length || input[offset] != '=')
@ -474,7 +473,7 @@ namespace Microsoft.Net.Http.Headers
return true;
}
private static string ReadToSemicolonOrEnd(string input, ref int offset)
private static StringSegment ReadToSemicolonOrEnd(StringSegment input, ref int offset)
{
var end = input.IndexOf(';', offset);
if (end < 0)
@ -483,7 +482,7 @@ namespace Microsoft.Net.Http.Headers
end = input.Length;
}
var itemLength = end - offset;
var result = input.Substring(offset, itemLength);
var result = input.Subsegment(offset, itemLength);
offset += itemLength;
return result;
}
@ -497,12 +496,12 @@ namespace Microsoft.Net.Http.Headers
return false;
}
return string.Equals(_name, other._name, StringComparison.OrdinalIgnoreCase)
&& string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase)
return StringSegment.Equals(_name, other._name, StringComparison.OrdinalIgnoreCase)
&& StringSegment.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase)
&& Expires.Equals(other.Expires)
&& MaxAge.Equals(other.MaxAge)
&& string.Equals(Domain, other.Domain, StringComparison.OrdinalIgnoreCase)
&& string.Equals(Path, other.Path, StringComparison.OrdinalIgnoreCase)
&& StringSegment.Equals(Domain, other.Domain, StringComparison.OrdinalIgnoreCase)
&& StringSegment.Equals(Path, other.Path, StringComparison.OrdinalIgnoreCase)
&& Secure == other.Secure
&& SameSite == other.SameSite
&& HttpOnly == other.HttpOnly;
@ -510,12 +509,12 @@ namespace Microsoft.Net.Http.Headers
public override int GetHashCode()
{
return StringComparer.OrdinalIgnoreCase.GetHashCode(_name)
^ StringComparer.OrdinalIgnoreCase.GetHashCode(_value)
return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_name)
^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_value)
^ (Expires.HasValue ? Expires.GetHashCode() : 0)
^ (MaxAge.HasValue ? MaxAge.GetHashCode() : 0)
^ (Domain != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Domain) : 0)
^ (Path != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Path) : 0)
^ (Domain != null ? StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(Domain) : 0)
^ (Path != null ? StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(Path) : 0)
^ Secure.GetHashCode()
^ SameSite.GetHashCode()
^ HttpOnly.GetHashCode();

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -15,7 +16,7 @@ namespace Microsoft.Net.Http.Headers
private static readonly HttpHeaderParser<StringWithQualityHeaderValue> MultipleValueParser
= new GenericHeaderParser<StringWithQualityHeaderValue>(true, GetStringWithQualityLength);
private string _value;
private StringSegment _value;
private double? _quality;
private StringWithQualityHeaderValue()
@ -23,14 +24,14 @@ namespace Microsoft.Net.Http.Headers
// Used by the parser to create a new instance of this type.
}
public StringWithQualityHeaderValue(string value)
public StringWithQualityHeaderValue(StringSegment value)
{
HeaderUtilities.CheckValidToken(value, nameof(value));
_value = value;
}
public StringWithQualityHeaderValue(string value, double quality)
public StringWithQualityHeaderValue(StringSegment value, double quality)
{
HeaderUtilities.CheckValidToken(value, nameof(value));
@ -43,7 +44,7 @@ namespace Microsoft.Net.Http.Headers
_quality = quality;
}
public string Value
public StringSegment Value
{
get { return _value; }
}
@ -60,7 +61,7 @@ namespace Microsoft.Net.Http.Headers
return _value + "; q=" + _quality.Value.ToString("0.0##", NumberFormatInfo.InvariantInfo);
}
return _value;
return _value.ToString();
}
public override bool Equals(object obj)
@ -72,7 +73,7 @@ namespace Microsoft.Net.Http.Headers
return false;
}
if (string.Compare(_value, other._value, StringComparison.OrdinalIgnoreCase) != 0)
if (!StringSegment.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase))
{
return false;
}
@ -92,7 +93,7 @@ namespace Microsoft.Net.Http.Headers
public override int GetHashCode()
{
var result = StringComparer.OrdinalIgnoreCase.GetHashCode(_value);
var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_value);
if (_quality.HasValue)
{
@ -102,13 +103,13 @@ namespace Microsoft.Net.Http.Headers
return result;
}
public static StringWithQualityHeaderValue Parse(string input)
public static StringWithQualityHeaderValue Parse(StringSegment input)
{
var index = 0;
return SingleValueParser.ParseValue(input, ref index);
}
public static bool TryParse(string input, out StringWithQualityHeaderValue parsedValue)
public static bool TryParse(StringSegment input, out StringWithQualityHeaderValue parsedValue)
{
var index = 0;
return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
@ -134,13 +135,13 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
}
private static int GetStringWithQualityLength(string input, int startIndex, out StringWithQualityHeaderValue parsedValue)
private static int GetStringWithQualityLength(StringSegment input, int startIndex, out StringWithQualityHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
@ -154,7 +155,7 @@ namespace Microsoft.Net.Http.Headers
}
StringWithQualityHeaderValue result = new StringWithQualityHeaderValue();
result._value = input.Substring(startIndex, valueLength);
result._value = input.Subsegment(startIndex, valueLength);
var current = startIndex + valueLength;
current = current + HttpRuleParser.GetWhitespaceLength(input, current);
@ -177,7 +178,7 @@ namespace Microsoft.Net.Http.Headers
return current - startIndex;
}
private static bool TryReadQuality(string input, StringWithQualityHeaderValue result, ref int index)
private static bool TryReadQuality(StringSegment input, StringWithQualityHeaderValue result, ref int index)
{
var current = index;

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
@ -64,13 +65,13 @@ namespace Microsoft.Net.Http.Headers
return 1;
}
if (!string.Equals(stringWithQuality1.Value, stringWithQuality2.Value, StringComparison.OrdinalIgnoreCase))
if (!StringSegment.Equals(stringWithQuality1.Value, stringWithQuality2.Value, StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(stringWithQuality1.Value, "*", StringComparison.Ordinal))
if (StringSegment.Equals(stringWithQuality1.Value, "*", StringComparison.Ordinal))
{
return -1;
}
else if (string.Equals(stringWithQuality2.Value, "*", StringComparison.Ordinal))
else if (StringSegment.Equals(stringWithQuality2.Value, "*", StringComparison.Ordinal))
{
return 1;
}

View File

@ -46,8 +46,8 @@ namespace Microsoft.Net.Http.Headers
var contentDisposition = new ContentDispositionHeaderValue("inline");
Assert.Equal("inline", contentDisposition.DispositionType);
Assert.Equal(0, contentDisposition.Parameters.Count);
Assert.Null(contentDisposition.Name);
Assert.Null(contentDisposition.FileName);
Assert.Null(contentDisposition.Name.Value);
Assert.Null(contentDisposition.FileName.Value);
Assert.Null(contentDisposition.CreationDate);
Assert.Null(contentDisposition.ModificationDate);
Assert.Null(contentDisposition.ReadDate);
@ -81,7 +81,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("name", contentDisposition.Parameters.First().Name);
contentDisposition.Name = null;
Assert.Null(contentDisposition.Name);
Assert.Null(contentDisposition.Name.Value);
Assert.Equal(0, contentDisposition.Parameters.Count);
contentDisposition.Name = null; // It's OK to set it again to null; no exception.
}
@ -103,7 +103,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("NAME", contentDisposition.Parameters.First().Name);
contentDisposition.Parameters.Remove(name);
Assert.Null(contentDisposition.Name);
Assert.Null(contentDisposition.Name.Value);
}
[Fact]
@ -123,7 +123,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
contentDisposition.Parameters.Remove(fileName);
Assert.Null(contentDisposition.FileName);
Assert.Null(contentDisposition.FileName.Value);
}
[Fact]
@ -138,7 +138,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("\"=?utf-8?B?RmlsZcODTmFtZS5iYXQ=?=\"", contentDisposition.Parameters.First().Value);
contentDisposition.Parameters.Remove(contentDisposition.Parameters.First());
Assert.Null(contentDisposition.FileName);
Assert.Null(contentDisposition.FileName.Value);
}
[Fact]
@ -160,7 +160,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
contentDisposition.Parameters.Remove(fileName);
Assert.Null(contentDisposition.FileName);
Assert.Null(contentDisposition.FileName.Value);
}
[Fact]
@ -173,7 +173,7 @@ namespace Microsoft.Net.Http.Headers
contentDisposition.Parameters.Add(fileNameStar);
Assert.Equal(1, contentDisposition.Parameters.Count);
Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
Assert.Null(contentDisposition.FileNameStar); // Decode failure
Assert.Null(contentDisposition.FileNameStar.Value); // Decode failure
contentDisposition.FileNameStar = "new_name";
Assert.Equal("new_name", contentDisposition.FileNameStar);
@ -182,7 +182,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("UTF-8\'\'new_name", contentDisposition.Parameters.First().Value);
contentDisposition.Parameters.Remove(fileNameStar);
Assert.Null(contentDisposition.FileNameStar);
Assert.Null(contentDisposition.FileNameStar.Value);
}
[Fact]
@ -197,7 +197,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("UTF-8\'\'File%C3%83Name.bat", contentDisposition.Parameters.First().Value);
contentDisposition.Parameters.Remove(contentDisposition.Parameters.First());
Assert.Null(contentDisposition.FileNameStar);
Assert.Null(contentDisposition.FileNameStar.Value);
}
[Fact]
@ -211,7 +211,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(1, contentDisposition.Parameters.Count);
Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
Assert.Equal("utf-99'lang'File%CZName.bat", contentDisposition.Parameters.First().Value);
Assert.Null(contentDisposition.FileNameStar); // Decode failure
Assert.Null(contentDisposition.FileNameStar.Value); // Decode failure
contentDisposition.FileNameStar = "new_name";
Assert.Equal("new_name", contentDisposition.FileNameStar);
@ -219,7 +219,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
contentDisposition.Parameters.Remove(fileNameStar);
Assert.Null(contentDisposition.FileNameStar);
Assert.Null(contentDisposition.FileNameStar.Value);
}
[Fact]

View File

@ -45,7 +45,7 @@ namespace Microsoft.Net.Http.Headers
var mediaType = new MediaTypeHeaderValue("text/plain");
Assert.Equal("text/plain", mediaType.MediaType);
Assert.Equal(0, mediaType.Parameters.Count);
Assert.Null(mediaType.Charset);
Assert.Null(mediaType.Charset.Value);
}
[Fact]
@ -70,7 +70,7 @@ namespace Microsoft.Net.Http.Headers
var mediaType0 = new MediaTypeHeaderValue("text/plain");
var mediaType1 = mediaType0.Copy();
Assert.NotSame(mediaType0, mediaType1);
Assert.Same(mediaType0.MediaType, mediaType1.MediaType);
Assert.Same(mediaType0.MediaType.Value, mediaType1.MediaType.Value);
Assert.NotSame(mediaType0.Parameters, mediaType1.Parameters);
Assert.Equal(mediaType0.Parameters.Count, mediaType1.Parameters.Count);
}
@ -81,7 +81,7 @@ namespace Microsoft.Net.Http.Headers
var mediaType0 = new MediaTypeHeaderValue("text/plain");
var mediaType1 = mediaType0.CopyAsReadOnly();
Assert.NotSame(mediaType0, mediaType1);
Assert.Same(mediaType0.MediaType, mediaType1.MediaType);
Assert.Same(mediaType0.MediaType.Value, mediaType1.MediaType.Value);
Assert.NotSame(mediaType0.Parameters, mediaType1.Parameters);
Assert.Equal(mediaType0.Parameters.Count, mediaType1.Parameters.Count);
@ -97,14 +97,14 @@ namespace Microsoft.Net.Http.Headers
mediaType0.Parameters.Add(new NameValueHeaderValue("name", "value"));
var mediaType1 = mediaType0.Copy();
Assert.NotSame(mediaType0, mediaType1);
Assert.Same(mediaType0.MediaType, mediaType1.MediaType);
Assert.Same(mediaType0.MediaType.Value, mediaType1.MediaType.Value);
Assert.NotSame(mediaType0.Parameters, mediaType1.Parameters);
Assert.Equal(mediaType0.Parameters.Count, mediaType1.Parameters.Count);
var pair0 = mediaType0.Parameters.First();
var pair1 = mediaType1.Parameters.First();
Assert.NotSame(pair0, pair1);
Assert.Same(pair0.Name, pair1.Name);
Assert.Same(pair0.Value, pair1.Value);
Assert.Same(pair0.Name.Value, pair1.Name.Value);
Assert.Same(pair0.Value.Value, pair1.Value.Value);
}
[Fact]
@ -116,7 +116,7 @@ namespace Microsoft.Net.Http.Headers
Assert.NotSame(mediaType0, mediaType1);
Assert.False(mediaType0.IsReadOnly);
Assert.True(mediaType1.IsReadOnly);
Assert.Same(mediaType0.MediaType, mediaType1.MediaType);
Assert.Same(mediaType0.MediaType.Value, mediaType1.MediaType.Value);
Assert.NotSame(mediaType0.Parameters, mediaType1.Parameters);
Assert.False(mediaType0.Parameters.IsReadOnly);
@ -131,8 +131,8 @@ namespace Microsoft.Net.Http.Headers
Assert.NotSame(pair0, pair1);
Assert.False(pair0.IsReadOnly);
Assert.True(pair1.IsReadOnly);
Assert.Same(pair0.Name, pair1.Name);
Assert.Same(pair0.Value, pair1.Value);
Assert.Same(pair0.Name.Value, pair1.Name.Value);
Assert.Same(pair0.Value.Value, pair1.Value.Value);
}
[Fact]
@ -144,7 +144,7 @@ namespace Microsoft.Net.Http.Headers
var mediaType2 = mediaType1.Copy();
Assert.NotSame(mediaType2, mediaType1);
Assert.Same(mediaType2.MediaType, mediaType1.MediaType);
Assert.Same(mediaType2.MediaType.Value, mediaType1.MediaType.Value);
Assert.True(mediaType1.IsReadOnly);
Assert.False(mediaType2.IsReadOnly);
Assert.NotSame(mediaType2.Parameters, mediaType1.Parameters);
@ -154,8 +154,8 @@ namespace Microsoft.Net.Http.Headers
Assert.NotSame(pair2, pair1);
Assert.True(pair1.IsReadOnly);
Assert.False(pair2.IsReadOnly);
Assert.Same(pair2.Name, pair1.Name);
Assert.Same(pair2.Value, pair1.Value);
Assert.Same(pair2.Name.Value, pair1.Name.Value);
Assert.Same(pair2.Value.Value, pair1.Value.Value);
}
[Fact]
@ -178,7 +178,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("charset", mediaType.Parameters.First().Name);
mediaType.Charset = null;
Assert.Null(mediaType.Charset);
Assert.Null(mediaType.Charset.Value);
Assert.Equal(0, mediaType.Parameters.Count);
mediaType.Charset = null; // It's OK to set it again to null; no exception.
}
@ -200,7 +200,7 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal("CHARSET", mediaType.Parameters.First().Name);
mediaType.Parameters.Remove(charset);
Assert.Null(mediaType.Charset);
Assert.Null(mediaType.Charset.Value);
}
[Fact]

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -65,14 +65,14 @@ namespace Microsoft.Net.Http.Headers
var pair0 = new NameValueHeaderValue("name");
var pair1 = pair0.Copy();
Assert.NotSame(pair0, pair1);
Assert.Same(pair0.Name, pair1.Name);
Assert.Null(pair0.Value);
Assert.Null(pair1.Value);
Assert.Same(pair0.Name.Value, pair1.Name.Value);
Assert.Null(pair0.Value.Value);
Assert.Null(pair1.Value.Value);
// Change one value and verify the other is unchanged.
pair0.Value = "othervalue";
Assert.Equal("othervalue", pair0.Value);
Assert.Null(pair1.Value);
Assert.Null(pair1.Value.Value);
}
[Fact]
@ -81,16 +81,16 @@ namespace Microsoft.Net.Http.Headers
var pair0 = new NameValueHeaderValue("name");
var pair1 = pair0.CopyAsReadOnly();
Assert.NotSame(pair0, pair1);
Assert.Same(pair0.Name, pair1.Name);
Assert.Null(pair0.Value);
Assert.Null(pair1.Value);
Assert.Same(pair0.Name.Value, pair1.Name.Value);
Assert.Null(pair0.Value.Value);
Assert.Null(pair1.Value.Value);
Assert.False(pair0.IsReadOnly);
Assert.True(pair1.IsReadOnly);
// Change one value and verify the other is unchanged.
pair0.Value = "othervalue";
Assert.Equal("othervalue", pair0.Value);
Assert.Null(pair1.Value);
Assert.Null(pair1.Value.Value);
Assert.Throws<InvalidOperationException>(() => { pair1.Value = "othervalue"; });
}
@ -100,8 +100,8 @@ namespace Microsoft.Net.Http.Headers
var pair0 = new NameValueHeaderValue("name", "value");
var pair1 = pair0.Copy();
Assert.NotSame(pair0, pair1);
Assert.Same(pair0.Name, pair1.Name);
Assert.Same(pair0.Value, pair1.Value);
Assert.Same(pair0.Name.Value, pair1.Name.Value);
Assert.Same(pair0.Value.Value, pair1.Value.Value);
// Change one value and verify the other is unchanged.
pair0.Value = "othervalue";
@ -115,8 +115,8 @@ namespace Microsoft.Net.Http.Headers
var pair0 = new NameValueHeaderValue("name", "value");
var pair1 = pair0.CopyAsReadOnly();
Assert.NotSame(pair0, pair1);
Assert.Same(pair0.Name, pair1.Name);
Assert.Same(pair0.Value, pair1.Value);
Assert.Same(pair0.Name.Value, pair1.Name.Value);
Assert.Same(pair0.Value.Value, pair1.Value.Value);
Assert.False(pair0.IsReadOnly);
Assert.True(pair1.IsReadOnly);
@ -134,8 +134,8 @@ namespace Microsoft.Net.Http.Headers
var pair1 = pair0.CopyAsReadOnly();
var pair2 = pair1.Copy();
Assert.NotSame(pair0, pair1);
Assert.Same(pair0.Name, pair1.Name);
Assert.Same(pair0.Value, pair1.Value);
Assert.Same(pair0.Name.Value, pair1.Name.Value);
Assert.Same(pair0.Value.Value, pair1.Value.Value);
// Change one value and verify the other is unchanged.
pair2.Value = "othervalue";