Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Add form and query helpers needed for Facebook auth. #113

Merged
merged 2 commits into from
Aug 20, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Microsoft.AspNet.Http/IBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object> Properties { get; set; }

IBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

IBuilder New();

RequestDelegate Build();
}
}
43 changes: 39 additions & 4 deletions src/Microsoft.AspNet.PipelineCore/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Infrastructure;

namespace Microsoft.AspNet.Builder
{
Expand All @@ -15,17 +16,51 @@ public class Builder : IBuilder

public Builder(IServiceProvider serviceProvider)
{
Properties = new Dictionary<string, object>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is case-sensitivity relevant here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Though I suppose someone could always overwrite the dictionary with whatever they want, so this is probably fine the way it is.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Katana these were always case sensitive (the default for a new Dictionary). I don't know that there are serious motivations one way or the other.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's fair.

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<IServiceProvider>(Constants.BuilderProperties.ApplicationServices);
}
set
{
SetProperty<IServiceProvider>(Constants.BuilderProperties.ApplicationServices, value);
}
}

public IServerInformation Server
{
get
{
return GetProperty<IServerInformation>(Constants.BuilderProperties.ServerInformation);
}
set
{
SetProperty<IServerInformation>(Constants.BuilderProperties.ServerInformation, value);
}
}

public IDictionary<string, object> Properties { get; set; }

private T GetProperty<T>(string key)
{
object value;
return Properties.TryGetValue(key, out value) ? (T)value : default(T);
}

private void SetProperty<T>(string key, T value)
{
Properties[key] = value;
}

public IBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
Expand Down
7 changes: 4 additions & 3 deletions src/Microsoft.AspNet.PipelineCore/FormFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -60,8 +61,8 @@ public async Task<IReadableStringCollection> 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;
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.AspNet.PipelineCore/Infrastructure/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
}
12 changes: 0 additions & 12 deletions src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -818,18 +818,6 @@ internal static IDictionary<string, string[]> GetQuery(string queryString)
StringComparer.OrdinalIgnoreCase);
}

internal static IFormCollection GetForm(string text)
{
IDictionary<string, string[]> form = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
var accumulator = new Dictionary<string, List<string>>(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<string, string[]> store, string key)
{
string[] values = GetUnmodifiedValues(store, key);
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.AspNet.PipelineCore/QueryFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.AspNet.PipelineCore/RequestCookiesFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.AspNet.PipelineCore/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"dependencies": {
"Microsoft.AspNet.FeatureModel": "",
"Microsoft.AspNet.Http": "",
"Microsoft.AspNet.HttpFeature": ""
"Microsoft.AspNet.HttpFeature": "",
"Microsoft.AspNet.WebUtilities": ""
},
"frameworks": {
"net45": {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
using Microsoft.AspNet.Http;
using System.Collections.Generic;

namespace Microsoft.AspNet.PipelineCore.Collections
namespace Microsoft.AspNet.WebUtilities.Collections
{
/// <summary>
/// Contains the parsed form values.
/// </summary>
public class FormCollection : ReadableStringCollection, IFormCollection
{
/// <summary>
/// Initializes a new instance of the <see cref="T:Microsoft.Owin.FormCollection" /> class.
/// Initializes a new instance of the <see cref="T:Microsoft.AspNet.WebUtilities.FormCollection" /> class.
/// </summary>
/// <param name="store">The store for the form.</param>
public FormCollection(IDictionary<string, string[]> store)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// Accessors for query, forms, etc.
Expand Down
18 changes: 18 additions & 0 deletions src/Microsoft.AspNet.WebUtilities/FormHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using Microsoft.AspNet.Http;

namespace Microsoft.AspNet.WebUtilities
{
public static class FormHelpers
{
/// <summary>
/// Parses an HTTP form body.
/// </summary>
/// <param name="text">The HTTP form body to parse.</param>
/// <returns>The <see cref="T:Microsoft.Owin.IFormCollection" /> object containing the parsed HTTP form body.</returns>
public static IFormCollection ParseForm(string text)
{
return ParsingHelpers.GetForm(text);
}
}
}
12 changes: 12 additions & 0 deletions src/Microsoft.AspNet.WebUtilities/NotNullAttribute.cs
Original file line number Diff line number Diff line change
@@ -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
{
}
}
94 changes: 94 additions & 0 deletions src/Microsoft.AspNet.WebUtilities/ParsingHelpers.cs
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why partial? I see src/Microsoft.AspNet.PipelineCore/Infrastructure/ParsingHelpers.cs has the same style partial class, but that also looks incorrect to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy, these used to be in separate files based on functionality. I'll clean it up.

{
internal static void ParseDelimited(string text, char[] delimiters, Action<string, string, object> 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]))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does IsWhiteSpace have the exact correct meaning as per whatever RFC this is? Looking at MSDN, this method considers like a zillion different characters to be whitespace (http://msdn.microsoft.com/en-us/library/system.char.iswhitespace(v=vs.110).aspx).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's OK because the content should still be encoded at this point. We decode it a few lines later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still highly recommend going with the most correct logic here, per whatever is in the RFC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed as #119

{
++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<string, string, object> AppendItemCallback = (name, value, state) =>
{
var dictionary = (IDictionary<string, List<String>>)state;

List<string> existing;
if (!dictionary.TryGetValue(name, out existing))
{
dictionary.Add(name, new List<string>(1) { value });
}
else
{
existing.Add(value);
}
};

internal static IFormCollection GetForm(string text)
{
IDictionary<string, string[]> form = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
var accumulator = new Dictionary<string, List<string>>(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<string, string[]> store, string key)
{
string[] values = GetUnmodifiedValues(store, key);
return values == null ? null : string.Join(",", values);
}

internal static string[] GetUnmodifiedValues(IDictionary<string, string[]> store, string key)
{
if (store == null)
{
throw new ArgumentNullException("store");
}
string[] values;
return store.TryGetValue(key, out values) ? values : null;
}
}
}
20 changes: 20 additions & 0 deletions src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace Microsoft.AspNet.WebUtilities
{
public static class QueryHelpers
{
/// <summary>
/// Append the given query key and value to the uri.
/// </summary>
/// <param name="uri">The base uri.</param>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uri --> URI in the English part of the comment (more than one instance).

/// <param name="name">The name of the query key.</param>
/// <param name="value">The query value.</param>
/// <returns>The combine result.</returns>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

combine --> combined?

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);
}
}
}