#426 Less alloc/wrapping/boxing for Query, Forms, Cookies
This commit is contained in:
parent
af0d2e5888
commit
3c2e2b9d98
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ namespace Microsoft.AspNet.Http.Internal
|
|||
{
|
||||
get
|
||||
{
|
||||
return Headers[HeaderNames.ContentType];
|
||||
return Headers[HeaderNames.ContentType].ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Http.Features.Internal
|
|||
{
|
||||
public interface IQueryFeature
|
||||
{
|
||||
IReadableStringCollection Query { get; set; }
|
||||
IQueryCollection Query { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Http.Features.Internal
|
|||
{
|
||||
public interface IRequestCookiesFeature
|
||||
{
|
||||
IReadableStringCollection Cookies { get; set; }
|
||||
IRequestCookieCollection Cookies { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue