From 7230a3d78e8c369b2acf4cb2f835a186900cd49e Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Thu, 7 Aug 2014 15:19:47 -0700 Subject: [PATCH] Add form and query helpers needed for Facebook auth. --- .../FormFeature.cs | 7 +- .../Infrastructure/ParsingHelpers.cs | 12 --- .../QueryFeature.cs | 2 +- .../RequestCookiesFeature.cs | 1 + .../project.json | 3 +- .../Collections/FormCollection.cs | 4 +- .../Collections/ReadableStringCollection.cs | 4 +- .../FormHelpers.cs | 18 ++++ .../NotNullAttribute.cs | 12 +++ .../ParsingHelpers.cs | 94 +++++++++++++++++++ .../QueryHelpers.cs | 20 ++++ 11 files changed, 155 insertions(+), 22 deletions(-) rename src/{Microsoft.AspNet.PipelineCore => Microsoft.AspNet.WebUtilities}/Collections/FormCollection.cs (87%) rename src/{Microsoft.AspNet.PipelineCore => Microsoft.AspNet.WebUtilities}/Collections/ReadableStringCollection.cs (95%) create mode 100644 src/Microsoft.AspNet.WebUtilities/FormHelpers.cs create mode 100644 src/Microsoft.AspNet.WebUtilities/NotNullAttribute.cs create mode 100644 src/Microsoft.AspNet.WebUtilities/ParsingHelpers.cs create mode 100644 src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs diff --git a/src/Microsoft.AspNet.PipelineCore/FormFeature.cs b/src/Microsoft.AspNet.PipelineCore/FormFeature.cs index c2b90a6574..271264b870 100644 --- a/src/Microsoft.AspNet.PipelineCore/FormFeature.cs +++ b/src/Microsoft.AspNet.PipelineCore/FormFeature.cs @@ -9,8 +9,9 @@ using System.Threading.Tasks; 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 @@ namespace Microsoft.AspNet.PipelineCore 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/ParsingHelpers.cs b/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs index a545a1dac7..2ede1ab15c 100644 --- a/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs +++ b/src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs @@ -818,18 +818,6 @@ namespace Microsoft.AspNet.PipelineCore.Infrastructure 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 1cf25622df..ab5ea5157a 100644 --- a/src/Microsoft.AspNet.PipelineCore/QueryFeature.cs +++ b/src/Microsoft.AspNet.PipelineCore/QueryFeature.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; 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 952bfed0f6..8a3ae99e72 100644 --- a/src/Microsoft.AspNet.PipelineCore/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.PipelineCore/RequestCookiesFeature.cs @@ -9,6 +9,7 @@ using Microsoft.AspNet.Http.Infrastructure; 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 316aaadbe0..81d5f9a326 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 b1da4f6c3c..a9d1df8529 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 139d8d1c78..97c03b0df2 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 0000000000..17f8135465 --- /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 0000000000..d489cf36b3 --- /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 0000000000..c9f4e6f544 --- /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 0000000000..27b8a96ded --- /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