diff --git a/HttpAbstractions.sln b/HttpAbstractions.sln
index a2737cba10..d0bb133101 100644
--- a/HttpAbstractions.sln
+++ b/HttpAbstractions.sln
@@ -21,11 +21,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.PipelineCo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.PipelineCore.k10", "src\Microsoft.AspNet.PipelineCore\Microsoft.AspNet.PipelineCore.k10.csproj", "{E31CF247-FDA9-4007-B194-A7DBAC18532C}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D38DDB2B-1138-4F45-8A6A-9499E880F620}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1D737C82-F2F1-40B6-AE95-A3D878612E91}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.PipelineCore.Tests", "test\Microsoft.AspNet.PipelineCore.Tests\Microsoft.AspNet.PipelineCore.Tests.csproj", "{86942914-0334-4352-87ED-B971281C74E2}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.FeatureModel.Tests", "test\Microsoft.AspNet.FeatureModel.Tests\Microsoft.AspNet.FeatureModel.Tests.csproj", "{8C671AE3-1188-499F-A2A2-3A0B117B33CE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Abstractions.Owin.net45", "src\Microsoft.AspNet.Abstractions.Owin\Microsoft.AspNet.Abstractions.Owin.net45.csproj", "{E6B7056F-547E-4B96-9E58-858DDDAE75D3}"
EndProject
diff --git a/src/Microsoft.AspNet.Abstractions/CookieOptions.cs b/src/Microsoft.AspNet.Abstractions/CookieOptions.cs
new file mode 100644
index 0000000000..c8505cae1b
--- /dev/null
+++ b/src/Microsoft.AspNet.Abstractions/CookieOptions.cs
@@ -0,0 +1,48 @@
+using System;
+
+namespace Microsoft.AspNet.Abstractions
+{
+ ///
+ /// Options used to create a new cookie.
+ ///
+ public class CookieOptions
+ {
+ ///
+ /// Creates a default cookie with a path of '/'.
+ ///
+ public CookieOptions()
+ {
+ Path = "/";
+ }
+
+ ///
+ /// Gets or sets the domain to associate the cookie with.
+ ///
+ /// The domain to associate the cookie with.
+ public string Domain { get; set; }
+
+ ///
+ /// Gets or sets the cookie path.
+ ///
+ /// The cookie path.
+ public string Path { get; set; }
+
+ ///
+ /// Gets or sets the expiration date and time for the cookie.
+ ///
+ /// The expiration date and time for the cookie.
+ public DateTime? Expires { get; set; }
+
+ ///
+ /// Gets or sets a value that indicates whether to transmit the cookie using Secure Sockets Layer (SSL)—that is, over HTTPS only.
+ ///
+ /// true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false.
+ public bool Secure { get; set; }
+
+ ///
+ /// Gets or sets a value that indicates whether a cookie is accessible by client-side script.
+ ///
+ /// true if a cookie is accessible by client-side script; otherwise, false.
+ public bool HttpOnly { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.Abstractions/HostString.cs b/src/Microsoft.AspNet.Abstractions/HostString.cs
new file mode 100644
index 0000000000..0847c7421e
--- /dev/null
+++ b/src/Microsoft.AspNet.Abstractions/HostString.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+
+namespace Microsoft.AspNet.Abstractions
+{
+ ///
+ /// Represents the host portion of a Uri can be used to construct Uri's properly formatted and encoded for use in
+ /// HTTP headers.
+ ///
+ public struct HostString : IEquatable
+ {
+ private readonly string _value;
+
+ ///
+ /// Creates a new HostString without modification. The value should be Unicode rather than punycode, and may have a port.
+ /// IPv4 and IPv6 addresses are also allowed, and also may have ports.
+ ///
+ ///
+ public HostString(string value)
+ {
+ _value = value;
+ }
+
+ ///
+ /// Returns the original value from the constructor.
+ ///
+ public string Value
+ {
+ get { return _value; }
+ }
+
+ ///
+ /// Returns the value as normalized by ToUriComponent().
+ ///
+ ///
+ public override string ToString()
+ {
+ return ToUriComponent();
+ }
+
+ ///
+ /// Returns the value properly formatted and encoded for use in a URI in a HTTP header.
+ /// Any Unicode is converted to punycode. IPv6 addresses will have brackets added if they are missing.
+ ///
+ ///
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Only the host segment of a uri is returned.")]
+ public string ToUriComponent()
+ {
+ int index;
+ if (string.IsNullOrEmpty(_value))
+ {
+ return string.Empty;
+ }
+ else if (_value.IndexOf('[') >= 0)
+ {
+ // IPv6 in brackets [::1], maybe with port
+ return _value;
+ }
+ else if ((index = _value.IndexOf(':')) >= 0
+ && index < _value.Length - 1
+ && _value.IndexOf(':', index + 1) >= 0)
+ {
+ // IPv6 without brackets ::1 is the only type of host with 2 or more colons
+ return "[" + _value + "]";
+ }
+ else if (index >= 0)
+ {
+ // Has a port
+ string port = _value.Substring(index);
+ IdnMapping mapping = new IdnMapping();
+ return mapping.GetAscii(_value, 0, index) + port;
+ }
+ else
+ {
+ IdnMapping mapping = new IdnMapping();
+ return mapping.GetAscii(_value);
+ }
+ }
+
+ ///
+ /// Creates a new HostString from the given uri component.
+ /// Any punycode will be converted to Unicode.
+ ///
+ ///
+ ///
+ [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Only the host segment of a uri is provided.")]
+ public static HostString FromUriComponent(string uriComponent)
+ {
+ if (!string.IsNullOrEmpty(uriComponent))
+ {
+ int index;
+ if (uriComponent.IndexOf('[') >= 0)
+ {
+ // IPv6 in brackets [::1], maybe with port
+ }
+ else if ((index = uriComponent.IndexOf(':')) >= 0
+ && index < uriComponent.Length - 1
+ && uriComponent.IndexOf(':', index + 1) >= 0)
+ {
+ // IPv6 without brackets ::1 is the only type of host with 2 or more colons
+ }
+ else if (uriComponent.IndexOf("xn--", StringComparison.Ordinal) >= 0)
+ {
+ // Contains punycode
+ if (index >= 0)
+ {
+ // Has a port
+ string port = uriComponent.Substring(index);
+ IdnMapping mapping = new IdnMapping();
+ uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port;
+ }
+ else
+ {
+ IdnMapping mapping = new IdnMapping();
+ uriComponent = mapping.GetUnicode(uriComponent);
+ }
+ }
+ }
+ return new HostString(uriComponent);
+ }
+
+ ///
+ /// Creates a new HostString from the host and port of the give Uri instance.
+ /// Punycode will be converted to Unicode.
+ ///
+ ///
+ ///
+ public static HostString FromUriComponent(Uri uri)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException("uri");
+ }
+ return new HostString(uri.GetComponents(
+#if !NET40
+ UriComponents.NormalizedHost | // Always convert punycode to Unicode.
+#endif
+ UriComponents.HostAndPort, UriFormat.Unescaped));
+ }
+
+ ///
+ /// Compares the equality of the Value property, ignoring case.
+ ///
+ ///
+ ///
+ public bool Equals(HostString other)
+ {
+ return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Compares against the given object only if it is a HostString.
+ ///
+ ///
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+ return obj is HostString && Equals((HostString)obj);
+ }
+
+ ///
+ /// Gets a hash code for the value.
+ ///
+ ///
+ public override int GetHashCode()
+ {
+ return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0);
+ }
+
+ ///
+ /// Compares the two instances for equality.
+ ///
+ ///
+ ///
+ ///
+ public static bool operator ==(HostString left, HostString right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares the two instances for inequality.
+ ///
+ ///
+ ///
+ ///
+ public static bool operator !=(HostString left, HostString right)
+ {
+ return !left.Equals(right);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Abstractions/HttpRequest.cs b/src/Microsoft.AspNet.Abstractions/HttpRequest.cs
index 7277195cc7..40a7860af5 100644
--- a/src/Microsoft.AspNet.Abstractions/HttpRequest.cs
+++ b/src/Microsoft.AspNet.Abstractions/HttpRequest.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Threading;
namespace Microsoft.AspNet.Abstractions
{
@@ -9,10 +10,113 @@ namespace Microsoft.AspNet.Abstractions
public abstract HttpContext HttpContext { get; }
- public abstract Uri Uri { get; }
+ ///
+ /// Gets or set the HTTP method.
+ ///
+ /// The HTTP method.
+ public abstract string Method { get; set; }
+
+ ///
+ /// Gets or set the HTTP request scheme from owin.RequestScheme.
+ ///
+ /// The HTTP request scheme from owin.RequestScheme.
+ public abstract string Scheme { get; set; }
+
+ ///
+ /// Returns true if the owin.RequestScheme is https.
+ ///
+ /// true if this request is using https; otherwise, false.
+ public abstract bool IsSecure { get; }
+
+ ///
+ /// Gets or set the Host header. May include the port.
+ ///
+ /// The Host header.
+ public abstract HostString Host { get; set; }
+
+ ///
+ /// Gets or set the owin.RequestPathBase.
+ ///
+ /// The owin.RequestPathBase.
public abstract PathString PathBase { get; set; }
+
+ ///
+ /// Gets or set the request path from owin.RequestPath.
+ ///
+ /// The request path from owin.RequestPath.
public abstract PathString Path { get; set; }
+
+ ///
+ /// Gets or set the query string from owin.RequestQueryString.
+ ///
+ /// The query string from owin.RequestQueryString.
public abstract QueryString QueryString { get; set; }
+
+ ///
+ /// Gets the query value collection parsed from owin.RequestQueryString.
+ ///
+ /// The query value collection parsed from owin.RequestQueryString.
+ public abstract IReadableStringCollection Query { get; }
+
+ ///
+ /// Gets the uniform resource identifier (URI) associated with the request.
+ ///
+ /// The uniform resource identifier (URI) associated with the request.
+ public abstract Uri Uri { get; }
+
+ ///
+ /// Gets or set the owin.RequestProtocol.
+ ///
+ /// The owin.RequestProtocol.
+ public abstract string Protocol { get; set; }
+
+ ///
+ /// Gets the request headers.
+ ///
+ /// The request headers.
+ public abstract IHeaderDictionary Headers { get; }
+
+ ///
+ /// Gets the collection of Cookies for this request.
+ ///
+ /// The collection of Cookies for this request.
+ public abstract IReadableStringCollection Cookies { get; }
+
+ ///
+ /// Gets or sets the Content-Type header.
+ ///
+ /// The Content-Type header.
+ // (TODO header conventions?) public abstract string ContentType { get; set; }
+
+ ///
+ /// Gets or sets the Cache-Control header.
+ ///
+ /// The Cache-Control header.
+ // (TODO header conventions?) public abstract string CacheControl { get; set; }
+
+ ///
+ /// Gets or sets the Media-Type header.
+ ///
+ /// The Media-Type header.
+ // (TODO header conventions?) public abstract string MediaType { get; set; }
+
+ ///
+ /// Gets or set the Accept header.
+ ///
+ /// The Accept header.
+ // (TODO header conventions?) public abstract string Accept { get; set; }
+
+ ///
+ /// Gets or set the owin.RequestBody Stream.
+ ///
+ /// The owin.RequestBody Stream.
public abstract Stream Body { get; set; }
+
+ ///
+ /// Gets or sets the cancellation token for the request.
+ ///
+ /// The cancellation token for the request.
+ public abstract CancellationToken CallCanceled { get; set; }
+
}
}
diff --git a/src/Microsoft.AspNet.Abstractions/IFormCollection.cs b/src/Microsoft.AspNet.Abstractions/IFormCollection.cs
new file mode 100644
index 0000000000..56862e6be6
--- /dev/null
+++ b/src/Microsoft.AspNet.Abstractions/IFormCollection.cs
@@ -0,0 +1,9 @@
+namespace Microsoft.AspNet.Abstractions
+{
+ ///
+ /// Contains the parsed form values.
+ ///
+ public interface IFormCollection : IReadableStringCollection
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.Abstractions/IHeaderDictionary.cs b/src/Microsoft.AspNet.Abstractions/IHeaderDictionary.cs
new file mode 100644
index 0000000000..7039b8b87e
--- /dev/null
+++ b/src/Microsoft.AspNet.Abstractions/IHeaderDictionary.cs
@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.AspNet.Abstractions
+{
+ ///
+ /// Represents request and response headers
+ ///
+ public interface IHeaderDictionary : IReadableStringCollection, IDictionary
+ {
+ ///
+ /// Get or sets the associated value from the collection as a single string.
+ ///
+ /// The header name.
+ /// the associated value from the collection as a single string or null if the key is not present.
+ new string this[string key] { get; set; }
+
+ ///
+ /// Get the associated values from the collection separated into individual values.
+ /// Quoted values will not be split, and the quotes will be removed.
+ ///
+ /// The header name.
+ /// the associated values from the collection separated into individual values, or null if the key is not present.
+ IList GetCommaSeparatedValues(string key);
+
+ ///
+ /// Add a new value. Appends to the header if already present
+ ///
+ /// The header name.
+ /// The header value.
+ void Append(string key, string value);
+
+ ///
+ /// Add new values. Each item remains a separate array entry.
+ ///
+ /// The header name.
+ /// The header values.
+ void AppendValues(string key, params string[] values);
+
+ ///
+ /// Quotes any values containing comas, and then coma joins all of the values with any existing values.
+ ///
+ /// The header name.
+ /// The header values.
+ void AppendCommaSeparatedValues(string key, params string[] values);
+
+ ///
+ /// Sets a specific header value.
+ ///
+ /// The header name.
+ /// The header value.
+ [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Set", Justification = "Re-evaluate later.")]
+ void Set(string key, string value);
+
+ ///
+ /// Sets the specified header values without modification.
+ ///
+ /// The header name.
+ /// The header values.
+ void SetValues(string key, params string[] values);
+
+ ///
+ /// Quotes any values containing comas, and then coma joins all of the values.
+ ///
+ /// The header name.
+ /// The header values.
+ void SetCommaSeparatedValues(string key, params string[] values);
+ }
+}
diff --git a/src/Microsoft.AspNet.Abstractions/IReadableStringCollection.cs b/src/Microsoft.AspNet.Abstractions/IReadableStringCollection.cs
new file mode 100644
index 0000000000..dc3f72c769
--- /dev/null
+++ b/src/Microsoft.AspNet.Abstractions/IReadableStringCollection.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.AspNet.Abstractions
+{
+ ///
+ /// Accessors for headers, query, forms, etc.
+ ///
+ public interface IReadableStringCollection : IEnumerable>
+ {
+ ///
+ /// Get the associated value from the collection. Multiple values will be merged.
+ /// Returns null if the key is not present.
+ ///
+ ///
+ ///
+ string this[string key] { get; }
+
+ // Joined
+
+ ///
+ /// Get the associated value from the collection. Multiple values will be merged.
+ /// Returns null if the key is not present.
+ ///
+ ///
+ ///
+ [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get", Justification = "Re-evaluate later.")]
+ string Get(string key);
+
+ // Joined
+
+ ///
+ /// Get the associated values from the collection in their original format.
+ /// Returns null if the key is not present.
+ ///
+ ///
+ ///
+ IList GetValues(string key);
+
+ // Raw
+ }
+}
diff --git a/src/Microsoft.AspNet.Abstractions/PathString.cs b/src/Microsoft.AspNet.Abstractions/PathString.cs
index 8cfab5912d..c497d40bf3 100644
--- a/src/Microsoft.AspNet.Abstractions/PathString.cs
+++ b/src/Microsoft.AspNet.Abstractions/PathString.cs
@@ -1,6 +1,4 @@
-// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
-
-using System;
+using System;
using System.Linq;
namespace Microsoft.AspNet.Abstractions
diff --git a/src/Microsoft.AspNet.Abstractions/QueryString.cs b/src/Microsoft.AspNet.Abstractions/QueryString.cs
index e80f5eedcf..9d96419788 100644
--- a/src/Microsoft.AspNet.Abstractions/QueryString.cs
+++ b/src/Microsoft.AspNet.Abstractions/QueryString.cs
@@ -1,6 +1,4 @@
-// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
-
-using System;
+using System;
namespace Microsoft.AspNet.Abstractions
{
diff --git a/src/Microsoft.AspNet.PipelineCore/Collections/FormCollection.cs b/src/Microsoft.AspNet.PipelineCore/Collections/FormCollection.cs
new file mode 100644
index 0000000000..7054d36350
--- /dev/null
+++ b/src/Microsoft.AspNet.PipelineCore/Collections/FormCollection.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNet.Abstractions;
+using System.Collections.Generic;
+
+namespace Microsoft.AspNet.PipelineCore.Collections
+{
+ ///
+ /// Contains the parsed form values.
+ ///
+ public class FormCollection : ReadableStringCollection, IFormCollection
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The store for the form.
+ public FormCollection(IDictionary store)
+ : base(store)
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.PipelineCore/Collections/HeaderDictionary.cs b/src/Microsoft.AspNet.PipelineCore/Collections/HeaderDictionary.cs
new file mode 100644
index 0000000000..8847fa66fc
--- /dev/null
+++ b/src/Microsoft.AspNet.PipelineCore/Collections/HeaderDictionary.cs
@@ -0,0 +1,287 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNet.Abstractions.Infrastructure;
+using Microsoft.AspNet.Abstractions;
+using Microsoft.AspNet.PipelineCore.Infrastructure;
+
+namespace Microsoft.AspNet.PipelineCore.Collections
+{
+ ///
+ /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders.
+ ///
+ public class HeaderDictionary : IHeaderDictionary
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The underlying data store.
+ public HeaderDictionary(IDictionary store)
+ {
+ if (store == null)
+ {
+ throw new ArgumentNullException("store");
+ }
+
+ Store = store;
+ }
+
+ private IDictionary Store { get; set; }
+
+ ///
+ /// Gets an that contains the keys in the ;.
+ ///
+ /// An that contains the keys in the .
+ public ICollection Keys
+ {
+ get { return Store.Keys; }
+ }
+
+ ///
+ ///
+ ///
+ public ICollection Values
+ {
+ get { return Store.Values; }
+ }
+
+ ///
+ /// Gets the number of elements contained in the ;.
+ ///
+ /// The number of elements contained in the .
+ public int Count
+ {
+ get { return Store.Count; }
+ }
+
+ ///
+ /// Gets a value that indicates whether the is in read-only mode.
+ ///
+ /// true if the is in read-only mode; otherwise, false.
+ public bool IsReadOnly
+ {
+ get { return Store.IsReadOnly; }
+ }
+
+ ///
+ /// Get or sets the associated value from the collection as a single string.
+ ///
+ /// The header name.
+ /// the associated value from the collection as a single string or null if the key is not present.
+ public string this[string key]
+ {
+ get { return Get(key); }
+ set { Set(key, value); }
+ }
+
+ ///
+ /// Throws KeyNotFoundException if the key is not present.
+ ///
+ /// The header name.
+ ///
+ string[] IDictionary.this[string key]
+ {
+ get { return Store[key]; }
+ set { Store[key] = value; }
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ /// An object that can be used to iterate through the collection.
+ public IEnumerator> GetEnumerator()
+ {
+ return Store.GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ /// An object that can be used to iterate through the collection.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// Get the associated value from the collection as a single string.
+ ///
+ /// The header name.
+ /// the associated value from the collection as a single string or null if the key is not present.
+ public string Get(string key)
+ {
+ return ParsingHelpers.GetHeader(Store, key);
+ }
+
+ ///
+ /// Get the associated values from the collection without modification.
+ ///
+ /// The header name.
+ /// the associated value from the collection without modification, or null if the key is not present.
+ public IList GetValues(string key)
+ {
+ return ParsingHelpers.GetHeaderUnmodified(Store, key);
+ }
+
+ ///
+ /// Get the associated values from the collection separated into individual values.
+ /// Quoted values will not be split, and the quotes will be removed.
+ ///
+ /// The header name.
+ /// the associated values from the collection separated into individual values, or null if the key is not present.
+ public IList GetCommaSeparatedValues(string key)
+ {
+ IEnumerable values = ParsingHelpers.GetHeaderSplit(Store, key);
+ return values == null ? null : values.ToList();
+ }
+
+ ///
+ /// Add a new value. Appends to the header if already present
+ ///
+ /// The header name.
+ /// The header value.
+ public void Append(string key, string value)
+ {
+ ParsingHelpers.AppendHeader(Store, key, value);
+ }
+
+ ///
+ /// Add new values. Each item remains a separate array entry.
+ ///
+ /// The header name.
+ /// The header values.
+ public void AppendValues(string key, params string[] values)
+ {
+ ParsingHelpers.AppendHeaderUnmodified(Store, key, values);
+ }
+
+ ///
+ /// Quotes any values containing comas, and then coma joins all of the values with any existing values.
+ ///
+ /// The header name.
+ /// The header values.
+ public void AppendCommaSeparatedValues(string key, params string[] values)
+ {
+ ParsingHelpers.AppendHeaderJoined(Store, key, values);
+ }
+
+ ///
+ /// Sets a specific header value.
+ ///
+ /// The header name.
+ /// The header value.
+ public void Set(string key, string value)
+ {
+ ParsingHelpers.SetHeader(Store, key, value);
+ }
+
+ ///
+ /// Sets the specified header values without modification.
+ ///
+ /// The header name.
+ /// The header values.
+ public void SetValues(string key, params string[] values)
+ {
+ ParsingHelpers.SetHeaderUnmodified(Store, key, values);
+ }
+
+ ///
+ /// Quotes any values containing comas, and then coma joins all of the values.
+ ///
+ /// The header name.
+ /// The header values.
+ public void SetCommaSeparatedValues(string key, params string[] values)
+ {
+ ParsingHelpers.SetHeaderJoined(Store, key, values);
+ }
+
+ ///
+ /// Adds the given header and values to the collection.
+ ///
+ /// The header name.
+ /// The header values.
+ public void Add(string key, string[] value)
+ {
+ Store.Add(key, value);
+ }
+
+ ///
+ /// Determines whether the contains a specific key.
+ ///
+ /// The key.
+ /// true if the contains a specific key; otherwise, false.
+ public bool ContainsKey(string key)
+ {
+ return Store.ContainsKey(key);
+ }
+
+ ///
+ /// Removes the given header from the collection.
+ ///
+ /// The header name.
+ /// true if the specified object was removed from the collection; otherwise, false.
+ public bool Remove(string key)
+ {
+ return Store.Remove(key);
+ }
+
+ ///
+ /// Retrieves a value from the dictionary.
+ ///
+ /// The header name.
+ /// The value.
+ /// true if the contains the key; otherwise, false.
+ public bool TryGetValue(string key, out string[] value)
+ {
+ return Store.TryGetValue(key, out value);
+ }
+
+ ///
+ /// Adds a new list of items to the collection.
+ ///
+ /// The item to add.
+ public void Add(KeyValuePair item)
+ {
+ Store.Add(item);
+ }
+
+ ///
+ /// Clears the entire list of objects.
+ ///
+ public void Clear()
+ {
+ Store.Clear();
+ }
+
+ ///
+ /// Returns a value indicating whether the specified object occurs within this collection.
+ ///
+ /// The item.
+ /// true if the specified object occurs within this collection; otherwise, false.
+ public bool Contains(KeyValuePair item)
+ {
+ return Store.Contains(item);
+ }
+
+ ///
+ /// Copies the elements to a one-dimensional Array instance at the specified index.
+ ///
+ /// The one-dimensional Array that is the destination of the specified objects copied from the .
+ /// The zero-based index in at which copying begins.
+ public void CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ Store.CopyTo(array, arrayIndex);
+ }
+
+ ///
+ /// Removes the given item from the the collection.
+ ///
+ /// The item.
+ /// true if the specified object was removed from the collection; otherwise, false.
+ public bool Remove(KeyValuePair item)
+ {
+ return Store.Remove(item);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.PipelineCore/Collections/ReadableStringCollection.cs b/src/Microsoft.AspNet.PipelineCore/Collections/ReadableStringCollection.cs
new file mode 100644
index 0000000000..f06a8aa5a0
--- /dev/null
+++ b/src/Microsoft.AspNet.PipelineCore/Collections/ReadableStringCollection.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.AspNet.Abstractions.Infrastructure;
+using Microsoft.AspNet.Abstractions;
+using Microsoft.AspNet.PipelineCore.Infrastructure;
+
+namespace Microsoft.AspNet.PipelineCore.Collections
+{
+ ///
+ /// Accessors for query, forms, etc.
+ ///
+ public class ReadableStringCollection : IReadableStringCollection
+ {
+ ///
+ /// Create a new wrapper
+ ///
+ ///
+ public ReadableStringCollection(IDictionary store)
+ {
+ if (store == null)
+ {
+ throw new ArgumentNullException("store");
+ }
+
+ Store = store;
+ }
+
+ private IDictionary Store { get; set; }
+
+ ///
+ /// Get the associated value from the collection. Multiple values will be merged.
+ /// Returns null if the key is not present.
+ ///
+ ///
+ ///
+ public string this[string key]
+ {
+ get { return Get(key); }
+ }
+
+ ///
+ /// Get the associated value from the collection. Multiple values will be merged.
+ /// Returns null if the key is not present.
+ ///
+ ///
+ ///
+ public string Get(string key)
+ {
+ return ParsingHelpers.GetJoinedValue(Store, key);
+ }
+
+ ///
+ /// Get the associated values from the collection in their original format.
+ /// Returns null if the key is not present.
+ ///
+ ///
+ ///
+ public IList GetValues(string key)
+ {
+ string[] values;
+ Store.TryGetValue(key, out values);
+ return values;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public IEnumerator> GetEnumerator()
+ {
+ return Store.GetEnumerator();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.PipelineCore/Collections/RequestCookieCollection.cs b/src/Microsoft.AspNet.PipelineCore/Collections/RequestCookieCollection.cs
new file mode 100644
index 0000000000..91b1577f28
--- /dev/null
+++ b/src/Microsoft.AspNet.PipelineCore/Collections/RequestCookieCollection.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.AspNet.PipelineCore.Collections
+{
+ ///
+ /// A wrapper for the request Cookie header
+ ///
+ public class RequestCookieCollection : IEnumerable>
+ {
+ ///
+ /// Create a new wrapper
+ ///
+ ///
+ public RequestCookieCollection(IDictionary store)
+ {
+ if (store == null)
+ {
+ throw new ArgumentNullException("store");
+ }
+
+ Store = store;
+ }
+
+ private IDictionary Store { get; set; }
+
+ ///
+ /// Returns null rather than throwing KeyNotFoundException
+ ///
+ ///
+ ///
+ public string this[string key]
+ {
+ get
+ {
+ string value;
+ Store.TryGetValue(key, out value);
+ return value;
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public IEnumerator> GetEnumerator()
+ {
+ return Store.GetEnumerator();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.PipelineCore/Collections/ResponseCookieCollection.cs b/src/Microsoft.AspNet.PipelineCore/Collections/ResponseCookieCollection.cs
new file mode 100644
index 0000000000..78703285cf
--- /dev/null
+++ b/src/Microsoft.AspNet.PipelineCore/Collections/ResponseCookieCollection.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Microsoft.AspNet.Abstractions.Infrastructure;
+using Microsoft.AspNet.Abstractions;
+
+namespace Microsoft.AspNet.PipelineCore.Collections
+{
+ ///
+ /// A wrapper for the response Set-Cookie header
+ ///
+ public class ResponseCookieCollection
+ {
+ ///
+ /// Create a new wrapper
+ ///
+ ///
+ public ResponseCookieCollection(IHeaderDictionary headers)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException("headers");
+ }
+
+ Headers = headers;
+ }
+
+ private IHeaderDictionary Headers { get; set; }
+
+ ///
+ /// Add a new cookie and value
+ ///
+ ///
+ ///
+ public void Append(string key, string value)
+ {
+ Headers.AppendValues(Constants.Headers.SetCookie, Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value) + "; path=/");
+ }
+
+ ///
+ /// Add a new cookie
+ ///
+ ///
+ ///
+ ///
+ public void Append(string key, string value, CookieOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
+ bool pathHasValue = !string.IsNullOrEmpty(options.Path);
+ bool expiresHasValue = options.Expires.HasValue;
+
+ string setCookieValue = string.Concat(
+ Uri.EscapeDataString(key),
+ "=",
+ Uri.EscapeDataString(value ?? string.Empty),
+ !domainHasValue ? null : "; domain=",
+ !domainHasValue ? null : options.Domain,
+ !pathHasValue ? null : "; path=",
+ !pathHasValue ? null : options.Path,
+ !expiresHasValue ? null : "; expires=",
+ !expiresHasValue ? null : options.Expires.Value.ToString("ddd, dd-MMM-yyyy HH:mm:ss ", CultureInfo.InvariantCulture) + "GMT",
+ !options.Secure ? null : "; secure",
+ !options.HttpOnly ? null : "; HttpOnly");
+ Headers.AppendValues("Set-Cookie", setCookieValue);
+ }
+
+ ///
+ /// Sets an expired cookie
+ ///
+ ///
+ public void Delete(string key)
+ {
+ Func predicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase);
+
+ var deleteCookies = new[] { Uri.EscapeDataString(key) + "=; expires=Thu, 01-Jan-1970 00:00:00 GMT" };
+ IList existingValues = Headers.GetValues(Constants.Headers.SetCookie);
+ if (existingValues == null || existingValues.Count == 0)
+ {
+ Headers.SetValues(Constants.Headers.SetCookie, deleteCookies);
+ }
+ else
+ {
+ Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray());
+ }
+ }
+
+ ///
+ /// Sets an expired cookie
+ ///
+ ///
+ ///
+ public void Delete(string key, CookieOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
+ bool pathHasValue = !string.IsNullOrEmpty(options.Path);
+
+ Func rejectPredicate;
+ if (domainHasValue)
+ {
+ rejectPredicate = value =>
+ value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) &&
+ value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1;
+ }
+ else if (pathHasValue)
+ {
+ rejectPredicate = value =>
+ value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) &&
+ value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1;
+ }
+ else
+ {
+ rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase);
+ }
+
+ IList existingValues = Headers.GetValues(Constants.Headers.SetCookie);
+ if (existingValues != null)
+ {
+ Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !rejectPredicate(value)).ToArray());
+ }
+
+ Append(key, string.Empty, new CookieOptions
+ {
+ Path = options.Path,
+ Domain = options.Domain,
+ Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.PipelineCore/DefaultHttpRequest.cs b/src/Microsoft.AspNet.PipelineCore/DefaultHttpRequest.cs
index 73860a9f32..4a5be161e2 100644
--- a/src/Microsoft.AspNet.PipelineCore/DefaultHttpRequest.cs
+++ b/src/Microsoft.AspNet.PipelineCore/DefaultHttpRequest.cs
@@ -70,5 +70,85 @@ namespace Microsoft.AspNet.PipelineCore
get { return IHttpRequest.Body; }
set { IHttpRequest.Body = value; }
}
+
+ public override string Method
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ set
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public override string Scheme
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ set
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public override bool IsSecure
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override HostString Host
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ set
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public override IReadableStringCollection Query
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override string Protocol
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ set
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public override IHeaderDictionary Headers
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override IReadableStringCollection Cookies
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override System.Threading.CancellationToken CallCanceled
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ set
+ {
+ throw new NotImplementedException();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.PipelineCore/Infrastructure/Constants.cs b/src/Microsoft.AspNet.PipelineCore/Infrastructure/Constants.cs
new file mode 100644
index 0000000000..114ae660e3
--- /dev/null
+++ b/src/Microsoft.AspNet.PipelineCore/Infrastructure/Constants.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.Abstractions.Infrastructure
+{
+ internal static class Constants
+ {
+ internal const string Https = "HTTPS";
+
+ internal const string HttpDateFormat = "r";
+
+ internal static class Headers
+ {
+ internal const string ContentType = "Content-Type";
+ internal const string CacheControl = "Cache-Control";
+ internal const string MediaType = "Media-Type";
+ internal const string Accept = "Accept";
+ internal const string Host = "Host";
+ internal const string ETag = "ETag";
+ internal const string Location = "Location";
+ internal const string ContentLength = "Content-Length";
+ internal const string SetCookie = "Set-Cookie";
+ internal const string Expires = "Expires";
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs b/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs
new file mode 100644
index 0000000000..204b3cda0e
--- /dev/null
+++ b/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs
@@ -0,0 +1,862 @@
+using Microsoft.AspNet.Abstractions;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.AspNet.PipelineCore.Infrastructure
+{
+ internal struct HeaderSegment : IEquatable
+ {
+ private readonly StringSegment _formatting;
+ private readonly StringSegment _data;
+
+ //
+ // Initializes a new instance of the class.
+ //
+ public HeaderSegment(StringSegment formatting, StringSegment data)
+ {
+ _formatting = formatting;
+ _data = data;
+ }
+
+ public StringSegment Formatting
+ {
+ get { return _formatting; }
+ }
+
+ public StringSegment Data
+ {
+ get { return _data; }
+ }
+
+ #region Equality members
+
+ public bool Equals(HeaderSegment other)
+ {
+ return _formatting.Equals(other._formatting) && _data.Equals(other._data);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ return obj is HeaderSegment && Equals((HeaderSegment)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (_formatting.GetHashCode() * 397) ^ _data.GetHashCode();
+ }
+ }
+
+ public static bool operator ==(HeaderSegment left, HeaderSegment right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(HeaderSegment left, HeaderSegment right)
+ {
+ return !left.Equals(right);
+ }
+
+ #endregion
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("App_Packages", "")]
+ internal struct HeaderSegmentCollection : IEnumerable, IEquatable
+ {
+ private readonly string[] _headers;
+
+ public HeaderSegmentCollection(string[] headers)
+ {
+ _headers = headers;
+ }
+
+ #region Equality members
+
+ public bool Equals(HeaderSegmentCollection other)
+ {
+ return Equals(_headers, other._headers);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return (_headers != null ? _headers.GetHashCode() : 0);
+ }
+
+ public static bool operator ==(HeaderSegmentCollection left, HeaderSegmentCollection right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(HeaderSegmentCollection left, HeaderSegmentCollection right)
+ {
+ return !left.Equals(right);
+ }
+
+ #endregion
+
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(_headers);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ internal struct Enumerator : IEnumerator
+ {
+ private readonly string[] _headers;
+ private int _index;
+
+ private string _header;
+ private int _headerLength;
+ private int _offset;
+
+ private int _leadingStart;
+ private int _leadingEnd;
+ private int _valueStart;
+ private int _valueEnd;
+ private int _trailingStart;
+
+ private Mode _mode;
+
+ private static readonly string[] NoHeaders = new string[0];
+
+ public Enumerator(string[] headers)
+ {
+ _headers = headers ?? NoHeaders;
+ _header = string.Empty;
+ _headerLength = -1;
+ _index = -1;
+ _offset = -1;
+ _leadingStart = -1;
+ _leadingEnd = -1;
+ _valueStart = -1;
+ _valueEnd = -1;
+ _trailingStart = -1;
+ _mode = Mode.Leading;
+ }
+
+ private enum Mode
+ {
+ Leading,
+ Value,
+ ValueQuoted,
+ Trailing,
+ Produce,
+ }
+
+ private enum Attr
+ {
+ Value,
+ Quote,
+ Delimiter,
+ Whitespace
+ }
+
+ public HeaderSegment Current
+ {
+ get
+ {
+ return new HeaderSegment(
+ new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart),
+ new StringSegment(_header, _valueStart, _valueEnd - _valueStart));
+ }
+ }
+
+ object IEnumerator.Current
+ {
+ get { return Current; }
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ while (true)
+ {
+ if (_mode == Mode.Produce)
+ {
+ _leadingStart = _trailingStart;
+ _leadingEnd = -1;
+ _valueStart = -1;
+ _valueEnd = -1;
+ _trailingStart = -1;
+
+ if (_offset == _headerLength &&
+ _leadingStart != -1 &&
+ _leadingStart != _offset)
+ {
+ // Also produce trailing whitespace
+ _leadingEnd = _offset;
+ return true;
+ }
+ _mode = Mode.Leading;
+ }
+
+ // if end of a string
+ if (_offset == _headerLength)
+ {
+ ++_index;
+ _offset = -1;
+ _leadingStart = 0;
+ _leadingEnd = -1;
+ _valueStart = -1;
+ _valueEnd = -1;
+ _trailingStart = -1;
+
+ // if that was the last string
+ if (_index == _headers.Length)
+ {
+ // no more move nexts
+ return false;
+ }
+
+ // grab the next string
+ _header = _headers[_index] ?? string.Empty;
+ _headerLength = _header.Length;
+ }
+ while (true)
+ {
+ ++_offset;
+ char ch = _offset == _headerLength ? (char)0 : _header[_offset];
+ // todo - array of attrs
+ Attr attr = char.IsWhiteSpace(ch) ? Attr.Whitespace : ch == '\"' ? Attr.Quote : (ch == ',' || ch == (char)0) ? Attr.Delimiter : Attr.Value;
+
+ switch (_mode)
+ {
+ case Mode.Leading:
+ switch (attr)
+ {
+ case Attr.Delimiter:
+ _leadingEnd = _offset;
+ _mode = Mode.Produce;
+ break;
+ case Attr.Quote:
+ _leadingEnd = _offset;
+ _valueStart = _offset;
+ _mode = Mode.ValueQuoted;
+ break;
+ case Attr.Value:
+ _leadingEnd = _offset;
+ _valueStart = _offset;
+ _mode = Mode.Value;
+ break;
+ case Attr.Whitespace:
+ // more
+ break;
+ }
+ break;
+ case Mode.Value:
+ switch (attr)
+ {
+ case Attr.Quote:
+ _mode = Mode.ValueQuoted;
+ break;
+ case Attr.Delimiter:
+ _valueEnd = _offset;
+ _trailingStart = _offset;
+ _mode = Mode.Produce;
+ break;
+ case Attr.Value:
+ // more
+ break;
+ case Attr.Whitespace:
+ _valueEnd = _offset;
+ _trailingStart = _offset;
+ _mode = Mode.Trailing;
+ break;
+ }
+ break;
+ case Mode.ValueQuoted:
+ switch (attr)
+ {
+ case Attr.Quote:
+ _mode = Mode.Value;
+ break;
+ case Attr.Delimiter:
+ if (ch == (char)0)
+ {
+ _valueEnd = _offset;
+ _trailingStart = _offset;
+ _mode = Mode.Produce;
+ }
+ break;
+ case Attr.Value:
+ case Attr.Whitespace:
+ // more
+ break;
+ }
+ break;
+ case Mode.Trailing:
+ switch (attr)
+ {
+ case Attr.Delimiter:
+ _mode = Mode.Produce;
+ break;
+ case Attr.Quote:
+ // back into value
+ _trailingStart = -1;
+ _valueEnd = -1;
+ _mode = Mode.ValueQuoted;
+ break;
+ case Attr.Value:
+ // back into value
+ _trailingStart = -1;
+ _valueEnd = -1;
+ _mode = Mode.Value;
+ break;
+ case Attr.Whitespace:
+ // more
+ break;
+ }
+ break;
+ }
+ if (_mode == Mode.Produce)
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ public void Reset()
+ {
+ _index = 0;
+ _offset = 0;
+ _leadingStart = 0;
+ _leadingEnd = 0;
+ _valueStart = 0;
+ _valueEnd = 0;
+ }
+ }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("App_Packages", "")]
+ internal struct StringSegment : IEquatable
+ {
+ private readonly string _buffer;
+ private readonly int _offset;
+ private readonly int _count;
+
+ //
+ // Initializes a new instance of the class.
+ //
+ 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; }
+ }
+
+ #region Equality members
+
+ 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);
+ }
+
+ #endregion
+
+ public bool StartsWith(string text, StringComparison comparisonType)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("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("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("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;
+ }
+ }
+
+ internal static partial class ParsingHelpers
+ {
+ private static readonly Action AddCookieCallback = (name, value, state) =>
+ {
+ var dictionary = (IDictionary)state;
+ if (!dictionary.ContainsKey(name))
+ {
+ dictionary.Add(name, value);
+ }
+ };
+
+ private static readonly char[] SemicolonAndComma = new[] { ';', ',' };
+
+ internal static IDictionary GetCookies(HttpRequest request)
+ {
+ var cookies = request.Get>("Microsoft.Owin.Cookies#dictionary");
+ if (cookies == null)
+ {
+ cookies = new Dictionary(StringComparer.Ordinal);
+ request.Set("Microsoft.Owin.Cookies#dictionary", cookies);
+ }
+
+ string text = GetHeader(request.Headers, "Cookie");
+ if (request.Get("Microsoft.Owin.Cookies#text") != text)
+ {
+ cookies.Clear();
+ ParseDelimited(text, SemicolonAndComma, AddCookieCallback, cookies);
+ request.Set("Microsoft.Owin.Cookies#text", text);
+ }
+ return cookies;
+ }
+
+ internal static void ParseDelimited(string text, char[] delimiters, Action callback, object state)
+ {
+ int textLength = text.Length;
+ int equalIndex = text.IndexOf('=');
+ if (equalIndex == -1)
+ {
+ equalIndex = textLength;
+ }
+ int scanIndex = 0;
+ while (scanIndex < textLength)
+ {
+ int delimiterIndex = text.IndexOfAny(delimiters, scanIndex);
+ if (delimiterIndex == -1)
+ {
+ delimiterIndex = textLength;
+ }
+ if (equalIndex < delimiterIndex)
+ {
+ while (scanIndex != equalIndex && char.IsWhiteSpace(text[scanIndex]))
+ {
+ ++scanIndex;
+ }
+ string name = text.Substring(scanIndex, equalIndex - scanIndex);
+ string value = text.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1);
+ callback(
+ Uri.UnescapeDataString(name.Replace('+', ' ')),
+ Uri.UnescapeDataString(value.Replace('+', ' ')),
+ state);
+ equalIndex = text.IndexOf('=', delimiterIndex);
+ if (equalIndex == -1)
+ {
+ equalIndex = textLength;
+ }
+ }
+ scanIndex = delimiterIndex + 1;
+ }
+ }
+
+ internal static string GetJoinedValue(IDictionary Store, string key)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ internal static partial class OwinHelpers
+ {
+ public static string GetHeader(IDictionary headers, string key)
+ {
+ string[] values = GetHeaderUnmodified(headers, key);
+ return values == null ? null : string.Join(",", values);
+ }
+
+ public static IEnumerable GetHeaderSplit(IDictionary headers, string key)
+ {
+ string[] values = GetHeaderUnmodified(headers, key);
+ return values == null ? null : GetHeaderSplitImplementation(values);
+ }
+
+ private static IEnumerable GetHeaderSplitImplementation(string[] values)
+ {
+ foreach (var segment in new HeaderSegmentCollection(values))
+ {
+ if (segment.Data.HasValue)
+ {
+ yield return DeQuote(segment.Data.Value);
+ }
+ }
+ }
+
+ public static string[] GetHeaderUnmodified(IDictionary headers, string key)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException("headers");
+ }
+ string[] values;
+ return headers.TryGetValue(key, out values) ? values : null;
+ }
+
+ public static void SetHeader(IDictionary headers, string key, string value)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException("headers");
+ }
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ throw new ArgumentNullException("key");
+ }
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ headers.Remove(key);
+ }
+ else
+ {
+ headers[key] = new[] { value };
+ }
+ }
+
+ public static void SetHeaderJoined(IDictionary headers, string key, params string[] values)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException("headers");
+ }
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ throw new ArgumentNullException("key");
+ }
+ if (values == null || values.Length == 0)
+ {
+ headers.Remove(key);
+ }
+ else
+ {
+ headers[key] = new[] { string.Join(",", values.Select(value => QuoteIfNeeded(value))) };
+ }
+ }
+
+ // Quote items that contain comas and are not already quoted.
+ private static string QuoteIfNeeded(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ // Ignore
+ }
+ 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] == '"')
+ {
+ value = value.Substring(1, value.Length - 2);
+ }
+
+ return value;
+ }
+
+ public static void SetHeaderUnmodified(IDictionary headers, string key, params string[] values)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException("headers");
+ }
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ throw new ArgumentNullException("key");
+ }
+ if (values == null || values.Length == 0)
+ {
+ headers.Remove(key);
+ }
+ else
+ {
+ headers[key] = values;
+ }
+ }
+
+ public static void SetHeaderUnmodified(IDictionary headers, string key, IEnumerable values)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException("headers");
+ }
+ headers[key] = values.ToArray();
+ }
+
+ public static void AppendHeader(IDictionary headers, string key, string values)
+ {
+ if (string.IsNullOrWhiteSpace(values))
+ {
+ return;
+ }
+
+ string existing = GetHeader(headers, key);
+ if (existing == null)
+ {
+ SetHeader(headers, key, values);
+ }
+ else
+ {
+ headers[key] = new[] { existing + "," + values };
+ }
+ }
+
+ public static void AppendHeaderJoined(IDictionary headers, string key, params string[] values)
+ {
+ if (values == null || values.Length == 0)
+ {
+ return;
+ }
+
+ string existing = GetHeader(headers, key);
+ if (existing == null)
+ {
+ SetHeaderJoined(headers, key, values);
+ }
+ else
+ {
+ headers[key] = new[] { existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value))) };
+ }
+ }
+
+ public static void AppendHeaderUnmodified(IDictionary headers, string key, params string[] values)
+ {
+ if (values == null || values.Length == 0)
+ {
+ return;
+ }
+
+ string[] existing = GetHeaderUnmodified(headers, key);
+ if (existing == null)
+ {
+ SetHeaderUnmodified(headers, key, values);
+ }
+ else
+ {
+ SetHeaderUnmodified(headers, key, existing.Concat(values));
+ }
+ }
+ }
+
+ internal static partial class OwinHelpers
+ {
+ private static readonly Action AppendItemCallback = (name, value, state) =>
+ {
+ var dictionary = (IDictionary>)state;
+
+ List existing;
+ if (!dictionary.TryGetValue(name, out existing))
+ {
+ dictionary.Add(name, new List(1) { value });
+ }
+ else
+ {
+ existing.Add(value);
+ }
+ };
+
+ private static readonly char[] AmpersandAndSemicolon = new[] { '&', ';' };
+
+ internal static IDictionary GetQuery(HttpRequest request)
+ {
+ var query = request.Get>("Microsoft.Owin.Query#dictionary");
+ if (query == null)
+ {
+ query = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ request.Set("Microsoft.Owin.Query#dictionary", query);
+ }
+
+ string text = request.QueryString.Value;
+ if (request.Get("Microsoft.Owin.Query#text") != text)
+ {
+ query.Clear();
+ var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+ ParseDelimited(text, AmpersandAndSemicolon, AppendItemCallback, accumulator);
+ foreach (var kv in accumulator)
+ {
+ query.Add(kv.Key, kv.Value.ToArray());
+ }
+ request.Set("Microsoft.Owin.Query#text", text);
+ }
+ return query;
+ }
+
+#if !NET40
+ internal static IFormCollection GetForm(string text)
+ {
+ IDictionary form = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+ ParseDelimited(text, new[] { '&' }, AppendItemCallback, accumulator);
+ foreach (var kv in accumulator)
+ {
+ form.Add(kv.Key, kv.Value.ToArray());
+ }
+ return new FormCollection(form);
+ }
+#endif
+
+ internal static string GetJoinedValue(IDictionary store, string key)
+ {
+ string[] values = GetUnmodifiedValues(store, key);
+ return values == null ? null : string.Join(",", values);
+ }
+
+ internal static string[] GetUnmodifiedValues(IDictionary store, string key)
+ {
+ if (store == null)
+ {
+ throw new ArgumentNullException("store");
+ }
+ string[] values;
+ return store.TryGetValue(key, out values) ? values : null;
+ }
+ }
+
+ internal static partial class OwinHelpers
+ {
+ internal static string GetHost(HttpRequest request)
+ {
+ IHeaderDictionary headers = request.Headers;
+
+ string host = GetHeader(headers, "Host");
+ if (!string.IsNullOrWhiteSpace(host))
+ {
+ return host;
+ }
+
+ string localIpAddress = request.LocalIpAddress ?? "localhost";
+ var localPort = request.Get(OwinConstants.CommonKeys.LocalPort);
+ return string.IsNullOrWhiteSpace(localPort) ? localIpAddress : (localIpAddress + ":" + localPort);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.FeatureModel.Tests/Properties/AssemblyInfo.cs b/test/Microsoft.AspNet.FeatureModel.Tests/Properties/AssemblyInfo.cs
index da65ff9698..8beefe2634 100644
--- a/test/Microsoft.AspNet.FeatureModel.Tests/Properties/AssemblyInfo.cs
+++ b/test/Microsoft.AspNet.FeatureModel.Tests/Properties/AssemblyInfo.cs
@@ -1,4 +1,4 @@
-using System.Reflection;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -31,6 +31,6 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+// [assembly: AssemblyVersion("0.1.0")]
+[assembly: AssemblyVersion("0.1.0")]
+[assembly: AssemblyFileVersion("0.1.0")]
diff --git a/test/Microsoft.AspNet.PipelineCore.Tests/Microsoft.AspNet.PipelineCore.Tests.csproj b/test/Microsoft.AspNet.PipelineCore.Tests/Microsoft.AspNet.PipelineCore.Tests.csproj
index 9ef35f3c8d..d68b62764a 100644
--- a/test/Microsoft.AspNet.PipelineCore.Tests/Microsoft.AspNet.PipelineCore.Tests.csproj
+++ b/test/Microsoft.AspNet.PipelineCore.Tests/Microsoft.AspNet.PipelineCore.Tests.csproj
@@ -53,6 +53,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+