From e818783ba4b818542a681be4a206dc40804c33a6 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Fri, 10 Apr 2015 12:06:09 -0700 Subject: [PATCH] #167: Update cookie APIs to use shared core. --- .../Collections/RequestCookiesCollection.cs | 28 +++-- .../Collections/ResponseCookies.cs | 47 ++++---- .../Infrastructure/ParsingHelpers.cs | 107 ------------------ .../RequestCookiesFeature.cs | 19 ++-- 4 files changed, 48 insertions(+), 153 deletions(-) diff --git a/src/Microsoft.AspNet.Http.Core/Collections/RequestCookiesCollection.cs b/src/Microsoft.AspNet.Http.Core/Collections/RequestCookiesCollection.cs index 36a91735a3..6f43aa3d07 100644 --- a/src/Microsoft.AspNet.Http.Core/Collections/RequestCookiesCollection.cs +++ b/src/Microsoft.AspNet.Http.Core/Collections/RequestCookiesCollection.cs @@ -4,8 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Http.Core.Infrastructure; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Http.Core.Collections { @@ -73,12 +72,20 @@ namespace Microsoft.AspNet.Http.Core.Collections return _dictionary.TryGetValue(key, out value) ? new[] { value } : null; } - private static readonly char[] SemicolonAndComma = { ';', ',' }; - - public void Reparse(string cookiesHeader) + public void Reparse(IList values) { _dictionary.Clear(); - ParsingHelpers.ParseDelimited(cookiesHeader, SemicolonAndComma, AddCookieCallback, _dictionary); + + IList cookies; + if (CookieHeaderValue.TryParseList(values, out cookies)) + { + foreach (var cookie in cookies) + { + var name = Uri.UnescapeDataString(cookie.Name.Replace('+', ' ')); + var value = Uri.UnescapeDataString(cookie.Value.Replace('+', ' ')); + _dictionary[name] = value; + } + } } public IEnumerator> GetEnumerator() @@ -93,14 +100,5 @@ namespace Microsoft.AspNet.Http.Core.Collections { return GetEnumerator(); } - - private static readonly Action AddCookieCallback = (name, value, state) => - { - var dictionary = (IDictionary)state; - if (!dictionary.ContainsKey(name)) - { - dictionary.Add(name, value); - } - }; } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Core/Collections/ResponseCookies.cs b/src/Microsoft.AspNet.Http.Core/Collections/ResponseCookies.cs index cfdd8798c7..45da572ccb 100644 --- a/src/Microsoft.AspNet.Http.Core/Collections/ResponseCookies.cs +++ b/src/Microsoft.AspNet.Http.Core/Collections/ResponseCookies.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Microsoft.Framework.Internal; using Microsoft.Framework.WebEncoders; @@ -34,7 +33,11 @@ namespace Microsoft.AspNet.Http.Core.Collections /// public void Append(string key, string value) { - Headers.AppendValues(HeaderNames.SetCookie, UrlEncoder.Default.UrlEncode(key) + "=" + UrlEncoder.Default.UrlEncode(value) + "; path=/"); + Headers.AppendValues(HeaderNames.SetCookie, + new SetCookieHeaderValue( + UrlEncoder.Default.UrlEncode(key), + UrlEncoder.Default.UrlEncode(value)) + { Path = "/" }.ToString()); } /// @@ -45,23 +48,17 @@ namespace Microsoft.AspNet.Http.Core.Collections /// public void Append(string key, string value, [NotNull] CookieOptions options) { - bool domainHasValue = !string.IsNullOrEmpty(options.Domain); - bool pathHasValue = !string.IsNullOrEmpty(options.Path); - bool expiresHasValue = options.Expires.HasValue; - - string setCookieValue = string.Concat( - UrlEncoder.Default.UrlEncode(key), - "=", - UrlEncoder.Default.UrlEncode(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(HeaderNames.SetCookie, setCookieValue); + Headers.AppendValues(HeaderNames.SetCookie, + new SetCookieHeaderValue( + UrlEncoder.Default.UrlEncode(key), + UrlEncoder.Default.UrlEncode(value)) + { + Domain = options.Domain, + Path = options.Path, + Expires = options.Expires, + Secure = options.Secure, + HttpOnly = options.HttpOnly, + }.ToString()); } /// @@ -70,9 +67,10 @@ namespace Microsoft.AspNet.Http.Core.Collections /// public void Delete(string key) { - Func predicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); + var encodedKeyPlusEquals = UrlEncoder.Default.UrlEncode(key) + "="; + Func predicate = value => value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase); - var deleteCookies = new[] { UrlEncoder.Default.UrlEncode(key) + "=; expires=Thu, 01-Jan-1970 00:00:00 GMT" }; + var deleteCookies = new[] { encodedKeyPlusEquals + "; expires=Thu, 01-Jan-1970 00:00:00 GMT" }; IList existingValues = Headers.GetValues(HeaderNames.SetCookie); if (existingValues == null || existingValues.Count == 0) { @@ -91,6 +89,7 @@ namespace Microsoft.AspNet.Http.Core.Collections /// public void Delete(string key, [NotNull] CookieOptions options) { + var encodedKeyPlusEquals = UrlEncoder.Default.UrlEncode(key) + "="; bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); @@ -98,18 +97,18 @@ namespace Microsoft.AspNet.Http.Core.Collections if (domainHasValue) { rejectPredicate = value => - value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && + value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase) && value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1; } else if (pathHasValue) { rejectPredicate = value => - value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && + value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase) && value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1; } else { - rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); + rejectPredicate = value => value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase); } IList existingValues = Headers.GetValues(HeaderNames.SetCookie); diff --git a/src/Microsoft.AspNet.Http.Core/Infrastructure/ParsingHelpers.cs b/src/Microsoft.AspNet.Http.Core/Infrastructure/ParsingHelpers.cs index 0831839bd7..cf6a955d86 100644 --- a/src/Microsoft.AspNet.Http.Core/Infrastructure/ParsingHelpers.cs +++ b/src/Microsoft.AspNet.Http.Core/Infrastructure/ParsingHelpers.cs @@ -496,71 +496,6 @@ namespace Microsoft.AspNet.Http.Core.Infrastructure internal static 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 T GetItem(HttpRequest request, string key) - { - object value; - return request.HttpContext.Items.TryGetValue(key, out value) ? (T)value : default(T); - } - - internal static void SetItem(HttpRequest request, string key, T value) - { - request.HttpContext.Items[key] = value; - } - - internal static void ParseCookies(string cookiesHeader, IDictionary cookiesCollection) - { - ParseDelimited(cookiesHeader, SemicolonAndComma, AddCookieCallback, cookiesCollection); - } - - 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; - } - } - public static string GetHeader(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); @@ -729,48 +664,6 @@ namespace Microsoft.AspNet.Http.Core.Infrastructure } } - 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 string GetJoinedValue(IDictionary store, string key) - { - string[] values = GetUnmodifiedValues(store, key); - return values == null ? null : string.Join(",", values); - } - - internal static string[] GetUnmodifiedValues([NotNull] IDictionary store, string key) - { - string[] values; - return store.TryGetValue(key, out values) ? values : null; - } - - //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); - //} - public static long? GetContentLength([NotNull] IHeaderDictionary headers) { const NumberStyles styles = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite; diff --git a/src/Microsoft.AspNet.Http.Core/RequestCookiesFeature.cs b/src/Microsoft.AspNet.Http.Core/RequestCookiesFeature.cs index 46d7a4f249..4a22b22a93 100644 --- a/src/Microsoft.AspNet.Http.Core/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http.Core/RequestCookiesFeature.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.AspNet.FeatureModel; using Microsoft.AspNet.Http.Core.Collections; using Microsoft.AspNet.Http.Core.Infrastructure; @@ -15,7 +16,7 @@ namespace Microsoft.AspNet.Http.Core { private readonly IFeatureCollection _features; private readonly FeatureReference _request = FeatureReference.Default; - private string _cookiesHeader; + private string[] _cookieHeaders; private RequestCookiesCollection _cookiesCollection; private IReadableStringCollection _cookies; @@ -44,18 +45,22 @@ namespace Microsoft.AspNet.Http.Core } var headers = _request.Fetch(_features).Headers; - string cookiesHeader = ParsingHelpers.GetHeader(headers, HeaderNames.Cookie) ?? string.Empty; + string[] values; + if (!headers.TryGetValue(HeaderNames.Cookie, out values)) + { + values = new string[0]; + } if (_cookiesCollection == null) { + _cookieHeaders = values; _cookiesCollection = new RequestCookiesCollection(); - _cookiesCollection.Reparse(cookiesHeader); - _cookiesHeader = cookiesHeader; + _cookiesCollection.Reparse(values); } - else if (!string.Equals(_cookiesHeader, cookiesHeader, StringComparison.Ordinal)) + else if (!Enumerable.SequenceEqual(_cookieHeaders, values, StringComparer.Ordinal)) { - _cookiesCollection.Reparse(cookiesHeader); - _cookiesHeader = cookiesHeader; + _cookieHeaders = values; + _cookiesCollection.Reparse(values); } return _cookiesCollection;