287 lines
8.3 KiB
C#
287 lines
8.3 KiB
C#
// 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<CookieHeaderValue> ParseList(IList<string> inputs)
|
|
{
|
|
return MultipleValueParser.ParseValues(inputs);
|
|
}
|
|
|
|
public static IList<CookieHeaderValue> ParseStrictList(IList<string> inputs)
|
|
{
|
|
return MultipleValueParser.ParseStrictValues(inputs);
|
|
}
|
|
|
|
public static bool TryParseList(IList<string> inputs, out IList<CookieHeaderValue> parsedValues)
|
|
{
|
|
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
|
|
}
|
|
|
|
public static bool TryParseStrictList(IList<string> inputs, out IList<CookieHeaderValue> 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<int>() >= 0) && (Contract.Result<int>() <= (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();
|
|
}
|
|
}
|
|
} |