// Copyright (c) .NET Foundation. 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 System.Diagnostics.Contracts; using System.Text; namespace Microsoft.Net.Http.Headers { // http://tools.ietf.org/html/rfc6265 public class CookieHeaderValue { private static readonly CookieHeaderParser SingleValueParser = new CookieHeaderParser(supportsMultipleValues: false); private static readonly CookieHeaderParser MultipleValueParser = new CookieHeaderParser(supportsMultipleValues: true); private string _name; private string _value; private CookieHeaderValue() { // Used by the parser to create a new instance of this type. } public CookieHeaderValue(string name) : this(name, string.Empty) { if (name == null) { throw new ArgumentNullException(nameof(name)); } } public CookieHeaderValue(string name, string value) { if (name == null) { throw new ArgumentNullException(nameof(name)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } Name = name; Value = value; } public string Name { get { return _name; } set { CheckNameFormat(value, nameof(value)); _name = value; } } public string Value { get { return _value; } set { CheckValueFormat(value, nameof(value)); _value = value; } } // name="val ue"; public override string ToString() { var header = new StringBuilder(); header.Append(_name); header.Append("="); header.Append(_value); return header.ToString(); } private static void AppendSegment(StringBuilder builder, string name, string value) { builder.Append("; "); builder.Append(name); if (value != null) { builder.Append("="); builder.Append(value); } } public static CookieHeaderValue Parse(string input) { var index = 0; return SingleValueParser.ParseValue(input, ref index); } public static bool TryParse(string input, out CookieHeaderValue parsedValue) { var index = 0; return SingleValueParser.TryParseValue(input, ref index, out parsedValue); } public static IList ParseList(IList inputs) { return MultipleValueParser.ParseValues(inputs); } public static IList ParseStrictList(IList inputs) { return MultipleValueParser.ParseStrictValues(inputs); } public static bool TryParseList(IList inputs, out IList parsedValues) { return MultipleValueParser.TryParseValues(inputs, out parsedValues); } public static bool TryParseStrictList(IList inputs, out IList parsedValues) { return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues); } // name=value; name="value" internal static bool TryGetCookieLength(string input, ref int offset, out CookieHeaderValue parsedValue) { Contract.Requires(offset >= 0); parsedValue = null; if (string.IsNullOrEmpty(input) || (offset >= input.Length)) { return false; } var result = new CookieHeaderValue(); // The caller should have already consumed any leading whitespace, commas, etc.. // Name=value; // Name var itemLength = HttpRuleParser.GetTokenLength(input, offset); if (itemLength == 0) { return false; } result._name = input.Substring(offset, itemLength); offset += itemLength; // = (no spaces) if (!ReadEqualsSign(input, ref offset)) { return false; } // value or "quoted value" // The value may be empty result._value = GetCookieValue(input, ref offset); parsedValue = result; return true; } // cookie-value = *cookie-octet / ( DQUOTE* cookie-octet DQUOTE ) // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E // ; US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash internal static string GetCookieValue(string input, ref int offset) { Contract.Requires(input != null); Contract.Requires(offset >= 0); Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - offset))); var startIndex = offset; if (offset >= input.Length) { return string.Empty; } var inQuotes = false; if (input[offset] == '"') { inQuotes = true; offset++; } while (offset < input.Length) { var c = input[offset]; if (!IsCookieValueChar(c)) { break; } offset++; } if (inQuotes) { if (offset == input.Length || input[offset] != '"') { // Missing final quote return string.Empty; } offset++; } int length = offset - startIndex; if (offset > startIndex) { return input.Substring(startIndex, length); } return string.Empty; } private static bool ReadEqualsSign(string input, ref int offset) { // = (no spaces) if (offset >= input.Length || input[offset] != '=') { return false; } offset++; return true; } // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E // ; US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash private static bool IsCookieValueChar(char c) { if (c < 0x21 || c > 0x7E) { return false; } return !(c == '"' || c == ',' || c == ';' || c == '\\'); } internal static void CheckNameFormat(string name, string parameterName) { if (name == null) { throw new ArgumentNullException(nameof(name)); } if (HttpRuleParser.GetTokenLength(name, 0) != name.Length) { throw new ArgumentException("Invalid cookie name: " + name, parameterName); } } internal static void CheckValueFormat(string value, string parameterName) { if (value == null) { throw new ArgumentNullException(nameof(value)); } var offset = 0; var result = GetCookieValue(value, ref offset); if (result.Length != value.Length) { throw new ArgumentException("Invalid cookie value: " + value, parameterName); } } public override bool Equals(object obj) { var other = obj as CookieHeaderValue; if (other == null) { return false; } return string.Equals(_name, other._name, StringComparison.OrdinalIgnoreCase) && string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } public override int GetHashCode() { return _name.GetHashCode() ^ _value.GetHashCode(); } } }