#426 Less alloc/wrapping/boxing for Query, Forms, Cookies

This commit is contained in:
Ben Adams 2015-10-22 14:13:41 +01:00 committed by Chris R
parent af0d2e5888
commit 3c2e2b9d98
50 changed files with 1635 additions and 776 deletions

View File

@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Http
string fragmentValue = uri.GetComponents(UriComponents.Fragment, UriFormat.UriEscaped);
if (!string.IsNullOrEmpty(fragmentValue))
{
fragmentValue = "#" + fragmentValue;
fragmentValue = $"#{fragmentValue}";
}
return new FragmentString(fragmentValue);
}

View File

@ -70,18 +70,18 @@ namespace Microsoft.AspNet.Http
&& _value.IndexOf(':', index + 1) >= 0)
{
// IPv6 without brackets ::1 is the only type of host with 2 or more colons
return "[" + _value + "]";
return $"[{_value}]";
}
else if (index >= 0)
{
// Has a port
string port = _value.Substring(index);
IdnMapping mapping = new IdnMapping();
var mapping = new IdnMapping();
return mapping.GetAscii(_value, 0, index) + port;
}
else
{
IdnMapping mapping = new IdnMapping();
var mapping = new IdnMapping();
return mapping.GetAscii(_value);
}
}
@ -115,12 +115,12 @@ namespace Microsoft.AspNet.Http
{
// Has a port
string port = uriComponent.Substring(index);
IdnMapping mapping = new IdnMapping();
var mapping = new IdnMapping();
uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port;
}
else
{
IdnMapping mapping = new IdnMapping();
var mapping = new IdnMapping();
uriComponent = mapping.GetUnicode(uriComponent);
}
}

View File

@ -24,13 +24,13 @@ namespace Microsoft.AspNet.Http
public abstract string Method { get; set; }
/// <summary>
/// Gets or set the HTTP request scheme from owin.RequestScheme.
/// Gets or set the HTTP request scheme.
/// </summary>
/// <returns>The HTTP request scheme from owin.RequestScheme.</returns>
/// <returns>The HTTP request scheme.</returns>
public abstract string Scheme { get; set; }
/// <summary>
/// Returns true if the owin.RequestScheme is https.
/// Returns true if the RequestScheme is https.
/// </summary>
/// <returns>true if this request is using https; otherwise, false.</returns>
public abstract bool IsHttps { get; set; }
@ -42,33 +42,33 @@ namespace Microsoft.AspNet.Http
public abstract HostString Host { get; set; }
/// <summary>
/// Gets or set the owin.RequestPathBase.
/// Gets or set the RequestPathBase.
/// </summary>
/// <returns>The owin.RequestPathBase.</returns>
/// <returns>The RequestPathBase.</returns>
public abstract PathString PathBase { get; set; }
/// <summary>
/// Gets or set the request path from owin.RequestPath.
/// Gets or set the request path from RequestPath.
/// </summary>
/// <returns>The request path from owin.RequestPath.</returns>
/// <returns>The request path from RequestPath.</returns>
public abstract PathString Path { get; set; }
/// <summary>
/// Gets or set the query string from owin.RequestQueryString.
/// Gets or set the query string.
/// </summary>
/// <returns>The query string from owin.RequestQueryString.</returns>
/// <returns>The query string.</returns>
public abstract QueryString QueryString { get; set; }
/// <summary>
/// Gets the query value collection parsed from owin.RequestQueryString.
/// Gets the query value collection parsed from RequestQueryString.
/// </summary>
/// <returns>The query value collection parsed from owin.RequestQueryString.</returns>
public abstract IReadableStringCollection Query { get; set; }
/// <returns>The query value collection parsed from RequestQueryString.</returns>
public abstract IQueryCollection Query { get; set; }
/// <summary>
/// Gets or set the owin.RequestProtocol.
/// Gets or set the RequestProtocol.
/// </summary>
/// <returns>The owin.RequestProtocol.</returns>
/// <returns>The RequestProtocol.</returns>
public abstract string Protocol { get; set; }
/// <summary>
@ -81,7 +81,7 @@ namespace Microsoft.AspNet.Http
/// Gets the collection of Cookies for this request.
/// </summary>
/// <returns>The collection of Cookies for this request.</returns>
public abstract IReadableStringCollection Cookies { get; set; }
public abstract IRequestCookieCollection Cookies { get; set; }
/// <summary>
/// Gets or sets the Content-Length header
@ -95,9 +95,9 @@ namespace Microsoft.AspNet.Http
public abstract string ContentType { get; set; }
/// <summary>
/// Gets or set the owin.RequestBody Stream.
/// Gets or set the RequestBody Stream.
/// </summary>
/// <returns>The owin.RequestBody Stream.</returns>
/// <returns>The RequestBody Stream.</returns>
public abstract Stream Body { get; set; }
/// <summary>

View File

@ -1,13 +1,95 @@
// 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.Collections.Generic;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Http
{
/// <summary>
/// Contains the parsed form values.
/// Represents the parsed form values sent with the HttpRequest.
/// </summary>
public interface IFormCollection : IReadableStringCollection
public interface IFormCollection : IEnumerable<KeyValuePair<string, StringValues>>
{
/// <summary>
/// Gets the number of elements contained in the <see cref="T:Microsoft.AspNet.Http.IFormCollection" />.
/// </summary>
/// <returns>
/// The number of elements contained in the <see cref="T:Microsoft.AspNet.Http.IFormCollection" />.
/// </returns>
int Count { get; }
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the
/// <see cref="T:Microsoft.AspNet.Http.IFormCollection" />.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the object
/// that implements <see cref="T:Microsoft.AspNet.Http.IFormCollection" />.
/// </returns>
ICollection<string> Keys { get; }
/// <summary>
/// Determines whether the <see cref="T:Microsoft.AspNet.Http.IFormCollection" /> contains an element
/// with the specified key.
/// </summary>
/// <param name="key">
/// The key to locate in the <see cref="T:Microsoft.AspNet.Http.IFormCollection" />.
/// </param>
/// <returns>
/// true if the <see cref="T:Microsoft.AspNet.Http.IFormCollection" /> contains an element with
/// the key; otherwise, false.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// key is null.
/// </exception>
bool ContainsKey(string key);
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">
/// The key of the value to get.
/// </param>
/// <param name="value">
/// The key of the value to get.
/// When this method returns, the value associated with the specified key, if the
/// key is found; otherwise, the default value for the type of the value parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns>
/// true if the object that implements <see cref="T:Microsoft.AspNet.Http.IFormCollection" /> contains
/// an element with the specified key; otherwise, false.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// key is null.
/// </exception>
bool TryGetValue(string key, out StringValues value);
/// <summary>
/// Gets the value with the specified key.
/// </summary>
/// <param name="key">
/// The key of the value to get.
/// </param>
/// <returns>
/// The element with the specified key, or <see cref="T:Microsoft.Extensions.Primitives.StringValues" />.Empty if the key is not present.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// key is null.
/// </exception>
/// <remarks>
/// <see cref="T:Microsoft.AspNet.Http.IFormCollection" /> has a different indexer contract than
/// <see cref="T:System.Collections.Generic.IDictionary`2" />, as it will return StringValues.Empty for missing entries
/// rather than throwing an Exception.
/// </remarks>
StringValues this[string key] { get; }
/// <summary>
/// The file collection sent with the request.
/// </summary>
/// <param name="key"></param>
/// <returns>The files included with the request.</returns>
IFormFileCollection Files { get; }
}
}

View File

@ -5,6 +5,9 @@ using System.IO;
namespace Microsoft.AspNet.Http
{
/// <summary>
/// Represents a file sent with the HttpRequest.
/// </summary>
public interface IFormFile
{
string ContentType { get; }

View File

@ -5,6 +5,9 @@ using System.Collections.Generic;
namespace Microsoft.AspNet.Http
{
/// <summary>
/// Represents the collection of files sent with the HttpRequest.
/// </summary>
public interface IFormFileCollection : IReadOnlyList<IFormFile>
{
IFormFile this[string name] { get; }

View File

@ -0,0 +1,88 @@
// 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.Collections.Generic;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Http
{
/// <summary>
/// Represents the HttpRequest query string collection
/// </summary>
public interface IQueryCollection : IEnumerable<KeyValuePair<string, StringValues>>
{
/// <summary>
/// Gets the number of elements contained in the <see cref="T:Microsoft.AspNet.Http.IQueryCollection" />.
/// </summary>
/// <returns>
/// The number of elements contained in the <see cref="T:Microsoft.AspNet.Http.IQueryCollection" />.
/// </returns>
int Count { get; }
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the
/// <see cref="T:Microsoft.AspNet.Http.IQueryCollection" />.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the object
/// that implements <see cref="T:Microsoft.AspNet.Http.IQueryCollection" />.
/// </returns>
ICollection<string> Keys { get; }
/// <summary>
/// Determines whether the <see cref="T:Microsoft.AspNet.Http.IQueryCollection" /> contains an element
/// with the specified key.
/// </summary>
/// <param name="key">
/// The key to locate in the <see cref="T:Microsoft.AspNet.Http.IQueryCollection" />.
/// </param>
/// <returns>
/// true if the <see cref="T:Microsoft.AspNet.Http.IQueryCollection" /> contains an element with
/// the key; otherwise, false.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// key is null.
/// </exception>
bool ContainsKey(string key);
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">
/// The key of the value to get.
/// </param>
/// <param name="value">
/// The key of the value to get.
/// When this method returns, the value associated with the specified key, if the
/// key is found; otherwise, the default value for the type of the value parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns>
/// true if the object that implements <see cref="T:Microsoft.AspNet.Http.IQueryCollection" /> contains
/// an element with the specified key; otherwise, false.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// key is null.
/// </exception>
bool TryGetValue(string key, out StringValues value);
/// <summary>
/// Gets the value with the specified key.
/// </summary>
/// <param name="key">
/// The key of the value to get.
/// </param>
/// <returns>
/// The element with the specified key, or <see cref="T:Microsoft.Extensions.Primitives.StringValues" />.Empty if the key is not present.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// key is null.
/// </exception>
/// <remarks>
/// <see cref="T:Microsoft.AspNet.Http.IQueryCollection" /> has a different indexer contract than
/// <see cref="T:System.Collections.Generic.IDictionary`2" />, as it will return StringValues.Empty for missing entries
/// rather than throwing an Exception.
/// </remarks>
StringValues this[string key] { get; }
}
}

View File

@ -1,39 +0,0 @@
// 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.Collections.Generic;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Http
{
/// <summary>
/// Accessors for headers, query, forms, etc.
/// </summary>
public interface IReadableStringCollection : IEnumerable<KeyValuePair<string, StringValues>>
{
/// <summary>
/// Get the associated value from the collection.
/// Returns StringValues.Empty if the key is not present.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
StringValues this[string key] { get; }
/// <summary>
/// Gets the number of elements contained in the collection.
/// </summary>
int Count { get; }
/// <summary>
/// Gets a collection containing the keys.
/// </summary>
ICollection<string> Keys { get; }
/// <summary>
/// Determines whether the collection contains an element with the specified key.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
bool ContainsKey(string key);
}
}

View File

@ -0,0 +1,87 @@
// 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.Collections.Generic;
namespace Microsoft.AspNet.Http
{
/// <summary>
/// Represents the HttpRequest cookie collection
/// </summary>
public interface IRequestCookieCollection : IEnumerable<KeyValuePair<string, string>>
{
/// <summary>
/// Gets the number of elements contained in the <see cref="T:Microsoft.AspNet.Http.IRequestCookieCollection" />.
/// </summary>
/// <returns>
/// The number of elements contained in the <see cref="T:Microsoft.AspNet.Http.IRequestCookieCollection" />.
/// </returns>
int Count { get; }
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the
/// <see cref="T:Microsoft.AspNet.Http.IRequestCookieCollection" />.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the object
/// that implements <see cref="T:Microsoft.AspNet.Http.IRequestCookieCollection" />.
/// </returns>
ICollection<string> Keys { get; }
/// <summary>
/// Determines whether the <see cref="T:Microsoft.AspNet.Http.IRequestCookieCollection" /> contains an element
/// with the specified key.
/// </summary>
/// <param name="key">
/// The key to locate in the <see cref="T:Microsoft.AspNet.Http.IRequestCookieCollection" />.
/// </param>
/// <returns>
/// true if the <see cref="T:Microsoft.AspNet.Http.IRequestCookieCollection" /> contains an element with
/// the key; otherwise, false.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// key is null.
/// </exception>
bool ContainsKey(string key);
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">
/// The key of the value to get.
/// </param>
/// <param name="value">
/// The key of the value to get.
/// When this method returns, the value associated with the specified key, if the
/// key is found; otherwise, the default value for the type of the value parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns>
/// true if the object that implements <see cref="T:Microsoft.AspNet.Http.IRequestCookieCollection" /> contains
/// an element with the specified key; otherwise, false.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// key is null.
/// </exception>
bool TryGetValue(string key, out string value);
/// <summary>
/// Gets the value with the specified key.
/// </summary>
/// <param name="key">
/// The key of the value to get.
/// </param>
/// <returns>
/// The element with the specified key, or <see cref="T:System.String" />.Empty if the key is not present.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// key is null.
/// </exception>
/// <remarks>
/// <see cref="T:Microsoft.AspNet.Http.IRequestCookieCollection" /> has a different indexer contract than
/// <see cref="T:System.Collections.Generic.IDictionary`2" />, as it will return String.Empty for missing entries
/// rather than throwing an Exception.
/// </remarks>
string this[string key] { get; }
}
}

View File

@ -12,6 +12,8 @@ namespace Microsoft.AspNet.Http
/// </summary>
public struct PathString : IEquatable<PathString>
{
private static readonly char[] splitChar = { '/' };
/// <summary>
/// Represents the empty path. This field is read-only.
/// </summary>
@ -66,7 +68,24 @@ namespace Microsoft.AspNet.Http
public string ToUriComponent()
{
// TODO: Measure the cost of this escaping and consider optimizing.
return HasValue ? string.Join("/", _value.Split('/').Select(UrlEncoder.Default.Encode)) : string.Empty;
if (!HasValue)
{
return string.Empty;
}
var values = _value.Split(splitChar);
var changed = false;
for (var i = 0; i < values.Length; i++)
{
var value = values[i];
values[i] = UrlEncoder.Default.Encode(value);
if (!changed && value != values[i])
{
changed = true;
}
}
return changed ? string.Join("/", values) : _value;
}
/// <summary>

View File

@ -128,7 +128,7 @@ namespace Microsoft.AspNet.Http
throw new ArgumentNullException(nameof(value));
}
return new QueryString("?" + UrlEncoder.Default.Encode(name) + '=' + UrlEncoder.Default.Encode(value));
return new QueryString($"?{UrlEncoder.Default.Encode(name)}={UrlEncoder.Default.Encode(value)}");
}
/// <summary>

View File

@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Http
/// <returns>the associated values from the collection separated into individual values, or StringValues.Empty if the key is not present.</returns>
public static string[] GetCommaSeparatedValues(this IHeaderDictionary headers, string key)
{
return ParsingHelpers.GetHeaderSplit(headers, key);
return ParsingHelpers.GetHeaderSplit(headers, key).ToArray();
}
/// <summary>

View File

@ -78,9 +78,49 @@ namespace Microsoft.AspNet.Http
{
headers.Remove(name);
}
else if (values.Count == 1)
{
headers[name] = new StringValues(values[0].ToString());
}
else
{
headers[name] = values.Select(value => value.ToString()).ToArray();
var newValues = new string[values.Count];
for (var i = 0; i < values.Count; i++)
{
newValues[i] = values[i].ToString();
}
headers[name] = new StringValues(newValues);
}
}
public static void AppendList<T>(this IHeaderDictionary Headers, string name, IList<T> values)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (values == null)
{
throw new ArgumentNullException(nameof(values));
}
switch (values.Count)
{
case 0:
Headers.Append(name, StringValues.Empty);
break;
case 1:
Headers.Append(name, new StringValues(values[0].ToString()));
break;
default:
var newValues = new string[values.Count];
for (var i = 0; i < values.Count; i++)
{
newValues[i] = values[i].ToString();
}
Headers.Append(name, new StringValues(newValues));
break;
}
}
@ -139,7 +179,7 @@ namespace Microsoft.AspNet.Http
if (KnownParsers.TryGetValue(typeof(T), out temp))
{
var func = (Func<string, T>)temp;
return func(headers[name]);
return func(headers[name].ToString());
}
var value = headers[name];
@ -148,7 +188,7 @@ namespace Microsoft.AspNet.Http
return default(T);
}
return GetViaReflection<T>(value);
return GetViaReflection<T>(value.ToString());
}
internal static IList<T> GetList<T>(this IHeaderDictionary headers, string name)
@ -162,7 +202,7 @@ namespace Microsoft.AspNet.Http
if (KnownListParsers.TryGetValue(typeof(T), out temp))
{
var func = (Func<IList<string>, IList<T>>)temp;
return func(headers[name]);
return func(headers[name].ToArray());
}
var values = headers[name];
@ -179,7 +219,7 @@ namespace Microsoft.AspNet.Http
// TODO: Cache the reflected type for later? Only if success?
var type = typeof(T);
var method = type.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(methodInfo =>
.FirstOrDefault(methodInfo =>
{
if (string.Equals("TryParse", methodInfo.Name, StringComparison.Ordinal)
&& methodInfo.ReturnParameter.ParameterType.Equals(typeof(bool)))
@ -191,7 +231,7 @@ namespace Microsoft.AspNet.Http
&& methodParams[1].ParameterType.Equals(type.MakeByRefType());
}
return false;
}).FirstOrDefault();
});
if (method == null)
{
@ -213,7 +253,7 @@ namespace Microsoft.AspNet.Http
// TODO: Cache the reflected type for later? Only if success?
var type = typeof(T);
var method = type.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(methodInfo =>
.FirstOrDefault(methodInfo =>
{
if (string.Equals("TryParseList", methodInfo.Name, StringComparison.Ordinal)
&& methodInfo.ReturnParameter.ParameterType.Equals(typeof(Boolean)))
@ -225,7 +265,7 @@ namespace Microsoft.AspNet.Http
&& methodParams[1].ParameterType.Equals(typeof(IList<T>).MakeByRefType());
}
return false;
}).FirstOrDefault();
});
if (method == null)
{

View File

@ -1,4 +1,9 @@
using System;
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Http.Internal
{

View File

@ -1,3 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
@ -16,7 +19,7 @@ namespace Microsoft.AspNet.Http.Internal
public bool Equals(HeaderSegmentCollection other)
{
return Equals(_headers, other._headers);
return StringValues.Equals(_headers, other._headers);
}
public override bool Equals(object obj)

View File

@ -10,13 +10,13 @@ namespace Microsoft.AspNet.Http.Internal
{
internal static class ParsingHelpers
{
public static StringValues GetHeader(IDictionary<string, StringValues> headers, string key)
public static StringValues GetHeader(IHeaderDictionary headers, string key)
{
StringValues value;
return headers.TryGetValue(key, out value) ? value : StringValues.Empty;
}
public static StringValues GetHeaderSplit(IDictionary<string, StringValues> headers, string key)
public static StringValues GetHeaderSplit(IHeaderDictionary headers, string key)
{
var values = GetHeaderUnmodified(headers, key);
return new StringValues(GetHeaderSplitImplementation(values).ToArray());
@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Http.Internal
}
}
public static StringValues GetHeaderUnmodified(IDictionary<string, StringValues> headers, string key)
public static StringValues GetHeaderUnmodified(IHeaderDictionary headers, string key)
{
if (headers == null)
{
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Http.Internal
return headers.TryGetValue(key, out values) ? values : StringValues.Empty;
}
public static void SetHeaderJoined(IDictionary<string, StringValues> headers, string key, StringValues value)
public static void SetHeaderJoined(IHeaderDictionary headers, string key, StringValues value)
{
if (headers == null)
{
@ -61,35 +61,26 @@ namespace Microsoft.AspNet.Http.Internal
}
else
{
headers[key] = string.Join(",", value.Select(QuoteIfNeeded));
headers[key] = string.Join(",", value.Select((s) => QuoteIfNeeded(s)));
}
}
// Quote items that contain comas and are not already quoted.
private static string QuoteIfNeeded(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
// Ignore
if (!string.IsNullOrWhiteSpace(value) &&
value.Contains(',') &&
(value[0] != '"' || value[value.Length - 1] != '"'))
{
return $"\"{value}\"";
}
else if (value.Contains(','))
{
if (value[0] != '"' || value[value.Length - 1] != '"')
{
value = '"' + value + '"';
}
}
return value;
}
private static string DeQuote(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
// Ignore
}
else if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"')
if (!string.IsNullOrWhiteSpace(value) &&
(value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"'))
{
value = value.Substring(1, value.Length - 2);
}
@ -97,7 +88,7 @@ namespace Microsoft.AspNet.Http.Internal
return value;
}
public static void SetHeaderUnmodified(IDictionary<string, StringValues> headers, string key, StringValues? values)
public static void SetHeaderUnmodified(IHeaderDictionary headers, string key, StringValues? values)
{
if (headers == null)
{
@ -118,7 +109,7 @@ namespace Microsoft.AspNet.Http.Internal
}
}
public static void AppendHeaderJoined(IDictionary<string, StringValues> headers, string key, params string[] values)
public static void AppendHeaderJoined(IHeaderDictionary headers, string key, params string[] values)
{
if (headers == null)
{
@ -135,7 +126,7 @@ namespace Microsoft.AspNet.Http.Internal
return;
}
string existing = GetHeader(headers, key);
string existing = GetHeader(headers, key).ToString();
if (existing == null)
{
SetHeaderJoined(headers, key, values);
@ -146,7 +137,7 @@ namespace Microsoft.AspNet.Http.Internal
}
}
public static void AppendHeaderUnmodified(IDictionary<string, StringValues> headers, string key, StringValues values)
public static void AppendHeaderUnmodified(IHeaderDictionary headers, string key, StringValues values)
{
if (headers == null)
{

View File

@ -1,146 +0,0 @@
using System;
namespace Microsoft.AspNet.Http.Internal
{
internal struct StringSegment : IEquatable<StringSegment>
{
private readonly string _buffer;
private readonly int _offset;
private readonly int _count;
// <summary>
// Initializes a new instance of the <see cref="T:System.Object"/> class.
// </summary>
public StringSegment(string buffer, int offset, int count)
{
_buffer = buffer;
_offset = offset;
_count = count;
}
public string Buffer
{
get { return _buffer; }
}
public int Offset
{
get { return _offset; }
}
public int Count
{
get { return _count; }
}
public string Value
{
get { return _offset == -1 ? null : _buffer.Substring(_offset, _count); }
}
public bool HasValue
{
get { return _offset != -1 && _count != 0 && _buffer != null; }
}
public bool Equals(StringSegment other)
{
return string.Equals(_buffer, other._buffer) && _offset == other._offset && _count == other._count;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is StringSegment && Equals((StringSegment)obj);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = (_buffer != null ? _buffer.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ _offset;
hashCode = (hashCode * 397) ^ _count;
return hashCode;
}
}
public static bool operator ==(StringSegment left, StringSegment right)
{
return left.Equals(right);
}
public static bool operator !=(StringSegment left, StringSegment right)
{
return !left.Equals(right);
}
public bool StartsWith(string text, StringComparison comparisonType)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
int textLength = text.Length;
if (!HasValue || _count < textLength)
{
return false;
}
return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0;
}
public bool EndsWith(string text, StringComparison comparisonType)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
int textLength = text.Length;
if (!HasValue || _count < textLength)
{
return false;
}
return string.Compare(_buffer, _offset + _count - textLength, text, 0, textLength, comparisonType) == 0;
}
public bool Equals(string text, StringComparison comparisonType)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
int textLength = text.Length;
if (!HasValue || _count != textLength)
{
return false;
}
return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0;
}
public string Substring(int offset, int length)
{
return _buffer.Substring(_offset + offset, length);
}
public StringSegment Subsegment(int offset, int length)
{
return new StringSegment(_buffer, _offset + offset, length);
}
public override string ToString()
{
return Value ?? string.Empty;
}
}
}

View File

@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Http.Extensions;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Http.Headers
@ -170,7 +170,7 @@ namespace Microsoft.AspNet.Http.Headers
{
get
{
return HostString.FromUriComponent(Headers[HeaderNames.Host]);
return HostString.FromUriComponent(Headers[HeaderNames.Host].ToString());
}
set
{
@ -309,17 +309,7 @@ namespace Microsoft.AspNet.Http.Headers
public void AppendList<T>(string name, IList<T> values)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (values == null)
{
throw new ArgumentNullException(nameof(values));
}
Headers.Append(name, values.Select(value => value.ToString()).ToArray());
Headers.AppendList<T>(name, values);
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Http.Extensions;
using Microsoft.Net.Http.Headers;
@ -135,7 +134,7 @@ namespace Microsoft.AspNet.Http.Headers
get
{
Uri uri;
if (Uri.TryCreate(Headers[HeaderNames.Location], UriKind.RelativeOrAbsolute, out uri))
if (Uri.TryCreate(Headers[HeaderNames.Location].ToString(), UriKind.RelativeOrAbsolute, out uri))
{
return uri;
}
@ -206,17 +205,7 @@ namespace Microsoft.AspNet.Http.Headers
public void AppendList<T>(string name, IList<T> values)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (values == null)
{
throw new ArgumentNullException(nameof(values));
}
Headers.Append(name, values.Select(value => value.ToString()).ToArray());
Headers.AppendList<T>(name, values);
}
}
}

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Http.Extensions
FragmentString fragment = new FragmentString())
{
string combinePath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/";
return combinePath + query + fragment;
return $"{combinePath}{query.ToString()}{fragment.ToString()}";
}
/// <summary>
@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Http.Extensions
FragmentString fragment = new FragmentString())
{
string combinePath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/";
return scheme + "://" + host + combinePath + query + fragment;
return $"{scheme}://{host.ToString()}{combinePath}{query.ToString()}{fragment.ToString()}";
}
/// <summary>

View File

@ -7,7 +7,7 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Http
{
/// <summary>
/// Represents request and response headers
/// Represents HttpRequest and HttpResponse headers
/// </summary>
public interface IHeaderDictionary : IDictionary<string, StringValues>
{

View File

@ -140,11 +140,11 @@ namespace Microsoft.AspNet.Http.Internal
public override HostString Host
{
get { return HostString.FromUriComponent(Headers["Host"]); }
get { return HostString.FromUriComponent(Headers["Host"].ToString()); }
set { Headers["Host"] = value.ToUriComponent(); }
}
public override IReadableStringCollection Query
public override IQueryCollection Query
{
get { return QueryFeature.Query; }
set { QueryFeature.Query = value; }
@ -161,7 +161,7 @@ namespace Microsoft.AspNet.Http.Internal
get { return HttpRequestFeature.Headers; }
}
public override IReadableStringCollection Cookies
public override IRequestCookieCollection Cookies
{
get { return RequestCookiesFeature.Cookies; }
set { RequestCookiesFeature.Cookies = value; }

View File

@ -87,7 +87,7 @@ namespace Microsoft.AspNet.Http.Internal
{
get
{
return Headers[HeaderNames.ContentType];
return Headers[HeaderNames.ContentType].ToString();
}
set
{

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Http.Internal
{
get
{
return ParsingHelpers.GetHeaderSplit(HttpRequestFeature.Headers, HeaderNames.WebSocketSubProtocols);
return ParsingHelpers.GetHeaderSplit(HttpRequestFeature.Headers, HeaderNames.WebSocketSubProtocols).ToArray();
}
}

View File

@ -5,12 +5,13 @@ using System;
namespace Microsoft.AspNet.Http.Features
{
internal sealed class FeatureHelpers
internal static class FeatureHelpers
{
public static T GetAndCache<T>(
IFeatureCache cache,
IFeatureCollection features,
ref T cachedObject)
where T : class
{
cache.CheckFeaturesRevision();
@ -26,6 +27,7 @@ namespace Microsoft.AspNet.Http.Features
public static T GetOrCreate<T>(
IFeatureCollection features,
Func<T> factory)
where T : class
{
T obj = features.Get<T>();
if (obj == null)
@ -43,6 +45,7 @@ namespace Microsoft.AspNet.Http.Features
IFeatureCollection features,
Func<T> factory,
ref T cachedObject)
where T : class
{
cache.CheckFeaturesRevision();
@ -65,6 +68,7 @@ namespace Microsoft.AspNet.Http.Features
IFeatureCollection features,
Func<IFeatureCollection, T> factory,
ref T cachedObject)
where T : class
{
cache.CheckFeaturesRevision();
@ -88,6 +92,7 @@ namespace Microsoft.AspNet.Http.Features
HttpRequest request,
Func<HttpRequest, T> factory,
ref T cachedObject)
where T : class
{
cache.CheckFeaturesRevision();

View File

@ -2,14 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.WebUtilities;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Http.Features.Internal
@ -17,6 +15,8 @@ namespace Microsoft.AspNet.Http.Features.Internal
public class FormFeature : IFormFeature
{
private readonly HttpRequest _request;
private Task<IFormCollection> _parsedFormTask;
private IFormCollection _form;
public FormFeature(IFormCollection form)
{
@ -63,7 +63,15 @@ namespace Microsoft.AspNet.Http.Features.Internal
}
}
public IFormCollection Form { get; set; }
public IFormCollection Form
{
get { return _form; }
set
{
_parsedFormTask = null;
_form = value;
}
}
public IFormCollection ReadForm()
{
@ -77,17 +85,32 @@ namespace Microsoft.AspNet.Http.Features.Internal
throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType);
}
// TODO: Issue #456 Avoid Sync-over-Async http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx
// TODO: How do we prevent thread exhaustion?
return ReadFormAsync(CancellationToken.None).GetAwaiter().GetResult();
return ReadFormAsync().GetAwaiter().GetResult();
}
public async Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken)
{
if (Form != null)
{
return Form;
}
public Task<IFormCollection> ReadFormAsync() => ReadFormAsync(CancellationToken.None);
public Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken)
{
// Avoid state machine and task allocation for repeated reads
if (_parsedFormTask == null)
{
if (Form != null)
{
_parsedFormTask = Task.FromResult(Form);
}
else
{
_parsedFormTask = InnerReadFormAsync(cancellationToken);
}
}
return _parsedFormTask;
}
private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancellationToken)
{
if (!HasFormContentType)
{
throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType);
@ -97,18 +120,18 @@ namespace Microsoft.AspNet.Http.Features.Internal
_request.EnableRewind();
IDictionary<string, StringValues> formFields = null;
var files = new FormFileCollection();
FormCollection formFields = null;
FormFileCollection files = null;
// Some of these code paths use StreamReader which does not support cancellation tokens.
using (cancellationToken.Register(_request.HttpContext.Abort))
using (cancellationToken.Register((state) => ((HttpContext)state).Abort(), _request.HttpContext))
{
var contentType = ContentType;
// Check the content-type
if (HasApplicationFormContentType(contentType))
{
var encoding = FilterEncoding(contentType.Encoding);
formFields = await FormReader.ReadFormAsync(_request.Body, encoding, cancellationToken);
formFields = new FormCollection(await FormReader.ReadFormAsync(_request.Body, encoding, cancellationToken));
}
else if (HasMultipartFormContentType(contentType))
{
@ -119,9 +142,8 @@ namespace Microsoft.AspNet.Http.Features.Internal
var section = await multipartReader.ReadNextSectionAsync(cancellationToken);
while (section != null)
{
var headers = new HeaderDictionary(section.Headers);
ContentDispositionHeaderValue contentDisposition;
ContentDispositionHeaderValue.TryParse(headers[HeaderNames.ContentDisposition], out contentDisposition);
ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
if (HasFileContentDisposition(contentDisposition))
{
// Find the end
@ -129,8 +151,12 @@ namespace Microsoft.AspNet.Http.Features.Internal
var file = new FormFile(_request.Body, section.BaseStreamOffset.Value, section.Body.Length)
{
Headers = headers,
Headers = new HeaderDictionary(section.Headers),
};
if (files == null)
{
files = new FormFileCollection();
}
files.Add(file);
}
else if (HasFormDataContentDisposition(contentDisposition))
@ -141,7 +167,7 @@ namespace Microsoft.AspNet.Http.Features.Internal
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
MediaTypeHeaderValue mediaType;
MediaTypeHeaderValue.TryParse(headers[HeaderNames.ContentType], out mediaType);
MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
var encoding = FilterEncoding(mediaType?.Encoding);
using (var reader = new StreamReader(section.Body, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true))
{
@ -151,20 +177,35 @@ namespace Microsoft.AspNet.Http.Features.Internal
}
else
{
System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + headers[HeaderNames.ContentDisposition]);
System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition);
}
section = await multipartReader.ReadNextSectionAsync(cancellationToken);
}
formFields = formAccumulator.GetResults();
if (formAccumulator.HasValues)
{
formFields = new FormCollection(formAccumulator.GetResults(), files);
}
}
}
// Rewind so later readers don't have to.
_request.Body.Seek(0, SeekOrigin.Begin);
Form = new FormCollection(formFields, files);
if (formFields != null)
{
Form = formFields;
}
else if (files != null)
{
Form = new FormCollection(null, files);
}
else
{
Form = FormCollection.Empty;
}
return Form;
}

View File

@ -21,13 +21,13 @@ namespace Microsoft.AspNet.Http.Features.Internal
public string ContentDisposition
{
get { return Headers["Content-Disposition"]; }
get { return Headers["Content-Disposition"].ToString(); }
set { Headers["Content-Disposition"] = value; }
}
public string ContentType
{
get { return Headers["Content-Type"]; }
get { return Headers["Content-Type"].ToString(); }
set { Headers["Content-Type"] = value; }
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using Microsoft.AspNet.Http.Features;
namespace Microsoft.AspNet.Http.Features.Internal
{

View File

@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Http.Features.Internal
{
public interface IQueryFeature
{
IReadableStringCollection Query { get; set; }
IQueryCollection Query { get; set; }
}
}

View File

@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Http.Features.Internal
{
public interface IRequestCookiesFeature
{
IReadableStringCollection Cookies { get; set; }
IRequestCookieCollection Cookies { get; set; }
}
}

View File

@ -2,10 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.WebUtilities;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Http.Features.Internal
{
@ -17,18 +15,9 @@ namespace Microsoft.AspNet.Http.Features.Internal
private IHttpRequestFeature _request;
private string _original;
private IReadableStringCollection _parsedValues;
private IQueryCollection _parsedValues;
public QueryFeature(IDictionary<string, StringValues> query)
: this(new ReadableStringCollection(query))
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
}
public QueryFeature(IReadableStringCollection query)
public QueryFeature(IQueryCollection query)
{
if (query == null)
{
@ -62,20 +51,34 @@ namespace Microsoft.AspNet.Http.Features.Internal
get { return FeatureHelpers.GetAndCache(this, _features, ref _request); }
}
public IReadableStringCollection Query
public IQueryCollection Query
{
get
{
if (_features == null)
{
return _parsedValues ?? ReadableStringCollection.Empty;
if (_parsedValues == null)
{
_parsedValues = QueryCollection.Empty;
}
return _parsedValues;
}
var current = HttpRequestFeature.QueryString;
if (_parsedValues == null || !string.Equals(_original, current, StringComparison.Ordinal))
{
_original = current;
_parsedValues = new ReadableStringCollection(QueryHelpers.ParseQuery(current));
var result = QueryHelpers.ParseNullableQuery(current);
if (result == null)
{
_parsedValues = QueryCollection.Empty;
}
else
{
_parsedValues = new QueryCollection(result);
}
}
return _parsedValues;
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Http.Internal;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
@ -18,14 +17,9 @@ namespace Microsoft.AspNet.Http.Features.Internal
private IHttpRequestFeature _request;
private StringValues _original;
private IReadableStringCollection _parsedValues;
public RequestCookiesFeature(IDictionary<string, StringValues> cookies)
: this(new ReadableStringCollection(cookies))
{
}
public RequestCookiesFeature(IReadableStringCollection cookies)
private IRequestCookieCollection _parsedValues;
public RequestCookiesFeature(IRequestCookieCollection cookies)
{
if (cookies == null)
{
@ -59,32 +53,30 @@ namespace Microsoft.AspNet.Http.Features.Internal
get { return FeatureHelpers.GetAndCache(this, _features, ref _request); }
}
public IReadableStringCollection Cookies
public IRequestCookieCollection Cookies
{
get
{
if (_features == null)
{
return _parsedValues ?? ReadableStringCollection.Empty;
if (_parsedValues == null)
{
_parsedValues = RequestCookieCollection.Empty;
}
return _parsedValues;
}
var headers = HttpRequestFeature.Headers;
StringValues current;
if (!headers.TryGetValue(HeaderNames.Cookie, out current))
{
current = StringValues.Empty;
current = string.Empty;
}
if (_parsedValues == null || !Enumerable.SequenceEqual(_original, current, StringComparer.Ordinal))
if (_parsedValues == null || _original != current)
{
_original = current;
var collectionParser = _parsedValues as RequestCookiesCollection;
if (collectionParser == null)
{
collectionParser = new RequestCookiesCollection();
_parsedValues = collectionParser;
}
collectionParser.Reparse(current);
_parsedValues = RequestCookieCollection.Parse(current.ToArray());
}
return _parsedValues;
@ -104,10 +96,7 @@ namespace Microsoft.AspNet.Http.Features.Internal
var headers = new List<string>();
foreach (var pair in _parsedValues)
{
foreach (var cookieValue in pair.Value)
{
headers.Add(new CookieHeaderValue(pair.Key, cookieValue).ToString());
}
headers.Add(new CookieHeaderValue(pair.Key, pair.Value).ToString());
}
_original = headers.ToArray();
HttpRequestFeature.Headers[HeaderNames.Cookie] = _original;

View File

@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Http.Features.Internal
if (_cookiesCollection == null)
{
var headers = HttpResponseFeature.Headers;
_cookiesCollection = new ResponseCookies(new HeaderDictionary(headers));
_cookiesCollection = new ResponseCookies(headers);
}
return _cookiesCollection;
}

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
@ -10,29 +10,226 @@ namespace Microsoft.AspNet.Http.Internal
/// <summary>
/// Contains the parsed form values.
/// </summary>
public class FormCollection : ReadableStringCollection, IFormCollection
public class FormCollection : IFormCollection
{
public FormCollection(IDictionary<string, StringValues> store)
: this(store, new FormFileCollection())
public static readonly FormCollection Empty = new FormCollection();
#if DNXCORE50
private static readonly string[] EmptyKeys = Array.Empty<string>();
private static readonly StringValues[] EmptyValues = Array.Empty<StringValues>();
#else
private static readonly string[] EmptyKeys = new string[0];
private static readonly StringValues[] EmptyValues = new StringValues[0];
#endif
private static readonly Enumerator EmptyEnumerator = new Enumerator();
// Pre-box
private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = EmptyEnumerator;
private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;
private static IFormFileCollection EmptyFiles = new FormFileCollection();
private IFormFileCollection _files;
private FormCollection()
{
// For static Empty
}
public FormCollection(IDictionary<string, StringValues> store, IFormFileCollection files)
: base(store)
public FormCollection(Dictionary<string, StringValues> fields, IFormFileCollection files = null)
{
if (store == null)
{
throw new ArgumentNullException(nameof(store));
}
if (files == null)
{
throw new ArgumentNullException(nameof(files));
}
Files = files;
// can be null
Store = fields;
_files = files;
}
public IFormFileCollection Files { get; }
public IFormFileCollection Files
{
get
{
return _files ?? EmptyFiles;
}
private set { _files = value; }
}
private Dictionary<string, StringValues> Store { get; set; }
/// <summary>
/// Get or sets the associated value from the collection as a single string.
/// </summary>
/// <param name="key">The header name.</param>
/// <returns>the associated value from the collection as a StringValues or StringValues.Empty if the key is not present.</returns>
public StringValues this[string key]
{
get
{
if (Store == null)
{
return StringValues.Empty;
}
StringValues value;
if (TryGetValue(key, out value))
{
return value;
}
return StringValues.Empty;
}
}
/// <summary>
/// Gets the number of elements contained in the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" />;.
/// </summary>
/// <returns>The number of elements contained in the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" />.</returns>
public int Count
{
get
{
if (Store == null)
{
return 0;
}
return Store.Count;
}
}
public ICollection<string> Keys
{
get
{
if (Store == null)
{
return EmptyKeys;
}
return Store.Keys;
}
}
/// <summary>
/// Determines whether the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> contains a specific key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>true if the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> contains a specific key; otherwise, false.</returns>
public bool ContainsKey(string key)
{
if (Store == null)
{
return false;
}
return Store.ContainsKey(key);
}
/// <summary>
/// Retrieves a value from the dictionary.
/// </summary>
/// <param name="key">The header name.</param>
/// <param name="value">The value.</param>
/// <returns>true if the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> contains the key; otherwise, false.</returns>
public bool TryGetValue(string key, out StringValues value)
{
if (Store == null)
{
value = default(StringValues);
return false;
}
return Store.TryGetValue(key, out value);
}
/// <summary>
/// Returns an struct enumerator that iterates through a collection without boxing and is also used via the <see cref="T:Microsoft.AspNet.Http.IFormCollection" /> interface.
/// </summary>
/// <returns>An <see cref="T:Microsoft.AspNet.Http.StructEnumerator" /> object that can be used to iterate through the collection.</returns>
public Enumerator GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyEnumerator;
}
// Non-boxed Enumerator
return new Enumerator(Store.GetEnumerator());
}
/// <summary>
/// Returns an enumerator that iterates through a collection, boxes in non-empty path.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyIEnumeratorType;
}
// Boxed Enumerator
return Store.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection, boxes in non-empty path.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyIEnumerator;
}
// Boxed Enumerator
return Store.GetEnumerator();
}
public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
{
// Do NOT make this readonly, or MoveNext will not work
private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
private bool _notEmpty;
internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
{
_dictionaryEnumerator = dictionaryEnumerator;
_notEmpty = true;
}
public bool MoveNext()
{
if (_notEmpty)
{
return _dictionaryEnumerator.MoveNext();
}
return false;
}
public KeyValuePair<string, StringValues> Current
{
get
{
if (_notEmpty)
{
return _dictionaryEnumerator.Current;
}
return default(KeyValuePair<string, StringValues>);
}
}
public void Dispose()
{
}
object IEnumerator.Current
{
get
{
return Current;
}
}
void IEnumerator.Reset()
{
if (_notEmpty)
{
((IEnumerator)_dictionaryEnumerator).Reset();
}
}
}
}
}

View File

@ -9,64 +9,37 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Http.Internal
{
/// <summary>
/// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders.
/// Represents a wrapper for RequestHeaders and ResponseHeaders.
/// </summary>
public class HeaderDictionary : IHeaderDictionary
{
public HeaderDictionary() : this(new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase))
#if DNXCORE50
private static readonly string[] EmptyKeys = Array.Empty<string>();
private static readonly StringValues[] EmptyValues = Array.Empty<StringValues>();
#else
private static readonly string[] EmptyKeys = new string[0];
private static readonly StringValues[] EmptyValues = new StringValues[0];
#endif
private static readonly Enumerator EmptyEnumerator = new Enumerator();
// Pre-box
private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = EmptyEnumerator;
private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;
public HeaderDictionary()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="T:Microsoft.Owin.HeaderDictionary" /> class.
/// </summary>
/// <param name="store">The underlying data store.</param>
public HeaderDictionary(IDictionary<string, StringValues> store)
public HeaderDictionary(Dictionary<string, StringValues> store)
{
if (store == null)
{
throw new ArgumentNullException(nameof(store));
}
Store = store;
}
private IDictionary<string, StringValues> Store { get; set; }
/// <summary>
/// Gets an <see cref="T:System.Collections.ICollection" /> that contains the keys in the <see cref="T:Microsoft.Owin.HeaderDictionary" />;.
/// </summary>
/// <returns>An <see cref="T:System.Collections.ICollection" /> that contains the keys in the <see cref="T:Microsoft.Owin.HeaderDictionary" />.</returns>
public ICollection<string> Keys
public HeaderDictionary(int capacity)
{
get { return Store.Keys; }
Store = new Dictionary<string, StringValues>(capacity, StringComparer.OrdinalIgnoreCase);
}
/// <summary>
///
/// </summary>
public ICollection<StringValues> Values
{
get { return Store.Values; }
}
/// <summary>
/// Gets the number of elements contained in the <see cref="T:Microsoft.Owin.HeaderDictionary" />;.
/// </summary>
/// <returns>The number of elements contained in the <see cref="T:Microsoft.Owin.HeaderDictionary" />.</returns>
public int Count
{
get { return Store.Count; }
}
/// <summary>
/// Gets a value that indicates whether the <see cref="T:Microsoft.Owin.HeaderDictionary" /> is in read-only mode.
/// </summary>
/// <returns>true if the <see cref="T:Microsoft.Owin.HeaderDictionary" /> is in read-only mode; otherwise, false.</returns>
public bool IsReadOnly
{
get { return Store.IsReadOnly; }
}
private Dictionary<string, StringValues> Store { get; set; }
/// <summary>
/// Get or sets the associated value from the collection as a single string.
@ -75,8 +48,47 @@ namespace Microsoft.AspNet.Http.Internal
/// <returns>the associated value from the collection as a StringValues or StringValues.Empty if the key is not present.</returns>
public StringValues this[string key]
{
get { return ParsingHelpers.GetHeader(Store, key); }
set { ParsingHelpers.SetHeader(Store, key, value); }
get
{
if (Store == null)
{
return StringValues.Empty;
}
StringValues value;
if (TryGetValue(key, out value))
{
return value;
}
return StringValues.Empty;
}
set
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (StringValues.IsNullOrEmpty(value))
{
if (Store == null)
{
return;
}
Store.Remove(key);
}
else
{
if (Store == null)
{
Store = new Dictionary<string, StringValues>(1, StringComparer.OrdinalIgnoreCase);
}
Store[key] = value;
}
}
}
/// <summary>
@ -87,25 +99,76 @@ namespace Microsoft.AspNet.Http.Internal
StringValues IDictionary<string, StringValues>.this[string key]
{
get { return Store[key]; }
set { Store[key] = value; }
set { this[key] = value; }
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// Gets the number of elements contained in the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" />;.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
/// <returns>The number of elements contained in the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" />.</returns>
public int Count
{
return Store.GetEnumerator();
get
{
if (Store == null)
{
return 0;
}
return Store.Count;
}
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// Gets a value that indicates whether the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> is in read-only mode.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
/// <returns>true if the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> is in read-only mode; otherwise, false.</returns>
public bool IsReadOnly
{
return Store.GetEnumerator();
get
{
return false;
}
}
public ICollection<string> Keys
{
get
{
if (Store == null)
{
return EmptyKeys;
}
return Store.Keys;
}
}
public ICollection<StringValues> Values
{
get
{
if (Store == null)
{
return EmptyValues;
}
return Store.Values;
}
}
/// <summary>
/// Adds a new list of items to the collection.
/// </summary>
/// <param name="item">The item to add.</param>
public void Add(KeyValuePair<string, StringValues> item)
{
if (item.Key == null)
{
throw new ArgumentNullException("The key is null");
}
if (Store == null)
{
Store = new Dictionary<string, StringValues>(1, StringComparer.OrdinalIgnoreCase);
}
Store.Add(item.Key, item.Value);
}
/// <summary>
@ -115,54 +178,27 @@ namespace Microsoft.AspNet.Http.Internal
/// <param name="value">The header values.</param>
public void Add(string key, StringValues value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (Store == null)
{
Store = new Dictionary<string, StringValues>(1);
}
Store.Add(key, value);
}
/// <summary>
/// Determines whether the <see cref="T:Microsoft.Owin.HeaderDictionary" /> contains a specific key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>true if the <see cref="T:Microsoft.Owin.HeaderDictionary" /> contains a specific key; otherwise, false.</returns>
public bool ContainsKey(string key)
{
return Store.ContainsKey(key);
}
/// <summary>
/// Removes the given header from the collection.
/// </summary>
/// <param name="key">The header name.</param>
/// <returns>true if the specified object was removed from the collection; otherwise, false.</returns>
public bool Remove(string key)
{
return Store.Remove(key);
}
/// <summary>
/// Retrieves a value from the dictionary.
/// </summary>
/// <param name="key">The header name.</param>
/// <param name="value">The value.</param>
/// <returns>true if the <see cref="T:Microsoft.Owin.HeaderDictionary" /> contains the key; otherwise, false.</returns>
public bool TryGetValue(string key, out StringValues value)
{
return Store.TryGetValue(key, out value);
}
/// <summary>
/// Adds a new list of items to the collection.
/// </summary>
/// <param name="item">The item to add.</param>
public void Add(KeyValuePair<string, StringValues> item)
{
Store.Add(item);
}
/// <summary>
/// Clears the entire list of objects.
/// </summary>
public void Clear()
{
if (Store == null)
{
return;
}
Store.Clear();
}
@ -173,17 +209,47 @@ namespace Microsoft.AspNet.Http.Internal
/// <returns>true if the specified object occurs within this collection; otherwise, false.</returns>
public bool Contains(KeyValuePair<string, StringValues> item)
{
return Store.Contains(item);
StringValues value;
if (Store == null ||
!Store.TryGetValue(item.Key, out value) ||
!StringValues.Equals(value, item.Value))
{
return false;
}
return true;
}
/// <summary>
/// Copies the <see cref="T:Microsoft.Owin.HeaderDictionary" /> elements to a one-dimensional Array instance at the specified index.
/// Determines whether the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> contains a specific key.
/// </summary>
/// <param name="array">The one-dimensional Array that is the destination of the specified objects copied from the <see cref="T:Microsoft.Owin.HeaderDictionary" />.</param>
/// <param name="key">The key.</param>
/// <returns>true if the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> contains a specific key; otherwise, false.</returns>
public bool ContainsKey(string key)
{
if (Store == null)
{
return false;
}
return Store.ContainsKey(key);
}
/// <summary>
/// Copies the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> elements to a one-dimensional Array instance at the specified index.
/// </summary>
/// <param name="array">The one-dimensional Array that is the destination of the specified objects copied from the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" />.</param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array" /> at which copying begins.</param>
public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
{
Store.CopyTo(array, arrayIndex);
if (Store == null)
{
return;
}
foreach (var item in Store)
{
array[arrayIndex] = item;
arrayIndex++;
}
}
/// <summary>
@ -193,7 +259,144 @@ namespace Microsoft.AspNet.Http.Internal
/// <returns>true if the specified object was removed from the collection; otherwise, false.</returns>
public bool Remove(KeyValuePair<string, StringValues> item)
{
return Store.Remove(item);
if (Store == null)
{
return false;
}
StringValues value;
if (Store.TryGetValue(item.Key, out value) && StringValues.Equals(item.Value, value))
{
return Store.Remove(item.Key);
}
return false;
}
/// <summary>
/// Removes the given header from the collection.
/// </summary>
/// <param name="key">The header name.</param>
/// <returns>true if the specified object was removed from the collection; otherwise, false.</returns>
public bool Remove(string key)
{
if (Store == null)
{
return false;
}
return Store.Remove(key);
}
/// <summary>
/// Retrieves a value from the dictionary.
/// </summary>
/// <param name="key">The header name.</param>
/// <param name="value">The value.</param>
/// <returns>true if the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> contains the key; otherwise, false.</returns>
public bool TryGetValue(string key, out StringValues value)
{
if (Store == null)
{
value = default(StringValues);
return false;
}
return Store.TryGetValue(key, out value);
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
public Enumerator GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyEnumerator;
}
return new Enumerator(Store.GetEnumerator());
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyIEnumeratorType;
}
return Store.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyIEnumerator;
}
return Store.GetEnumerator();
}
public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
{
// Do NOT make this readonly, or MoveNext will not work
private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
private bool _notEmpty;
internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
{
_dictionaryEnumerator = dictionaryEnumerator;
_notEmpty = true;
}
public bool MoveNext()
{
if (_notEmpty)
{
return _dictionaryEnumerator.MoveNext();
}
return false;
}
public KeyValuePair<string, StringValues> Current
{
get
{
if (_notEmpty)
{
return _dictionaryEnumerator.Current;
}
return default(KeyValuePair<string, StringValues>);
}
}
public void Dispose()
{
}
object IEnumerator.Current
{
get
{
return Current;
}
}
void IEnumerator.Reset()
{
if (_notEmpty)
{
((IEnumerator)_dictionaryEnumerator).Reset();
}
}
}
}
}

View File

@ -97,7 +97,12 @@ namespace Microsoft.AspNet.Http.Internal
bool ICollection<KeyValuePair<object, object>>.Remove(KeyValuePair<object, object> item)
{
return Items.Remove(item);
object value;
if (Items.TryGetValue(item.Key, out value) && Equals(item.Value, value))
{
return Items.Remove(item.Key);
}
return false;
}
IEnumerator<KeyValuePair<object, object>> IEnumerable<KeyValuePair<object, object>>.GetEnumerator()

View File

@ -87,7 +87,7 @@ namespace Microsoft.AspNet.Http.Internal
public bool Equals(HeaderSegmentCollection other)
{
return Equals(_headers, other._headers);
return StringValues.Equals(_headers, other._headers);
}
public override bool Equals(object obj)
@ -363,13 +363,7 @@ namespace Microsoft.AspNet.Http.Internal
internal static class ParsingHelpers
{
public static StringValues GetHeader(IDictionary<string, StringValues> headers, string key)
{
StringValues value;
return headers.TryGetValue(key, out value) ? value : StringValues.Empty;
}
public static StringValues GetHeaderSplit(IDictionary<string, StringValues> headers, string key)
public static StringValues GetHeaderSplit(IHeaderDictionary headers, string key)
{
var values = GetHeaderUnmodified(headers, key);
return new StringValues(GetHeaderSplitImplementation(values).ToArray());
@ -386,7 +380,7 @@ namespace Microsoft.AspNet.Http.Internal
}
}
public static StringValues GetHeaderUnmodified(IDictionary<string, StringValues> headers, string key)
public static StringValues GetHeaderUnmodified(IHeaderDictionary headers, string key)
{
if (headers == null)
{
@ -397,32 +391,6 @@ namespace Microsoft.AspNet.Http.Internal
return headers.TryGetValue(key, out values) ? values : StringValues.Empty;
}
public static void SetHeader(IDictionary<string, StringValues> headers, string key, StringValues value)
{
if (headers == null)
{
throw new ArgumentNullException(nameof(headers));
}
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentNullException(nameof(key));
}
if (StringValues.IsNullOrEmpty(value))
{
headers.Remove(key);
}
else
{
headers[key] = value;
}
}
private static string DeQuote(string value)
{
if (string.IsNullOrWhiteSpace(value))

View File

@ -0,0 +1,227 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Http.Internal
{
/// <summary>
/// The HttpRequest query string collection
/// </summary>
public class QueryCollection : IQueryCollection
{
public static readonly QueryCollection Empty = new QueryCollection();
#if DNXCORE50
private static readonly string[] EmptyKeys = Array.Empty<string>();
private static readonly StringValues[] EmptyValues = Array.Empty<StringValues>();
#else
private static readonly string[] EmptyKeys = new string[0];
private static readonly StringValues[] EmptyValues = new StringValues[0];
#endif
private static readonly Enumerator EmptyEnumerator = new Enumerator();
// Pre-box
private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = EmptyEnumerator;
private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;
private Dictionary<string, StringValues> Store { get; set; }
public QueryCollection()
{
}
public QueryCollection(Dictionary<string, StringValues> store)
{
Store = store;
}
public QueryCollection(QueryCollection store)
{
Store = store.Store;
}
public QueryCollection(int capacity)
{
Store = new Dictionary<string, StringValues>(capacity, StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Get or sets the associated value from the collection as a single string.
/// </summary>
/// <param name="key">The header name.</param>
/// <returns>the associated value from the collection as a StringValues or StringValues.Empty if the key is not present.</returns>
public StringValues this[string key]
{
get
{
if (Store == null)
{
return StringValues.Empty;
}
StringValues value;
if (TryGetValue(key, out value))
{
return value;
}
return StringValues.Empty;
}
}
/// <summary>
/// Gets the number of elements contained in the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" />;.
/// </summary>
/// <returns>The number of elements contained in the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" />.</returns>
public int Count
{
get
{
if (Store == null)
{
return 0;
}
return Store.Count;
}
}
public ICollection<string> Keys
{
get
{
if (Store == null)
{
return EmptyKeys;
}
return Store.Keys;
}
}
/// <summary>
/// Determines whether the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> contains a specific key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>true if the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> contains a specific key; otherwise, false.</returns>
public bool ContainsKey(string key)
{
if (Store == null)
{
return false;
}
return Store.ContainsKey(key);
}
/// <summary>
/// Retrieves a value from the dictionary.
/// </summary>
/// <param name="key">The header name.</param>
/// <param name="value">The value.</param>
/// <returns>true if the <see cref="T:Microsoft.AspNet.Http.Internal.HeaderDictionary" /> contains the key; otherwise, false.</returns>
public bool TryGetValue(string key, out StringValues value)
{
if (Store == null)
{
value = default(StringValues);
return false;
}
return Store.TryGetValue(key, out value);
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
public Enumerator GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyEnumerator;
}
return new Enumerator(Store.GetEnumerator());
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyIEnumeratorType;
}
return Store.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyIEnumerator;
}
return Store.GetEnumerator();
}
public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
{
// Do NOT make this readonly, or MoveNext will not work
private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
private bool _notEmpty;
internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
{
_dictionaryEnumerator = dictionaryEnumerator;
_notEmpty = true;
}
public bool MoveNext()
{
if (_notEmpty)
{
return _dictionaryEnumerator.MoveNext();
}
return false;
}
public KeyValuePair<string, StringValues> Current
{
get
{
if (_notEmpty)
{
return _dictionaryEnumerator.Current;
}
return default(KeyValuePair<string, StringValues>);
}
}
public void Dispose()
{
}
object IEnumerator.Current
{
get
{
return Current;
}
}
void IEnumerator.Reset()
{
if (_notEmpty)
{
((IEnumerator)_dictionaryEnumerator).Reset();
}
}
}
}
}

View File

@ -1,99 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Http.Internal
{
/// <summary>
/// Accessors for query, forms, etc.
/// </summary>
public class ReadableStringCollection : IReadableStringCollection
{
public static readonly IReadableStringCollection Empty = new ReadableStringCollection(new Dictionary<string, StringValues>(0));
/// <summary>
/// Create a new wrapper
/// </summary>
/// <param name="store"></param>
public ReadableStringCollection(IDictionary<string, StringValues> store)
{
if (store == null)
{
throw new ArgumentNullException(nameof(store));
}
Store = store;
}
private IDictionary<string, StringValues> Store { get; set; }
/// <summary>
/// Gets the number of elements contained in the collection.
/// </summary>
public int Count
{
get { return Store.Count; }
}
/// <summary>
/// Gets a collection containing the keys.
/// </summary>
public ICollection<string> Keys
{
get { return Store.Keys; }
}
/// <summary>
/// Get the associated value from the collection. Multiple values will be merged.
/// Returns StringValues.Empty if the key is not present.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public StringValues this[string key]
{
get
{
StringValues value;
if (Store.TryGetValue(key, out value))
{
return value;
}
return StringValues.Empty;
}
}
/// <summary>
/// Determines whether the collection contains an element with the specified key.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool ContainsKey(string key)
{
return Store.ContainsKey(key);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator()
{
return Store.GetEnumerator();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -61,7 +61,7 @@ namespace Microsoft.AspNet.Http.Internal
ThrowIfDisposed();
if (value < 0 || value > Length)
{
throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be within the length of the Stream: " + Length);
throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be within the length of the Stream: " + Length.ToString());
}
VerifyPosition();
_position = value;

View File

@ -0,0 +1,235 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Http.Internal
{
public class RequestCookieCollection : IRequestCookieCollection
{
public static readonly RequestCookieCollection Empty = new RequestCookieCollection();
#if DNXCORE50
private static readonly string[] EmptyKeys = Array.Empty<string>();
#else
private static readonly string[] EmptyKeys = new string[0];
#endif
private static readonly Enumerator EmptyEnumerator = new Enumerator();
// Pre-box
private static readonly IEnumerator<KeyValuePair<string, string>> EmptyIEnumeratorType = EmptyEnumerator;
private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;
private Dictionary<string, string> Store { get; set; }
public RequestCookieCollection()
{
}
public RequestCookieCollection(Dictionary<string, string> store)
{
Store = store;
}
public RequestCookieCollection(int capacity)
{
Store = new Dictionary<string, string>(capacity, StringComparer.OrdinalIgnoreCase);
}
public string this[string key]
{
get
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (Store == null)
{
return string.Empty;
}
string value;
if (TryGetValue(key, out value))
{
return value;
}
return string.Empty;
}
}
public static RequestCookieCollection Parse(IList<string> values)
{
if (values.Count == 0)
{
return Empty;
}
IList<CookieHeaderValue> cookies;
if (CookieHeaderValue.TryParseList(values, out cookies))
{
if (cookies.Count == 0)
{
return Empty;
}
var store = new Dictionary<string, string>(cookies.Count);
for (var i = 0; i < cookies.Count; i++)
{
var cookie = cookies[i];
var name = Uri.UnescapeDataString(cookie.Name.Replace('+', ' '));
var value = Uri.UnescapeDataString(cookie.Value.Replace('+', ' '));
store[name] = value;
}
return new RequestCookieCollection(store);
}
return Empty;
}
public int Count
{
get
{
if (Store == null)
{
return 0;
}
return Store.Count;
}
}
public ICollection<string> Keys
{
get
{
if (Store == null)
{
return EmptyKeys;
}
return Store.Keys;
}
}
public bool ContainsKey(string key)
{
if (Store == null)
{
return false;
}
return Store.ContainsKey(key);
}
public bool TryGetValue(string key, out string value)
{
if (Store == null)
{
value = string.Empty;
return false;
}
return Store.TryGetValue(key, out value);
}
/// <summary>
/// Returns an struct enumerator that iterates through a collection without boxing.
/// </summary>
/// <returns>An <see cref="T:Microsoft.AspNet.Http.Internal.RequestCookies.Enumerator" /> object that can be used to iterate through the collection.</returns>
public Enumerator GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyEnumerator;
}
// Non-boxed Enumerator
return new Enumerator(Store.GetEnumerator());
}
/// <summary>
/// Returns an enumerator that iterates through a collection, boxes in non-empty path.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyIEnumeratorType;
}
// Boxed Enumerator
return GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection, boxes in non-empty path.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
if (Store == null || Store.Count == 0)
{
// Non-boxed Enumerator
return EmptyIEnumerator;
}
// Boxed Enumerator
return GetEnumerator();
}
public struct Enumerator : IEnumerator<KeyValuePair<string, string>>
{
// Do NOT make this readonly, or MoveNext will not work
private Dictionary<string, string>.Enumerator _dictionaryEnumerator;
private bool _notEmpty;
internal Enumerator(Dictionary<string, string>.Enumerator dictionaryEnumerator)
{
_dictionaryEnumerator = dictionaryEnumerator;
_notEmpty = true;
}
public bool MoveNext()
{
if (_notEmpty)
{
return _dictionaryEnumerator.MoveNext();
}
return false;
}
public KeyValuePair<string, string> Current
{
get
{
if (_notEmpty)
{
var current = _dictionaryEnumerator.Current;
return new KeyValuePair<string, string>(current.Key, current.Value);
}
return default(KeyValuePair<string, string>);
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public void Dispose()
{
}
public void Reset()
{
if (_notEmpty)
{
((IEnumerator)_dictionaryEnumerator).Reset();
}
}
}
}
}

View File

@ -1,105 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Http.Internal
{
public class RequestCookiesCollection : IReadableStringCollection
{
private readonly IDictionary<string, string> _dictionary;
public RequestCookiesCollection()
{
_dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public StringValues this[string key]
{
get { return Get(key); }
}
/// <summary>
/// Gets the number of elements contained in the collection.
/// </summary>
public int Count
{
get { return _dictionary.Count; }
}
/// <summary>
/// Gets a collection containing the keys.
/// </summary>
public ICollection<string> Keys
{
get { return _dictionary.Keys; }
}
/// <summary>
/// Determines whether the collection contains an element with the specified key.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool ContainsKey(string key)
{
return _dictionary.ContainsKey(key);
}
/// <summary>
/// Get the associated value from the collection. Multiple values will be merged.
/// Returns null if the key is not present.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string Get(string key)
{
string value;
return _dictionary.TryGetValue(key, out value) ? value : null;
}
/// <summary>
/// Get the associated values from the collection in their original format.
/// Returns null if the key is not present.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public IList<string> GetValues(string key)
{
string value;
return _dictionary.TryGetValue(key, out value) ? new[] { value } : null;
}
public void Reparse(IList<string> values)
{
_dictionary.Clear();
IList<CookieHeaderValue> cookies;
if (CookieHeaderValue.TryParseList(values, out cookies))
{
foreach (var cookie in cookies)
{
var name = Uri.UnescapeDataString(cookie.Name.Replace('+', ' '));
var value = Uri.UnescapeDataString(cookie.Value.Replace('+', ' '));
_dictionary[name] = value;
}
}
}
public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator()
{
foreach (var pair in _dictionary)
{
yield return new KeyValuePair<string, StringValues>(pair.Key, pair.Value);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -2,8 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Text.Encodings.Web;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
@ -80,10 +80,10 @@ namespace Microsoft.AspNet.Http.Internal
/// <param name="key"></param>
public void Delete(string key)
{
var encodedKeyPlusEquals = UrlEncoder.Default.Encode(key) + "=";
Func<string, bool> predicate = value => value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase);
var encodedKeyPlusEquals = $"{UrlEncoder.Default.Encode(key)}=";
Func<string, string, bool> predicate = (value, encKeyPlusEquals) => value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase);
StringValues deleteCookies = encodedKeyPlusEquals + "; expires=Thu, 01-Jan-1970 00:00:00 GMT";
StringValues deleteCookies = $"{encodedKeyPlusEquals}; expires=Thu, 01-Jan-1970 00:00:00 GMT";
var existingValues = Headers[HeaderNames.SetCookie];
if (StringValues.IsNullOrEmpty(existingValues))
{
@ -91,7 +91,24 @@ namespace Microsoft.AspNet.Http.Internal
}
else
{
Headers[HeaderNames.SetCookie] = existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray();
var values = existingValues.ToArray();
var newValues = new List<string>();
for (var i = 0; i < values.Length; i++)
{
if (!predicate(values[i], encodedKeyPlusEquals))
{
newValues.Add(values[i]);
}
}
values = deleteCookies.ToArray();
for (var i = 0; i < values.Length; i++)
{
newValues.Add(values[i]);
}
Headers[HeaderNames.SetCookie] = new StringValues(newValues.ToArray());
}
}
@ -106,33 +123,44 @@ namespace Microsoft.AspNet.Http.Internal
{
throw new ArgumentNullException(nameof(options));
}
var encodedKeyPlusEquals = UrlEncoder.Default.Encode(key) + "=";
var encodedKeyPlusEquals = $"{UrlEncoder.Default.Encode(key)}=";
bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
bool pathHasValue = !string.IsNullOrEmpty(options.Path);
Func<string, bool> rejectPredicate;
Func<string, string, CookieOptions, bool> rejectPredicate;
if (domainHasValue)
{
rejectPredicate = value =>
value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase) &&
value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1;
rejectPredicate = (value, encKeyPlusEquals, opts) =>
value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) &&
value.IndexOf($"domain={opts.Domain}", StringComparison.OrdinalIgnoreCase) != -1;
}
else if (pathHasValue)
{
rejectPredicate = value =>
value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase) &&
value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1;
rejectPredicate = (value, encKeyPlusEquals, opts) =>
value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) &&
value.IndexOf($"path={opts.Path}", StringComparison.OrdinalIgnoreCase) != -1;
}
else
{
rejectPredicate = value => value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase);
rejectPredicate = (value, encKeyPlusEquals, opts) => value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase);
}
var existingValues = Headers[HeaderNames.SetCookie];
if (!StringValues.IsNullOrEmpty(existingValues))
{
Headers[HeaderNames.SetCookie] = existingValues.Where(value => !rejectPredicate(value)).ToArray();
var values = existingValues.ToArray();
var newValues = new List<string>();
for (var i = 0; i < values.Length; i++)
{
if (!rejectPredicate(values[i], encodedKeyPlusEquals, options))
{
newValues.Add(values[i]);
}
}
Headers[HeaderNames.SetCookie] = new StringValues(newValues.ToArray());
}
Append(key, string.Empty, new CookieOptions

View File

@ -262,7 +262,7 @@ namespace Microsoft.AspNet.WebUtilities
{
if (minCount > _buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length);
throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length.ToString());
}
while (_bufferCount < minCount)
{
@ -289,7 +289,7 @@ namespace Microsoft.AspNet.WebUtilities
{
if (minCount > _buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length);
throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length.ToString());
}
while (_bufferCount < minCount)
{
@ -315,38 +315,42 @@ namespace Microsoft.AspNet.WebUtilities
public string ReadLine(int lengthLimit)
{
CheckDisposed();
var builder = new MemoryStream(200);
bool foundCR = false, foundCRLF = false;
while (!foundCRLF && EnsureBuffered())
using (var builder = new MemoryStream(200))
{
if (builder.Length > lengthLimit)
{
throw new InvalidOperationException("Line length limit exceeded: " + lengthLimit);
}
ProcessLineChar(builder, ref foundCR, ref foundCRLF);
}
bool foundCR = false, foundCRLF = false;
return DecodeLine(builder, foundCRLF);
while (!foundCRLF && EnsureBuffered())
{
if (builder.Length > lengthLimit)
{
throw new InvalidOperationException("Line length limit exceeded: " + lengthLimit.ToString());
}
ProcessLineChar(builder, ref foundCR, ref foundCRLF);
}
return DecodeLine(builder, foundCRLF);
}
}
public async Task<string> ReadLineAsync(int lengthLimit, CancellationToken cancellationToken)
{
CheckDisposed();
var builder = new MemoryStream(200);
bool foundCR = false, foundCRLF = false;
while (!foundCRLF && await EnsureBufferedAsync(cancellationToken))
using (var builder = new MemoryStream(200))
{
if (builder.Length > lengthLimit)
bool foundCR = false, foundCRLF = false;
while (!foundCRLF && await EnsureBufferedAsync(cancellationToken))
{
throw new InvalidOperationException("Line length limit exceeded: " + lengthLimit);
if (builder.Length > lengthLimit)
{
throw new InvalidOperationException("Line length limit exceeded: " + lengthLimit.ToString());
}
ProcessLineChar(builder, ref foundCR, ref foundCRLF);
}
ProcessLineChar(builder, ref foundCR, ref foundCRLF);
return DecodeLine(builder, foundCRLF);
}
return DecodeLine(builder, foundCRLF);
}
private void ProcessLineChar(MemoryStream builder, ref bool foundCR, ref bool foundCRLF)

View File

@ -185,7 +185,7 @@ namespace Microsoft.AspNet.WebUtilities
/// </summary>
/// <param name="stream">The HTTP form body to parse.</param>
/// <returns>The collection containing the parsed HTTP form body.</returns>
public static Task<IDictionary<string, StringValues>> ReadFormAsync(Stream stream, CancellationToken cancellationToken = new CancellationToken())
public static Task<Dictionary<string, StringValues>> ReadFormAsync(Stream stream, CancellationToken cancellationToken = new CancellationToken())
{
return ReadFormAsync(stream, Encoding.UTF8, cancellationToken);
}
@ -195,7 +195,7 @@ namespace Microsoft.AspNet.WebUtilities
/// </summary>
/// <param name="stream">The HTTP form body to parse.</param>
/// <returns>The collection containing the parsed HTTP form body.</returns>
public static async Task<IDictionary<string, StringValues>> ReadFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = new CancellationToken())
public static async Task<Dictionary<string, StringValues>> ReadFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = new CancellationToken())
{
var reader = new FormReader(stream, encoding);

View File

@ -7,17 +7,16 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.WebUtilities
{
public class KeyValueAccumulator
public struct KeyValueAccumulator
{
private Dictionary<string, List<string>> _accumulator;
public KeyValueAccumulator()
{
_accumulator = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
}
public void Append(string key, string value)
{
if (_accumulator == null)
{
_accumulator = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
}
List<string> values;
if (_accumulator.TryGetValue(key, out values))
{
@ -25,18 +24,29 @@ namespace Microsoft.AspNet.WebUtilities
}
else
{
_accumulator[key] = new List<string>(1) { value };
values = new List<string>(1);
values.Add(value);
_accumulator[key] = values;
}
}
public IDictionary<string, StringValues> GetResults()
public bool HasValues => _accumulator != null;
public Dictionary<string, StringValues> GetResults()
{
var results = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
if (_accumulator == null)
{
return new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
}
var results = new Dictionary<string, StringValues>(_accumulator.Count, StringComparer.OrdinalIgnoreCase);
foreach (var kv in _accumulator)
{
results.Add(kv.Key, kv.Value.ToArray());
results.Add(kv.Key, kv.Value.Count == 1 ? new StringValues(kv.Value[0]) : new StringValues(kv.Value.ToArray()));
}
return results;
}
}
}
}

View File

@ -74,7 +74,7 @@ namespace Microsoft.AspNet.WebUtilities
return new MultipartSection() { Headers = headers, Body = _currentStream, BaseStreamOffset = baseStreamOffset };
}
private async Task<IDictionary<string, StringValues>> ReadHeadersAsync(CancellationToken cancellationToken)
private async Task<Dictionary<string, StringValues>> ReadHeadersAsync(CancellationToken cancellationToken)
{
int totalSize = 0;
var accumulator = new KeyValueAccumulator();
@ -84,10 +84,10 @@ namespace Microsoft.AspNet.WebUtilities
totalSize += line.Length;
if (totalSize > TotalHeaderSizeLimit)
{
throw new InvalidOperationException("Total header size limit exceeded: " + TotalHeaderSizeLimit);
throw new InvalidOperationException("Total header size limit exceeded: " + TotalHeaderSizeLimit.ToString());
}
int splitIndex = line.IndexOf(':');
Debug.Assert(splitIndex > 0, "Invalid header line: " + line);
Debug.Assert(splitIndex > 0, $"Invalid header line: {line.ToString()}");
if (splitIndex >= 0)
{
var name = line.Substring(0, splitIndex);

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNet.WebUtilities
StringValues values;
if (Headers.TryGetValue("Content-Type", out values))
{
return values;
return values.ToString();
}
return null;
}
@ -29,13 +29,13 @@ namespace Microsoft.AspNet.WebUtilities
StringValues values;
if (Headers.TryGetValue("Content-Disposition", out values))
{
return values;
return values.ToString();
}
return null;
}
}
public IDictionary<string, StringValues> Headers { get; set; }
public Dictionary<string, StringValues> Headers { get; set; }
public Stream Body { get; set; }

View File

@ -107,13 +107,39 @@ namespace Microsoft.AspNet.WebUtilities
/// </summary>
/// <param name="text">The raw query string value, with or without the leading '?'.</param>
/// <returns>A collection of parsed keys and values.</returns>
public static IDictionary<string, StringValues> ParseQuery(string queryString)
public static Dictionary<string, StringValues> ParseQuery(string queryString)
{
var result = ParseNullableQuery(queryString);
if (result == null)
{
return new Dictionary<string, StringValues>();
}
return result;
}
/// <summary>
/// Parse a query string into its component key and value parts.
/// </summary>
/// <param name="text">The raw query string value, with or without the leading '?'.</param>
/// <returns>A collection of parsed keys and values, null if there are no entries.</returns>
public static Dictionary<string, StringValues> ParseNullableQuery(string queryString)
{
var accumulator = new KeyValueAccumulator();
if (string.IsNullOrEmpty(queryString) || queryString == "?")
{
return null;
}
int scanIndex = 0;
if (!string.IsNullOrEmpty(queryString) && queryString[0] == '?')
{
queryString = queryString.Substring(1);
scanIndex = 1;
}
var accumulator = new KeyValueAccumulator();
int textLength = queryString.Length;
int equalIndex = queryString.IndexOf('=');
@ -121,7 +147,6 @@ namespace Microsoft.AspNet.WebUtilities
{
equalIndex = textLength;
}
int scanIndex = 0;
while (scanIndex < textLength)
{
int delimiterIndex = queryString.IndexOf('&', scanIndex);
@ -149,6 +174,11 @@ namespace Microsoft.AspNet.WebUtilities
scanIndex = delimiterIndex + 1;
}
if (!accumulator.HasValues)
{
return null;
}
return accumulator.GetResults();
}
}

View File

@ -150,7 +150,7 @@ namespace Microsoft.AspNet.Http.Internal
Assert.Equal("value0", query1["name0"]);
Assert.Equal("value1", query1["name1"]);
var query2 = new ReadableStringCollection(new Dictionary<string, StringValues>()
var query2 = new QueryCollection( new Dictionary<string, StringValues>()
{
{ "name2", "value2" }
});
@ -170,19 +170,23 @@ namespace Microsoft.AspNet.Http.Internal
var cookies0 = request.Cookies;
Assert.Equal(0, cookies0.Count);
request.Headers["Cookie"] = new[] { "name0=value0", "name1=value1" };
var newCookies = new[] { "name0=value0", "name1=value1" };
request.Headers["Cookie"] = newCookies;
cookies0 = RequestCookieCollection.Parse(newCookies);
var cookies1 = request.Cookies;
Assert.Same(cookies0, cookies1);
Assert.Equal(cookies0, cookies1);
Assert.Equal(2, cookies1.Count);
Assert.Equal("value0", cookies1["name0"]);
Assert.Equal("value1", cookies1["name1"]);
Assert.Equal(newCookies, request.Headers["Cookie"]);
var cookies2 = new ReadableStringCollection(new Dictionary<string, StringValues>()
var cookies2 = new RequestCookieCollection(new Dictionary<string,string>()
{
{ "name2", "value2" }
});
request.Cookies = cookies2;
Assert.Same(cookies2, request.Cookies);
Assert.Equal(cookies2, request.Cookies);
Assert.Equal("value2", request.Cookies["name2"]);
cookieHeaders = request.Headers["Cookie"];
Assert.Equal(new[] { "name2=value2" }, cookieHeaders);