diff --git a/src/Microsoft.AspNet.Http/IBuilder.cs b/src/Microsoft.AspNet.Http/IBuilder.cs index 6a2c1b31..1673390e 100644 --- a/src/Microsoft.AspNet.Http/IBuilder.cs +++ b/src/Microsoft.AspNet.Http/IBuilder.cs @@ -2,17 +2,22 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; namespace Microsoft.AspNet.Builder { public interface IBuilder { IServiceProvider ApplicationServices { get; set; } + IServerInformation Server { get; set; } + IDictionary Properties { get; set; } + IBuilder Use(Func middleware); IBuilder New(); + RequestDelegate Build(); } } diff --git a/src/Microsoft.AspNet.PipelineCore/Builder.cs b/src/Microsoft.AspNet.PipelineCore/Builder.cs index fae10cf6..12a95ebd 100644 --- a/src/Microsoft.AspNet.PipelineCore/Builder.cs +++ b/src/Microsoft.AspNet.PipelineCore/Builder.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Infrastructure; namespace Microsoft.AspNet.Builder { @@ -15,17 +16,51 @@ public class Builder : IBuilder public Builder(IServiceProvider serviceProvider) { + Properties = new Dictionary(); ApplicationServices = serviceProvider; } private Builder(Builder builder) { - ApplicationServices = builder.ApplicationServices; - Server = builder.Server; + Properties = builder.Properties; } - public IServiceProvider ApplicationServices { get; set; } - public IServerInformation Server { get; set; } + public IServiceProvider ApplicationServices + { + get + { + return GetProperty(Constants.BuilderProperties.ApplicationServices); + } + set + { + SetProperty(Constants.BuilderProperties.ApplicationServices, value); + } + } + + public IServerInformation Server + { + get + { + return GetProperty(Constants.BuilderProperties.ServerInformation); + } + set + { + SetProperty(Constants.BuilderProperties.ServerInformation, value); + } + } + + public IDictionary Properties { get; set; } + + private T GetProperty(string key) + { + object value; + return Properties.TryGetValue(key, out value) ? (T)value : default(T); + } + + private void SetProperty(string key, T value) + { + Properties[key] = value; + } public IBuilder Use(Func middleware) { diff --git a/src/Microsoft.AspNet.PipelineCore/FormFeature.cs b/src/Microsoft.AspNet.PipelineCore/FormFeature.cs index c2b90a65..271264b8 100644 --- a/src/Microsoft.AspNet.PipelineCore/FormFeature.cs +++ b/src/Microsoft.AspNet.PipelineCore/FormFeature.cs @@ -9,8 +9,9 @@ using Microsoft.AspNet.FeatureModel; using Microsoft.AspNet.Http; using Microsoft.AspNet.HttpFeature; -using Microsoft.AspNet.PipelineCore.Collections; using Microsoft.AspNet.PipelineCore.Infrastructure; +using Microsoft.AspNet.WebUtilities; +using Microsoft.AspNet.WebUtilities.Collections; namespace Microsoft.AspNet.PipelineCore { @@ -60,8 +61,8 @@ public async Task GetFormAsync(CancellationToken canc detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true)) { - string formQuery = await streamReader.ReadToEndAsync(); - _form = new ReadableStringCollection(ParsingHelpers.GetQuery(formQuery)); + string form = await streamReader.ReadToEndAsync(); + _form = FormHelpers.ParseForm(form); } } return _form; diff --git a/src/Microsoft.AspNet.PipelineCore/Infrastructure/Constants.cs b/src/Microsoft.AspNet.PipelineCore/Infrastructure/Constants.cs index 9404c79c..c918c4de 100644 --- a/src/Microsoft.AspNet.PipelineCore/Infrastructure/Constants.cs +++ b/src/Microsoft.AspNet.PipelineCore/Infrastructure/Constants.cs @@ -25,5 +25,11 @@ internal static class Headers internal const string Expires = "Expires"; internal const string WebSocketSubProtocols = "Sec-WebSocket-Protocol"; } + + internal static class BuilderProperties + { + internal static string ServerInformation = "server.Information"; + internal static string ApplicationServices = "application.Services"; + } } } diff --git a/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs b/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs index a545a1da..2ede1ab1 100644 --- a/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs +++ b/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs @@ -818,18 +818,6 @@ internal static IDictionary GetQuery(string queryString) StringComparer.OrdinalIgnoreCase); } - 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); - } - internal static string GetJoinedValue(IDictionary store, string key) { string[] values = GetUnmodifiedValues(store, key); diff --git a/src/Microsoft.AspNet.PipelineCore/QueryFeature.cs b/src/Microsoft.AspNet.PipelineCore/QueryFeature.cs index 1cf25622..ab5ea515 100644 --- a/src/Microsoft.AspNet.PipelineCore/QueryFeature.cs +++ b/src/Microsoft.AspNet.PipelineCore/QueryFeature.cs @@ -5,8 +5,8 @@ using Microsoft.AspNet.FeatureModel; using Microsoft.AspNet.Http; using Microsoft.AspNet.HttpFeature; -using Microsoft.AspNet.PipelineCore.Collections; using Microsoft.AspNet.PipelineCore.Infrastructure; +using Microsoft.AspNet.WebUtilities.Collections; namespace Microsoft.AspNet.PipelineCore { diff --git a/src/Microsoft.AspNet.PipelineCore/RequestCookiesFeature.cs b/src/Microsoft.AspNet.PipelineCore/RequestCookiesFeature.cs index 952bfed0..8a3ae99e 100644 --- a/src/Microsoft.AspNet.PipelineCore/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.PipelineCore/RequestCookiesFeature.cs @@ -9,6 +9,7 @@ using Microsoft.AspNet.HttpFeature; using Microsoft.AspNet.PipelineCore.Collections; using Microsoft.AspNet.PipelineCore.Infrastructure; +using Microsoft.AspNet.WebUtilities.Collections; namespace Microsoft.AspNet.PipelineCore { diff --git a/src/Microsoft.AspNet.PipelineCore/project.json b/src/Microsoft.AspNet.PipelineCore/project.json index 316aaadb..81d5f9a3 100644 --- a/src/Microsoft.AspNet.PipelineCore/project.json +++ b/src/Microsoft.AspNet.PipelineCore/project.json @@ -4,7 +4,8 @@ "dependencies": { "Microsoft.AspNet.FeatureModel": "", "Microsoft.AspNet.Http": "", - "Microsoft.AspNet.HttpFeature": "" + "Microsoft.AspNet.HttpFeature": "", + "Microsoft.AspNet.WebUtilities": "" }, "frameworks": { "net45": {}, diff --git a/src/Microsoft.AspNet.PipelineCore/Collections/FormCollection.cs b/src/Microsoft.AspNet.WebUtilities/Collections/FormCollection.cs similarity index 87% rename from src/Microsoft.AspNet.PipelineCore/Collections/FormCollection.cs rename to src/Microsoft.AspNet.WebUtilities/Collections/FormCollection.cs index b1da4f6c..a9d1df85 100644 --- a/src/Microsoft.AspNet.PipelineCore/Collections/FormCollection.cs +++ b/src/Microsoft.AspNet.WebUtilities/Collections/FormCollection.cs @@ -4,7 +4,7 @@ using Microsoft.AspNet.Http; using System.Collections.Generic; -namespace Microsoft.AspNet.PipelineCore.Collections +namespace Microsoft.AspNet.WebUtilities.Collections { /// /// Contains the parsed form values. @@ -12,7 +12,7 @@ namespace Microsoft.AspNet.PipelineCore.Collections public class FormCollection : ReadableStringCollection, IFormCollection { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The store for the form. public FormCollection(IDictionary store) diff --git a/src/Microsoft.AspNet.PipelineCore/Collections/ReadableStringCollection.cs b/src/Microsoft.AspNet.WebUtilities/Collections/ReadableStringCollection.cs similarity index 95% rename from src/Microsoft.AspNet.PipelineCore/Collections/ReadableStringCollection.cs rename to src/Microsoft.AspNet.WebUtilities/Collections/ReadableStringCollection.cs index 139d8d1c..97c03b0d 100644 --- a/src/Microsoft.AspNet.PipelineCore/Collections/ReadableStringCollection.cs +++ b/src/Microsoft.AspNet.WebUtilities/Collections/ReadableStringCollection.cs @@ -4,11 +4,9 @@ using System; using System.Collections; using System.Collections.Generic; -using Microsoft.AspNet.Http.Infrastructure; using Microsoft.AspNet.Http; -using Microsoft.AspNet.PipelineCore.Infrastructure; -namespace Microsoft.AspNet.PipelineCore.Collections +namespace Microsoft.AspNet.WebUtilities.Collections { /// /// Accessors for query, forms, etc. diff --git a/src/Microsoft.AspNet.WebUtilities/FormHelpers.cs b/src/Microsoft.AspNet.WebUtilities/FormHelpers.cs new file mode 100644 index 00000000..17f81354 --- /dev/null +++ b/src/Microsoft.AspNet.WebUtilities/FormHelpers.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.WebUtilities +{ + public static class FormHelpers + { + /// + /// Parses an HTTP form body. + /// + /// The HTTP form body to parse. + /// The object containing the parsed HTTP form body. + public static IFormCollection ParseForm(string text) + { + return ParsingHelpers.GetForm(text); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebUtilities/NotNullAttribute.cs b/src/Microsoft.AspNet.WebUtilities/NotNullAttribute.cs new file mode 100644 index 00000000..d489cf36 --- /dev/null +++ b/src/Microsoft.AspNet.WebUtilities/NotNullAttribute.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.WebUtilities +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] + internal sealed class NotNullAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebUtilities/ParsingHelpers.cs b/src/Microsoft.AspNet.WebUtilities/ParsingHelpers.cs new file mode 100644 index 00000000..c9f4e6f5 --- /dev/null +++ b/src/Microsoft.AspNet.WebUtilities/ParsingHelpers.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.WebUtilities.Collections; + +namespace Microsoft.AspNet.WebUtilities +{ + internal static partial class ParsingHelpers + { + 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; + } + } + + 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); + } + }; + + 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); + } + + 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; + } + } +} diff --git a/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs b/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs new file mode 100644 index 00000000..27b8a96d --- /dev/null +++ b/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs @@ -0,0 +1,20 @@ +using System; + +namespace Microsoft.AspNet.WebUtilities +{ + public static class QueryHelpers + { + /// + /// Append the given query key and value to the uri. + /// + /// The base uri. + /// The name of the query key. + /// The query value. + /// The combine result. + public static string AddQueryString([NotNull] string uri, [NotNull] string name, [NotNull] string value) + { + bool hasQuery = uri.IndexOf('?') != -1; + return uri + (hasQuery ? "&" : "?") + Uri.EscapeDataString(name) + "=" + Uri.EscapeDataString(value); + } + } +} \ No newline at end of file