/// <summary> /// Add a new cookie /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="options"></param> public static void AppendCookie(this IHeaderDictionary Headers, string key, string value, ExtendedCookieOptions 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; bool maxAgeHasValue = options.MaxAge.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", !maxAgeHasValue ? null : "; max-age=", !maxAgeHasValue ? null : options.MaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture), !options.Secure ? null : "; secure", !options.HttpOnly ? null : "; HttpOnly"); Headers.AppendValues("Set-Cookie", setCookieValue); }
public void AppendValues_AppendsToList() { IHeaderDictionary headers = CreateHeaders(CustomHeaderRawValues); headers.AppendValues(CustomHeaderKey, "vA, vB", "vC"); IList <string> values = headers.GetValues(CustomHeaderKey); Assert.Equal(CustomHeaderRawValues.Concat(new[] { "vA, vB", "vC" }), values); }
public static void SetMultiValuedCookie( this IHeaderDictionary headers, string key, params KeyValuePair <string, string>[] values) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException(nameof(key)); } if (values == null) { throw new ArgumentNullException(nameof(values)); } if (0 >= values.Length) { throw new ArgumentOutOfRangeException(nameof(values)); } var value = string.Join("&", values.Select(v => $"{Uri.EscapeDataString(v.Key)}={Uri.EscapeDataString(v.Value)}")); headers.AppendValues("Set-Cookie", Uri.EscapeDataString(key) + "=" + value + "; path=/"); }
/// <summary> /// Appends a new response cookie to the Set-Cookie header. If the cookie is larger than the given size limit /// then it will be broken down into multiple cookies as follows: /// Set-Cookie: CookieName=chunks:3; path=/ /// Set-Cookie: CookieNameC1=Segment1; path=/ /// Set-Cookie: CookieNameC2=Segment2; path=/ /// Set-Cookie: CookieNameC3=Segment3; path=/ /// </summary> /// <param name="context"></param> /// <param name="key"></param> /// <param name="value"></param> /// <param name="options"></param> public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options) { if (context == null) { throw new ArgumentNullException("context"); } if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); bool expiresHasValue = options.Expires.HasValue; bool sameSiteHasValue = options.SameSite.HasValue; string escapedKey = Uri.EscapeDataString(key); string prefix = escapedKey + "="; string suffix = string.Concat( !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 \\G\\M\\T", CultureInfo.InvariantCulture), !options.Secure ? null : "; secure", !options.HttpOnly ? null : "; HttpOnly", !sameSiteHasValue ? null : "; SameSite=", !sameSiteHasValue ? null : GetStringRepresentationOfSameSite(options.SameSite.Value)); value = value ?? string.Empty; bool quoted = false; if (IsQuoted(value)) { quoted = true; value = RemoveQuotes(value); } string escapedValue = Uri.EscapeDataString(value); // Normal cookie IHeaderDictionary responseHeaders = context.Response.Headers; if (!ChunkSize.HasValue || ChunkSize.Value > prefix.Length + escapedValue.Length + suffix.Length + (quoted ? 2 : 0)) { string setCookieValue = string.Concat( prefix, quoted ? Quote(escapedValue) : escapedValue, suffix); responseHeaders.AppendValues(Constants.Headers.SetCookie, setCookieValue); } else if (ChunkSize.Value < prefix.Length + suffix.Length + (quoted ? 2 : 0) + 10) { // 10 is the minimum data we want to put in an individual cookie, including the cookie chunk identifier "CXX". // No room for data, we can't chunk the options and name throw new InvalidOperationException(Resources.Exception_CookieLimitTooSmall); } else { // Break the cookie down into multiple cookies. // Key = CookieName, value = "Segment1Segment2Segment2" // Set-Cookie: CookieName=chunks:3; path=/ // Set-Cookie: CookieNameC1="Segment1"; path=/ // Set-Cookie: CookieNameC2="Segment2"; path=/ // Set-Cookie: CookieNameC3="Segment3"; path=/ int dataSizePerCookie = ChunkSize.Value - prefix.Length - suffix.Length - (quoted ? 2 : 0) - 3; // Budget 3 chars for the chunkid. int cookieChunkCount = (int)Math.Ceiling(escapedValue.Length * 1.0 / dataSizePerCookie); responseHeaders.AppendValues(Constants.Headers.SetCookie, prefix + "chunks:" + cookieChunkCount.ToString(CultureInfo.InvariantCulture) + suffix); string[] chunks = new string[cookieChunkCount]; int offset = 0; for (int chunkId = 1; chunkId <= cookieChunkCount; chunkId++) { int remainingLength = escapedValue.Length - offset; int length = Math.Min(dataSizePerCookie, remainingLength); string segment = escapedValue.Substring(offset, length); offset += length; chunks[chunkId - 1] = string.Concat( escapedKey, "C", chunkId.ToString(CultureInfo.InvariantCulture), "=", quoted ? "\"" : string.Empty, segment, quoted ? "\"" : string.Empty, suffix); } responseHeaders.AppendValues(Constants.Headers.SetCookie, chunks); } }