Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / whidbey / netfxsp / ndp / fx / src / Net / System / Net / cookie.cs / 1 / cookie.cs
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------------
namespace System.Net {
using System.Collections;
using System.Globalization;
using System.Threading;
internal enum CookieVariant {
Unknown,
Plain,
Rfc2109,
Rfc2965,
Default = Rfc2109
}
//
// Cookie class
//
// Adheres to RFC 2965
//
// Currently, only client-side cookies. The cookie classes know how to
// parse a set-cookie format string, but not a cookie format string
// (e.g. "Cookie: $Version=1; name=value; $Path=/foo; $Secure")
//
///
/// [To be supplied.]
///
[Serializable]
public sealed class Cookie {
internal const int MaxSupportedVersion = 1;
internal const string CommentAttributeName = "Comment";
internal const string CommentUrlAttributeName = "CommentURL";
internal const string DiscardAttributeName = "Discard";
internal const string DomainAttributeName = "Domain";
internal const string ExpiresAttributeName = "Expires";
internal const string MaxAgeAttributeName = "Max-Age";
internal const string PathAttributeName = "Path";
internal const string PortAttributeName = "Port";
internal const string SecureAttributeName = "Secure";
internal const string VersionAttributeName = "Version";
internal const string HttpOnlyAttributeName = "HttpOnly";
internal const string SeparatorLiteral = "; ";
internal const string EqualsLiteral = "=";
internal const string QuotesLiteral = "\"";
internal const string SpecialAttributeLiteral = "$";
internal static readonly char[] PortSplitDelimiters = new char[] {' ', ',', '\"'};
internal static readonly char[] Reserved2Name = new char[] {' ', '\t', '\r', '\n', '=', ';', ',' };
internal static readonly char[] Reserved2Value = new char[] {';', ',' };
private static Comparer staticComparer = new Comparer();
// fields
string m_comment = string.Empty;
Uri m_commentUri = null;
CookieVariant m_cookieVariant = CookieVariant.Plain;
bool m_discard = false;
string m_domain = string.Empty;
bool m_domain_implicit = true;
DateTime m_expires = DateTime.MinValue;
string m_name = string.Empty;
string m_path = string.Empty;
bool m_path_implicit = true;
string m_port = string.Empty;
bool m_port_implicit = true;
int[] m_port_list = null;
bool m_secure = false;
[System.Runtime.Serialization.OptionalField]
bool m_httpOnly=false;
DateTime m_timeStamp = DateTime.Now;
string m_value = string.Empty;
int m_version = 0;
string m_domainKey = string.Empty;
internal bool IsQuotedVersion = false;
internal bool IsQuotedDomain = false;
// constructors
///
/// [To be supplied.]
///
public Cookie() {
}
//public Cookie(string cookie) {
// if ((cookie == null) || (cookie == String.Empty)) {
// throw new ArgumentException("cookie");
// }
// Parse(cookie.Trim());
// Validate();
//}
///
/// [To be supplied.]
///
public Cookie(string name, string value) {
Name = name;
m_value = value;
}
///
/// [To be supplied.]
///
public Cookie(string name, string value, string path)
: this(name, value) {
Path = path;
}
///
/// [To be supplied.]
///
public Cookie(string name, string value, string path, string domain)
: this(name, value, path) {
Domain = domain;
}
// properties
///
/// [To be supplied.]
///
public string Comment {
get {
return m_comment;
}
set {
if (value == null) {
value = string.Empty;
}
m_comment = value;
}
}
///
/// [To be supplied.]
///
public Uri CommentUri {
get {
return m_commentUri;
}
set {
m_commentUri = value;
}
}
public bool HttpOnly{
get{
return m_httpOnly;
}
set{
m_httpOnly = value;
}
}
///
/// [To be supplied.]
///
public bool Discard {
get {
return m_discard;
}
set {
m_discard = value;
}
}
///
/// [To be supplied.]
///
public string Domain {
get {
return m_domain;
}
set {
m_domain = (value == null? String.Empty : value);
m_domain_implicit = false;
m_domainKey = string.Empty; //this will get it value when adding into the Container.
}
}
private string _Domain {
get {
return (Plain || m_domain_implicit || (m_domain.Length == 0))
? string.Empty
: (SpecialAttributeLiteral
+ DomainAttributeName
+ EqualsLiteral + (IsQuotedDomain? "\"": string.Empty)
+ m_domain + (IsQuotedDomain? "\"": string.Empty)
);
}
}
///
/// [To be supplied.]
///
public bool Expired {
get {
return (m_expires <= DateTime.Now) && (m_expires != DateTime.MinValue);
}
set {
if (value == true) {
m_expires = DateTime.Now;
}
}
}
///
/// [To be supplied.]
///
public DateTime Expires {
get {
return m_expires;
}
set {
m_expires = value;
}
}
///
/// [To be supplied.]
///
public string Name {
get {
return m_name;
}
set {
if (ValidationHelper.IsBlankString(value) || !InternalSetName(value)) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Name", value == null? "": value));
}
}
}
internal bool InternalSetName(string value) {
if (ValidationHelper.IsBlankString(value) || value[0] == '$' || value.IndexOfAny(Reserved2Name) != -1) {
m_name = string.Empty;
return false;
}
m_name = value;
return true;
}
///
/// [To be supplied.]
///
public string Path {
get {
return m_path;
}
set {
m_path = (value == null? String.Empty : value);
m_path_implicit = false;
}
}
private string _Path {
get {
return (Plain || m_path_implicit || (m_path.Length == 0))
? string.Empty
: (SpecialAttributeLiteral
+ PathAttributeName
+ EqualsLiteral
+ m_path
);
}
}
internal bool Plain {
get {
return Variant == CookieVariant.Plain;
}
}
//
// According to spec we must assume default values for attributes but still
// keep in mind that we must not include them into the requests.
// We also check the validiy of all attributes based on the version and variant (read RFC)
//
// To work properly this function must be called after cookie construction with
// default (response) URI AND set_default == true
//
// Afterwards, the function can be called many times with other URIs and
// set_default == false to check whether this cookie matches given uri
//
internal bool VerifySetDefaults(CookieVariant variant, Uri uri, bool isLocalDomain, string localDomain, bool set_default, bool isThrow) {
string host = uri.Host;
int port = uri.Port;
string path = uri.AbsolutePath;
bool valid= true;
if (set_default) {
// Set Variant. If version is zero => reset cookie to Version0 style
if (Version == 0) {
variant = CookieVariant.Plain;
}
else if (Version == 1 && variant == CookieVariant.Unknown) {
//since we don't expose Variant to an app, set it to Default
variant = CookieVariant.Default;
}
m_cookieVariant = variant;
}
//Check the name
if (m_name == null || m_name.Length == 0 || m_name[0] == '$' || m_name.IndexOfAny(Reserved2Name) != -1) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Name", m_name == null? "": m_name));
}
return false;
}
//Check the value
if (m_value == null ||
(!(m_value.Length > 2 && m_value[0] == '\"' && m_value[m_value.Length-1] == '\"') && m_value.IndexOfAny(Reserved2Value) != -1)) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Value", m_value == null? "": m_value));
}
return false;
}
//Check Comment syntax
if (Comment != null && !(Comment.Length > 2 && Comment[0] == '\"' && Comment[Comment.Length-1] == '\"')
&& (Comment.IndexOfAny(Reserved2Value) != -1)) {
if (isThrow)
throw new CookieException(SR.GetString(SR.net_cookie_attribute, CommentAttributeName, Comment));
return false;
}
//Check Path syntax
if (Path != null && !(Path.Length > 2 && Path[0] == '\"' && Path[Path.Length-1] == '\"')
&& (Path.IndexOfAny(Reserved2Value) != -1)) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PathAttributeName, Path));
}
return false;
}
//Check/set domain
// if domain is implicit => assume a) uri is valid, b) just set domain to uri hostname
if (set_default && m_domain_implicit == true) {
m_domain = host;
}
else {
if (!m_domain_implicit) {
// Forwarding note: If Uri.Host is of IP address form then the only supported case
// is for IMPLICIT domain property of a cookie.
// The below code (explicit cookie.Domain value) will try to parse Uri.Host IP string
// as a fqdn and reject the cookie
//aliasing since we might need the KeyValue (but not the original one)
string domain = m_domain;
//Syntax check for Domain charset plus empty string
if (!DomainCharsTest(domain)) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, DomainAttributeName, domain == null? "": domain));
}
return false;
}
//domain must start with '.' if set explicitly
if(domain[0] != '.' ) {
if (!(variant == CookieVariant.Rfc2965 || variant == CookieVariant.Plain)) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, DomainAttributeName, m_domain));
}
return false;
}
domain = '.' + domain;
}
int host_dot = host.IndexOf('.');
bool is_local = false;
// First quick check is for pushing a cookie into the local domain
if (isLocalDomain && (string.Compare(localDomain, domain, StringComparison.OrdinalIgnoreCase ) == 0)) {
valid = true;
}
else if (domain.Length < 4 || (!(is_local = (string.Compare(domain, ".local", StringComparison.OrdinalIgnoreCase)) == 0) && domain.IndexOf('.', 1, domain.Length-2) == -1)) {
// explicit domains must contain 'inside' dots or be ".local"
valid = false;
}
else if (is_local && isLocalDomain) {
// valid ".local" found for the local domain Uri
valid = true;
}
else if (is_local && !isLocalDomain) {
// ".local" cannot happen on non-local domain
valid = false;
}
else if (variant == CookieVariant.Plain) {
// We distiguish between Version0 cookie and other versions on domain issue
// According to Version0 spec a domain must be just a substring of the hostname
if (!(host.Length + 1 == domain.Length && string.Compare(host, 0, domain, 1, host.Length, StringComparison.OrdinalIgnoreCase) == 0)) {
if (host.Length <= domain.Length ||
string.Compare(host, host.Length-domain.Length, domain, 0, domain.Length, StringComparison.OrdinalIgnoreCase) != 0) {
valid = false;
}
}
}
else if ( !is_local &&
(host_dot == -1 ||
domain.Length != host.Length-host_dot ||
string.Compare(host, host_dot, domain, 0, domain.Length, StringComparison.OrdinalIgnoreCase) != 0)) {
//starting the first dot, the host must match the domain
valid = false;
}
if (valid) {
if(is_local) {
m_domainKey = localDomain.ToLower(CultureInfo.InvariantCulture);
}
else {
m_domainKey = domain.ToLower(CultureInfo.InvariantCulture);
}
}
}
else {
// for implicitly set domain AND at the set_default==false time
// we simply got to match uri.Host against m_domain
if (string.Compare(host, m_domain, StringComparison.OrdinalIgnoreCase) != 0) {
valid = false;
}
}
if(!valid) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, DomainAttributeName, m_domain));
}
return false;
}
}
//Check/Set Path
if (set_default && m_path_implicit == true) {
//assuming that uri path is always valid and contains at least one '/'
switch (m_cookieVariant) {
case CookieVariant.Plain:
m_path = path;
break;
case CookieVariant.Rfc2109:
m_path = path.Substring(0, path.LastIndexOf('/')); //may be empty
break;
case CookieVariant.Rfc2965:
default: //hope future versions will have same 'Path' semantic?
m_path = path.Substring(0, path.LastIndexOf('/')+1);
break;
}
}
else {
//check current path (implicit/explicit) against given uri
if (!path.StartsWith(CookieParser.CheckQuoted(m_path))) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PathAttributeName, m_path));
}
return false;
}
}
// set the default port if Port attribute was present but had no value
if (set_default && (m_port_implicit == false && m_port.Length == 0)) {
m_port_list = new int[1] {port};
}
if(m_port_implicit == false) {
// Port must match agaist the one from the uri
valid = false;
foreach (int p in m_port_list) {
if (p == port) {
valid = true;
break;
}
}
if (!valid) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port));
}
return false;
}
}
return true;
}
//Very primitive test to make sure that the name does not have illegal characters
// As per RFC 952 (relaxed on first char could be a digit and string can have '_')
private static bool DomainCharsTest(string name) {
if (name == null || name.Length == 0) {
return false;
}
for(int i=0; i < name.Length; ++i) {
char ch = name[i];
if (!(
(ch >= '0' && ch <= '9') ||
(ch == '.' || ch == '-') ||
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch == '_')
))
return false;
}
return true;
}
///
/// [To be supplied.]
///
public string Port {
get {
return m_port;
}
set {
m_port_implicit = false;
if ((value == null || value.Length == 0)) {
//"Port" is present but has no value.
m_port = string.Empty;
}
else {
m_port = value;
// Parse port list
if (value[0] != '\"' || value[value.Length-1] != '\"') {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port));
}
string[] ports = value.Split(PortSplitDelimiters);
m_port_list = new int[ports.Length];
for (int i = 0; i < ports.Length; ++i) {
m_port_list[i] = -1;
if(ports[i].Length == 0) {
//ignore spaces this way, and leave port=-1 in the slot
continue;
}
if (!Int32.TryParse(ports[i], out m_port_list[i]))
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port));
}
m_version = MaxSupportedVersion;
m_cookieVariant = CookieVariant.Rfc2965;
}
}
}
internal int[] PortList {
get {
//It must be null if Port Attribute was ommitted in the response
return m_port_list;
}
}
private string _Port {
get {
return m_port_implicit ? string.Empty :
(SpecialAttributeLiteral
+ PortAttributeName
+ ((m_port.Length == 0) ? string.Empty : (EqualsLiteral + m_port))
);
}
}
///
/// [To be supplied.]
///
public bool Secure {
get {
return m_secure;
}
set {
m_secure = value;
}
}
///
/// [To be supplied.]
///
public DateTime TimeStamp {
get {
return m_timeStamp;
}
}
///
/// [To be supplied.]
///
public string Value {
get {
return m_value;
}
set {
m_value = (value == null? String.Empty : value);
}
}
internal CookieVariant Variant {
get {
return m_cookieVariant;
}
set {
// only set by HttpListenerRequest::Cookies_get()
GlobalLog.Assert(value == CookieVariant.Rfc2965, "Cookie#{0}::set_Variant()|value:{1}" , ValidationHelper.HashString(this), value);
m_cookieVariant = value;
}
}
// m_domainKey member is set internally in VerifySetDefaults()
// If it is not set then verification function was not called
// and this should never happen
internal string DomainKey {
get {
return m_domain_implicit ? Domain : m_domainKey;
}
}
//public Version Version {
///
/// [To be supplied.]
///
public int Version {
get {
return m_version;
}
set {
if (value<0) {
throw new ArgumentOutOfRangeException("value");
}
m_version = value;
if (value>0 && m_cookieVariant
/// [To be supplied.]
///
public override bool Equals(object comparand) {
if (!(comparand is Cookie)) {
return false;
}
Cookie other = (Cookie)comparand;
return (string.Compare(Name, other.Name, StringComparison.OrdinalIgnoreCase) == 0)
&& (string.Compare(Value, other.Value, StringComparison.Ordinal) == 0)
&& (string.Compare(Path, other.Path, StringComparison.Ordinal) == 0)
&& (string.Compare(Domain, other.Domain, StringComparison.OrdinalIgnoreCase) == 0)
&& (Version == other.Version)
;
}
///
/// [To be supplied.]
///
public override int GetHashCode() {
//
//string hashString = Name + "=" + Value + ";" + Path + "; " + Domain + "; " + Version;
//int hash = 0;
//
//foreach (char ch in hashString) {
// hash = unchecked(hash << 1 ^ (int)ch);
//}
//return hash;
return (Name + "=" + Value + ";" + Path + "; " + Domain + "; " + Version).GetHashCode();
}
///
/// [To be supplied.]
///
public override string ToString() {
string domain = _Domain;
string path = _Path;
string port = _Port;
string version = _Version;
string result =
((version.Length == 0)? string.Empty : (version + SeparatorLiteral))
+ Name + EqualsLiteral + Value
+ ((path.Length == 0) ? string.Empty : (SeparatorLiteral + path))
+ ((domain.Length == 0) ? string.Empty : (SeparatorLiteral + domain))
+ ((port.Length == 0) ? string.Empty : (SeparatorLiteral + port))
;
if (result == "=") {
return string.Empty;
}
return result;
}
internal string ToServerString() {
string result = Name + EqualsLiteral + Value;
if (m_comment!=null && m_comment.Length>0) {
result += SeparatorLiteral + CommentAttributeName + EqualsLiteral + m_comment;
}
if (m_commentUri!=null) {
result += SeparatorLiteral + CommentUrlAttributeName + EqualsLiteral + QuotesLiteral + m_commentUri.ToString() + QuotesLiteral;
}
if (m_discard) {
result += SeparatorLiteral + DiscardAttributeName;
}
if (!Plain && !m_domain_implicit && m_domain!=null && m_domain.Length>0) {
result += SeparatorLiteral + DomainAttributeName + EqualsLiteral + m_domain;
}
int seconds = (Expires-DateTime.UtcNow).Seconds;
if (seconds>0) {
result += SeparatorLiteral + MaxAgeAttributeName + EqualsLiteral + seconds.ToString(NumberFormatInfo.InvariantInfo);
}
if (!Plain && !m_path_implicit && m_path!=null && m_path.Length>0) {
result += SeparatorLiteral + PathAttributeName + EqualsLiteral + m_path;
}
if (!Plain && !m_port_implicit && m_port!=null && m_port.Length>0) {
// QuotesLiteral are included in m_port
result += SeparatorLiteral + PortAttributeName + EqualsLiteral + m_port;
}
if (m_version>0) {
result += SeparatorLiteral + VersionAttributeName + EqualsLiteral + m_version.ToString(NumberFormatInfo.InvariantInfo);
}
return result==EqualsLiteral ? null : result;
}
//private void Validate() {
// if ((m_name == String.Empty) && (m_value == String.Empty)) {
// throw new CookieException();
// }
// if ((m_name != String.Empty) && (m_name[0] == '$')) {
// throw new CookieException();
// }
//}
#if DEBUG
///
/// [To be supplied.]
///
internal void Dump() {
GlobalLog.Print("Cookie: " + ToString() + "->\n"
+ "\tComment = " + Comment + "\n"
+ "\tCommentUri = " + CommentUri + "\n"
+ "\tDiscard = " + Discard + "\n"
+ "\tDomain = " + Domain + "\n"
+ "\tExpired = " + Expired + "\n"
+ "\tExpires = " + Expires + "\n"
+ "\tName = " + Name + "\n"
+ "\tPath = " + Path + "\n"
+ "\tPort = " + Port + "\n"
+ "\tSecure = " + Secure + "\n"
+ "\tTimeStamp = " + TimeStamp + "\n"
+ "\tValue = " + Value + "\n"
+ "\tVariant = " + Variant + "\n"
+ "\tVersion = " + Version + "\n"
+ "\tHttpOnly = " + HttpOnly + "\n"
);
}
#endif
}
internal enum CookieToken {
// state types
Nothing,
NameValuePair, // X=Y
Attribute, // X
EndToken, // ';'
EndCookie, // ','
End, // EOLN
Equals,
// value types
Comment,
CommentUrl,
CookieName,
Discard,
Domain,
Expires,
MaxAge,
Path,
Port,
Secure,
HttpOnly,
Unknown,
Version
}
//
// CookieTokenizer
//
// Used to split a single or multi-cookie (header) string into individual
// tokens
//
internal class CookieTokenizer {
// fields
bool m_eofCookie;
int m_index;
int m_length;
string m_name;
bool m_quoted;
int m_start;
CookieToken m_token;
int m_tokenLength;
string m_tokenStream;
string m_value;
// constructors
internal CookieTokenizer(string tokenStream) {
m_length = tokenStream.Length;
m_tokenStream = tokenStream;
}
// properties
internal bool EndOfCookie {
get {
return m_eofCookie;
}
set {
m_eofCookie = value;
}
}
internal bool Eof {
get {
return m_index >= m_length;
}
}
internal string Name {
get {
return m_name;
}
set {
m_name = value;
}
}
internal bool Quoted {
get {
return m_quoted;
}
set {
m_quoted = value;
}
}
internal CookieToken Token {
get {
return m_token;
}
set {
m_token = value;
}
}
internal string Value {
get {
return m_value;
}
set {
m_value = value;
}
}
// methods
//
// Extract
//
// extract the current token
//
internal string Extract() {
string tokenString = string.Empty;
if (m_tokenLength != 0) {
tokenString = m_tokenStream.Substring(m_start, m_tokenLength);
if (!Quoted) {
tokenString = tokenString.Trim();
}
}
return tokenString;
}
//
// FindNext
//
// Find the start and length of the next token. The token is terminated
// by one of:
//
// - end-of-line
// - end-of-cookie: unquoted comma separates multiple cookies
// - end-of-token: unquoted semi-colon
// - end-of-name: unquoted equals
//
// Inputs:
// ignoreComma
// true if parsing doesn't stop at a comma. This is only true when
// we know we're parsing an original cookie that has an expires=
// attribute, because the format of the time/date used in expires
// is:
// Wdy, dd-mmm-yyyy HH:MM:SS GMT
//
// ignoreEquals
// true if parsing doesn't stop at an equals sign. The LHS of the
// first equals sign is an attribute name. The next token may
// include one or more equals signs. E.g.,
//
// SESSIONID=ID=MSNx45&q=33
//
// Outputs:
// m_index
// incremented to the last position in m_tokenStream contained by
// the current token
//
// m_start
// incremented to the start of the current token
//
// m_tokenLength
// set to the length of the current token
//
// Assumes:
// Nothing
//
// Returns:
// type of CookieToken found:
//
// End - end of the cookie string
// EndCookie - end of current cookie in (potentially) a
// multi-cookie string
// EndToken - end of name=value pair, or end of an attribute
// Equals - end of name=
//
// Throws:
// Nothing
//
internal CookieToken FindNext(bool ignoreComma, bool ignoreEquals) {
m_tokenLength = 0;
m_start = m_index;
while ((m_index < m_length) && Char.IsWhiteSpace(m_tokenStream[m_index])) {
++m_index;
++m_start;
}
CookieToken token = CookieToken.End;
int increment = 1;
if (!Eof) {
if (m_tokenStream[m_index] == '"') {
Quoted = true;
++m_index;
bool quoteOn = false;
while (m_index < m_length) {
char currChar = m_tokenStream[m_index];
if (!quoteOn && currChar == '"')
break;
if (quoteOn)
quoteOn = false;
else if (currChar == '\\')
quoteOn = true;
++m_index;
}
if (m_index < m_length) {
++m_index;
}
m_tokenLength = m_index - m_start;
increment = 0;
// if we are here, reset ignoreComma
// In effect, we ignore everything after quoted string till next delimiter
ignoreComma = false;
}
while ((m_index < m_length)
&& (m_tokenStream[m_index] != ';')
&& (ignoreEquals || (m_tokenStream[m_index] != '='))
&& (ignoreComma || (m_tokenStream[m_index] != ','))) {
// Fixing 2 things:
// 1) ignore day of week in cookie string
// 2) revert ignoreComma once meet it, so won't miss the next cookie)
if (m_tokenStream[m_index] == ',') {
m_start = m_index+1;
m_tokenLength = -1;
ignoreComma = false;
}
++m_index;
m_tokenLength += increment;
}
if (!Eof) {
switch (m_tokenStream[m_index]) {
case ';':
token = CookieToken.EndToken;
break;
case '=':
token = CookieToken.Equals;
break;
default:
token = CookieToken.EndCookie;
break;
}
++m_index;
}
}
return token;
}
//
// Next
//
// Get the next cookie name/value or attribute
//
// Cookies come in the following formats:
//
// 1. Version0
// Set-Cookie: [][=][]
// [; expires=]
// [; path=]
// [; domain=]
// [; secure]
// Cookie: =
//
// Notes: and/or may be blank
// is the RFC 822/1123 date format that
// incorporates commas, e.g.
// "Wednesday, 09-Nov-99 23:12:40 GMT"
//
// 2. RFC 2109
// Set-Cookie: 1#{
// =
// [; comment=]
// [; domain=]
// [; max-age=]
// [; path=]
// [; secure]
// ; Version=
// }
// Cookie: $Version=
// 1#{
// ; =
// [; path=]
// [; domain=]
// }
//
// 3. RFC 2965
// Set-Cookie2: 1#{
// =
// [; comment=]
// [; commentURL=]
// [; discard]
// [; domain=]
// [; max-age=]
// [; path=]
// [; ports=]
// [; secure]
// ; Version=
// }
// Cookie: $Version=
// 1#{
// ; =
// [; path=]
// [; domain=]
// [; port=""]
// }
// [Cookie2: $Version=]
//
// Inputs:
// first
// true if this is the first name/attribute that we have looked for
// in the cookie stream
//
// Outputs:
//
// Assumes:
// Nothing
//
// Returns:
// type of CookieToken found:
//
// - Attribute
// - token was single-value. May be empty. Caller should check
// Eof or EndCookie to determine if any more action needs to
// be taken
//
// - NameValuePair
// - Name and Value are meaningful. Either may be empty
//
// Throws:
// Nothing
//
internal CookieToken Next(bool first, bool parseResponseCookies) {
Reset();
CookieToken terminator = FindNext(false, false);
if (terminator == CookieToken.EndCookie) {
EndOfCookie = true;
}
if ((terminator == CookieToken.End) || (terminator == CookieToken.EndCookie)) {
if ((Name = Extract()).Length != 0) {
Token = TokenFromName(parseResponseCookies);
return CookieToken.Attribute;
}
return terminator;
}
Name = Extract();
if (first) {
Token = CookieToken.CookieName;
}
else {
Token = TokenFromName(parseResponseCookies);
}
if (terminator == CookieToken.Equals) {
terminator = FindNext(!first && (Token == CookieToken.Expires), true);
if (terminator == CookieToken.EndCookie) {
EndOfCookie = true;
}
Value = Extract();
return CookieToken.NameValuePair;
}
else {
return CookieToken.Attribute;
}
}
//
// Reset
//
// set up this tokenizer for finding the next name/value pair or
// attribute, or end-of-[token, cookie, or line]
//
internal void Reset() {
m_eofCookie = false;
m_name = string.Empty;
m_quoted = false;
m_start = m_index;
m_token = CookieToken.Nothing;
m_tokenLength = 0;
m_value = string.Empty;
}
private struct RecognizedAttribute {
string m_name;
CookieToken m_token;
internal RecognizedAttribute(string name, CookieToken token) {
m_name = name;
m_token = token;
}
internal CookieToken Token {
get {
return m_token;
}
}
internal bool IsEqualTo(string value) {
return string.Compare(m_name, value, StringComparison.OrdinalIgnoreCase) == 0;
}
}
//
// recognized attributes in order of expected commonality
//
static RecognizedAttribute[] RecognizedAttributes = {
new RecognizedAttribute(Cookie.PathAttributeName, CookieToken.Path),
new RecognizedAttribute(Cookie.MaxAgeAttributeName, CookieToken.MaxAge),
new RecognizedAttribute(Cookie.ExpiresAttributeName, CookieToken.Expires),
new RecognizedAttribute(Cookie.VersionAttributeName, CookieToken.Version),
new RecognizedAttribute(Cookie.DomainAttributeName, CookieToken.Domain),
new RecognizedAttribute(Cookie.SecureAttributeName, CookieToken.Secure),
new RecognizedAttribute(Cookie.DiscardAttributeName, CookieToken.Discard),
new RecognizedAttribute(Cookie.PortAttributeName, CookieToken.Port),
new RecognizedAttribute(Cookie.CommentAttributeName, CookieToken.Comment),
new RecognizedAttribute(Cookie.CommentUrlAttributeName, CookieToken.CommentUrl),
new RecognizedAttribute(Cookie.HttpOnlyAttributeName, CookieToken.HttpOnly),
};
static RecognizedAttribute[] RecognizedServerAttributes = {
new RecognizedAttribute('$' + Cookie.PathAttributeName, CookieToken.Path),
new RecognizedAttribute('$' + Cookie.VersionAttributeName, CookieToken.Version),
new RecognizedAttribute('$' + Cookie.DomainAttributeName, CookieToken.Domain),
new RecognizedAttribute('$' + Cookie.PortAttributeName, CookieToken.Port),
new RecognizedAttribute('$' + Cookie.HttpOnlyAttributeName, CookieToken.HttpOnly),
};
internal CookieToken TokenFromName(bool parseResponseCookies) {
if (!parseResponseCookies) {
for (int i = 0; i < RecognizedServerAttributes.Length; ++i) {
if (RecognizedServerAttributes[i].IsEqualTo(Name)) {
return RecognizedServerAttributes[i].Token;
}
}
}
else {
for (int i = 0; i < RecognizedAttributes.Length; ++i) {
if (RecognizedAttributes[i].IsEqualTo(Name)) {
return RecognizedAttributes[i].Token;
}
}
}
return CookieToken.Unknown;
}
}
//
// CookieParser
//
// Takes a cookie header, makes cookies
//
internal class CookieParser {
// fields
CookieTokenizer m_tokenizer;
Cookie m_savedCookie;
// constructors
internal CookieParser(string cookieString) {
m_tokenizer = new CookieTokenizer(cookieString);
}
// properties
// methods
//
// Get
//
// Gets the next cookie
//
// Inputs:
// Nothing
//
// Outputs:
// Nothing
//
// Assumes:
// Nothing
//
// Returns:
// new cookie object, or null if there's no more
//
// Throws:
// Nothing
//
internal Cookie Get() {
Cookie cookie = null;
// only first ocurence of an attribute value must be counted
bool commentSet = false;
bool commentUriSet = false;
bool domainSet = false;
bool expiresSet = false;
bool pathSet = false;
bool portSet = false; //special case as it may have no value in header
bool versionSet = false;
bool secureSet = false;
bool discardSet = false;
do {
CookieToken token = m_tokenizer.Next(cookie==null, true);
if (cookie==null && (token==CookieToken.NameValuePair || token==CookieToken.Attribute)) {
cookie = new Cookie();
if (cookie.InternalSetName(m_tokenizer.Name) == false) {
//will be rejected
cookie.InternalSetName(string.Empty);
}
cookie.Value = m_tokenizer.Value;
}
else {
switch (token) {
case CookieToken.NameValuePair:
switch (m_tokenizer.Token) {
case CookieToken.Comment:
if (!commentSet) {
commentSet = true;
cookie.Comment = m_tokenizer.Value;
}
break;
case CookieToken.CommentUrl:
if (!commentUriSet) {
commentUriSet = true;
Uri parsed;
if (Uri.TryCreate(CheckQuoted(m_tokenizer.Value), UriKind.Absolute, out parsed)) {
cookie.CommentUri = parsed;
}
}
break;
case CookieToken.Domain:
if (!domainSet) {
domainSet = true;
cookie.Domain = CheckQuoted(m_tokenizer.Value);
cookie.IsQuotedDomain = m_tokenizer.Quoted;
}
break;
case CookieToken.Expires:
if (!expiresSet) {
expiresSet = true;
// ParseCookieDate() does many formats for the date.
// Also note that the parser will eat day of the week
// plus comma and leading spaces
DateTime expires;
if (HttpDateParse.ParseCookieDate(CheckQuoted(m_tokenizer.Value), out expires)) {
cookie.Expires = expires;
}
else {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
case CookieToken.MaxAge:
if (!expiresSet) {
expiresSet = true;
int parsed;
if (int.TryParse(CheckQuoted(m_tokenizer.Value), out parsed)) {
cookie.Expires = DateTime.Now.AddSeconds((double)parsed);
}
else {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
case CookieToken.Path:
if (!pathSet) {
pathSet = true;
cookie.Path = m_tokenizer.Value;
}
break;
case CookieToken.Port:
if (!portSet) {
portSet = true;
try {
cookie.Port = m_tokenizer.Value;
}
catch {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
case CookieToken.Version:
if (!versionSet) {
versionSet = true;
int parsed;
if (int.TryParse(CheckQuoted(m_tokenizer.Value), out parsed)) {
cookie.Version = parsed;
cookie.IsQuotedVersion = m_tokenizer.Quoted;
}
else {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
}
break;
case CookieToken.Attribute:
switch (m_tokenizer.Token) {
case CookieToken.Discard:
if (!discardSet) {
discardSet = true;
cookie.Discard = true;
}
break;
case CookieToken.Secure:
if (!secureSet) {
secureSet = true;
cookie.Secure = true;
}
break;
case CookieToken.HttpOnly:
cookie.HttpOnly = true;
break;
case CookieToken.Port:
if (!portSet) {
portSet = true;
cookie.Port = string.Empty;
}
break;
}
break;
}
}
} while (!m_tokenizer.Eof && !m_tokenizer.EndOfCookie);
return cookie;
}
// twin parsing method, different enough that it's better to split it into
// a different method
internal Cookie GetServer() {
Cookie cookie = m_savedCookie;
m_savedCookie = null;
// only first ocurence of an attribute value must be counted
bool domainSet = false;
bool pathSet = false;
bool portSet = false; //special case as it may have no value in header
do {
bool first = cookie==null || cookie.Name==null || cookie.Name.Length==0;
CookieToken token = m_tokenizer.Next(first, false);
if (first && (token==CookieToken.NameValuePair || token==CookieToken.Attribute)) {
if (cookie==null) {
cookie = new Cookie();
}
if (cookie.InternalSetName(m_tokenizer.Name) == false) {
//will be rejected
cookie.InternalSetName(string.Empty);
}
cookie.Value = m_tokenizer.Value;
}
else {
switch (token) {
case CookieToken.NameValuePair:
switch (m_tokenizer.Token) {
case CookieToken.Domain:
if (!domainSet) {
domainSet = true;
cookie.Domain = CheckQuoted(m_tokenizer.Value);
cookie.IsQuotedDomain = m_tokenizer.Quoted;
}
break;
case CookieToken.Path:
if (!pathSet) {
pathSet = true;
cookie.Path = m_tokenizer.Value;
}
break;
case CookieToken.Port:
if (!portSet) {
portSet = true;
try {
cookie.Port = m_tokenizer.Value;
}
catch (CookieException) {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
case CookieToken.Version:
// this is a new cookie, this token is for the next cookie.
m_savedCookie = new Cookie();
int parsed;
if (int.TryParse(m_tokenizer.Value, out parsed)) {
m_savedCookie.Version = parsed;
}
return cookie;
case CookieToken.Unknown:
// this is a new cookie, the token is for the next cookie.
m_savedCookie = new Cookie();
if (m_savedCookie.InternalSetName(m_tokenizer.Name) == false) {
//will be rejected
m_savedCookie.InternalSetName(string.Empty);
}
m_savedCookie.Value = m_tokenizer.Value;
return cookie;
}
break;
case CookieToken.Attribute:
switch (m_tokenizer.Token) {
case CookieToken.Port:
if (!portSet) {
portSet = true;
cookie.Port = string.Empty;
}
break;
}
break;
}
}
} while (!m_tokenizer.Eof && !m_tokenizer.EndOfCookie);
return cookie;
}
internal static string CheckQuoted(string value) {
if (value.Length < 2 || value[0] != '\"' || value[value.Length-1] != '\"')
return value;
return value.Length == 2? string.Empty: value.Substring(1, value.Length-2);
}
}
internal class Comparer: IComparer {
int IComparer.Compare(object ol, object or) {
Cookie left = (Cookie) ol;
Cookie right = (Cookie) or;
int result;
if ((result = string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase)) != 0) {
return result;
}
if ((result = string.Compare(left.Domain, right.Domain, StringComparison.OrdinalIgnoreCase)) != 0) {
return result;
}
//
//NB: The only path is case sensitive as per spec.
// However, on Win Platform that may break some lazy applications.
//
if ((result = string.Compare(left.Path, right.Path, StringComparison.Ordinal)) != 0) {
return result;
}
// They are equal here even if variants are still different.
return 0;
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------------
namespace System.Net {
using System.Collections;
using System.Globalization;
using System.Threading;
internal enum CookieVariant {
Unknown,
Plain,
Rfc2109,
Rfc2965,
Default = Rfc2109
}
//
// Cookie class
//
// Adheres to RFC 2965
//
// Currently, only client-side cookies. The cookie classes know how to
// parse a set-cookie format string, but not a cookie format string
// (e.g. "Cookie: $Version=1; name=value; $Path=/foo; $Secure")
//
///
/// [To be supplied.]
///
[Serializable]
public sealed class Cookie {
internal const int MaxSupportedVersion = 1;
internal const string CommentAttributeName = "Comment";
internal const string CommentUrlAttributeName = "CommentURL";
internal const string DiscardAttributeName = "Discard";
internal const string DomainAttributeName = "Domain";
internal const string ExpiresAttributeName = "Expires";
internal const string MaxAgeAttributeName = "Max-Age";
internal const string PathAttributeName = "Path";
internal const string PortAttributeName = "Port";
internal const string SecureAttributeName = "Secure";
internal const string VersionAttributeName = "Version";
internal const string HttpOnlyAttributeName = "HttpOnly";
internal const string SeparatorLiteral = "; ";
internal const string EqualsLiteral = "=";
internal const string QuotesLiteral = "\"";
internal const string SpecialAttributeLiteral = "$";
internal static readonly char[] PortSplitDelimiters = new char[] {' ', ',', '\"'};
internal static readonly char[] Reserved2Name = new char[] {' ', '\t', '\r', '\n', '=', ';', ',' };
internal static readonly char[] Reserved2Value = new char[] {';', ',' };
private static Comparer staticComparer = new Comparer();
// fields
string m_comment = string.Empty;
Uri m_commentUri = null;
CookieVariant m_cookieVariant = CookieVariant.Plain;
bool m_discard = false;
string m_domain = string.Empty;
bool m_domain_implicit = true;
DateTime m_expires = DateTime.MinValue;
string m_name = string.Empty;
string m_path = string.Empty;
bool m_path_implicit = true;
string m_port = string.Empty;
bool m_port_implicit = true;
int[] m_port_list = null;
bool m_secure = false;
[System.Runtime.Serialization.OptionalField]
bool m_httpOnly=false;
DateTime m_timeStamp = DateTime.Now;
string m_value = string.Empty;
int m_version = 0;
string m_domainKey = string.Empty;
internal bool IsQuotedVersion = false;
internal bool IsQuotedDomain = false;
// constructors
///
/// [To be supplied.]
///
public Cookie() {
}
//public Cookie(string cookie) {
// if ((cookie == null) || (cookie == String.Empty)) {
// throw new ArgumentException("cookie");
// }
// Parse(cookie.Trim());
// Validate();
//}
///
/// [To be supplied.]
///
public Cookie(string name, string value) {
Name = name;
m_value = value;
}
///
/// [To be supplied.]
///
public Cookie(string name, string value, string path)
: this(name, value) {
Path = path;
}
///
/// [To be supplied.]
///
public Cookie(string name, string value, string path, string domain)
: this(name, value, path) {
Domain = domain;
}
// properties
///
/// [To be supplied.]
///
public string Comment {
get {
return m_comment;
}
set {
if (value == null) {
value = string.Empty;
}
m_comment = value;
}
}
///
/// [To be supplied.]
///
public Uri CommentUri {
get {
return m_commentUri;
}
set {
m_commentUri = value;
}
}
public bool HttpOnly{
get{
return m_httpOnly;
}
set{
m_httpOnly = value;
}
}
///
/// [To be supplied.]
///
public bool Discard {
get {
return m_discard;
}
set {
m_discard = value;
}
}
///
/// [To be supplied.]
///
public string Domain {
get {
return m_domain;
}
set {
m_domain = (value == null? String.Empty : value);
m_domain_implicit = false;
m_domainKey = string.Empty; //this will get it value when adding into the Container.
}
}
private string _Domain {
get {
return (Plain || m_domain_implicit || (m_domain.Length == 0))
? string.Empty
: (SpecialAttributeLiteral
+ DomainAttributeName
+ EqualsLiteral + (IsQuotedDomain? "\"": string.Empty)
+ m_domain + (IsQuotedDomain? "\"": string.Empty)
);
}
}
///
/// [To be supplied.]
///
public bool Expired {
get {
return (m_expires <= DateTime.Now) && (m_expires != DateTime.MinValue);
}
set {
if (value == true) {
m_expires = DateTime.Now;
}
}
}
///
/// [To be supplied.]
///
public DateTime Expires {
get {
return m_expires;
}
set {
m_expires = value;
}
}
///
/// [To be supplied.]
///
public string Name {
get {
return m_name;
}
set {
if (ValidationHelper.IsBlankString(value) || !InternalSetName(value)) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Name", value == null? "": value));
}
}
}
internal bool InternalSetName(string value) {
if (ValidationHelper.IsBlankString(value) || value[0] == '$' || value.IndexOfAny(Reserved2Name) != -1) {
m_name = string.Empty;
return false;
}
m_name = value;
return true;
}
///
/// [To be supplied.]
///
public string Path {
get {
return m_path;
}
set {
m_path = (value == null? String.Empty : value);
m_path_implicit = false;
}
}
private string _Path {
get {
return (Plain || m_path_implicit || (m_path.Length == 0))
? string.Empty
: (SpecialAttributeLiteral
+ PathAttributeName
+ EqualsLiteral
+ m_path
);
}
}
internal bool Plain {
get {
return Variant == CookieVariant.Plain;
}
}
//
// According to spec we must assume default values for attributes but still
// keep in mind that we must not include them into the requests.
// We also check the validiy of all attributes based on the version and variant (read RFC)
//
// To work properly this function must be called after cookie construction with
// default (response) URI AND set_default == true
//
// Afterwards, the function can be called many times with other URIs and
// set_default == false to check whether this cookie matches given uri
//
internal bool VerifySetDefaults(CookieVariant variant, Uri uri, bool isLocalDomain, string localDomain, bool set_default, bool isThrow) {
string host = uri.Host;
int port = uri.Port;
string path = uri.AbsolutePath;
bool valid= true;
if (set_default) {
// Set Variant. If version is zero => reset cookie to Version0 style
if (Version == 0) {
variant = CookieVariant.Plain;
}
else if (Version == 1 && variant == CookieVariant.Unknown) {
//since we don't expose Variant to an app, set it to Default
variant = CookieVariant.Default;
}
m_cookieVariant = variant;
}
//Check the name
if (m_name == null || m_name.Length == 0 || m_name[0] == '$' || m_name.IndexOfAny(Reserved2Name) != -1) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Name", m_name == null? "": m_name));
}
return false;
}
//Check the value
if (m_value == null ||
(!(m_value.Length > 2 && m_value[0] == '\"' && m_value[m_value.Length-1] == '\"') && m_value.IndexOfAny(Reserved2Value) != -1)) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Value", m_value == null? "": m_value));
}
return false;
}
//Check Comment syntax
if (Comment != null && !(Comment.Length > 2 && Comment[0] == '\"' && Comment[Comment.Length-1] == '\"')
&& (Comment.IndexOfAny(Reserved2Value) != -1)) {
if (isThrow)
throw new CookieException(SR.GetString(SR.net_cookie_attribute, CommentAttributeName, Comment));
return false;
}
//Check Path syntax
if (Path != null && !(Path.Length > 2 && Path[0] == '\"' && Path[Path.Length-1] == '\"')
&& (Path.IndexOfAny(Reserved2Value) != -1)) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PathAttributeName, Path));
}
return false;
}
//Check/set domain
// if domain is implicit => assume a) uri is valid, b) just set domain to uri hostname
if (set_default && m_domain_implicit == true) {
m_domain = host;
}
else {
if (!m_domain_implicit) {
// Forwarding note: If Uri.Host is of IP address form then the only supported case
// is for IMPLICIT domain property of a cookie.
// The below code (explicit cookie.Domain value) will try to parse Uri.Host IP string
// as a fqdn and reject the cookie
//aliasing since we might need the KeyValue (but not the original one)
string domain = m_domain;
//Syntax check for Domain charset plus empty string
if (!DomainCharsTest(domain)) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, DomainAttributeName, domain == null? "": domain));
}
return false;
}
//domain must start with '.' if set explicitly
if(domain[0] != '.' ) {
if (!(variant == CookieVariant.Rfc2965 || variant == CookieVariant.Plain)) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, DomainAttributeName, m_domain));
}
return false;
}
domain = '.' + domain;
}
int host_dot = host.IndexOf('.');
bool is_local = false;
// First quick check is for pushing a cookie into the local domain
if (isLocalDomain && (string.Compare(localDomain, domain, StringComparison.OrdinalIgnoreCase ) == 0)) {
valid = true;
}
else if (domain.Length < 4 || (!(is_local = (string.Compare(domain, ".local", StringComparison.OrdinalIgnoreCase)) == 0) && domain.IndexOf('.', 1, domain.Length-2) == -1)) {
// explicit domains must contain 'inside' dots or be ".local"
valid = false;
}
else if (is_local && isLocalDomain) {
// valid ".local" found for the local domain Uri
valid = true;
}
else if (is_local && !isLocalDomain) {
// ".local" cannot happen on non-local domain
valid = false;
}
else if (variant == CookieVariant.Plain) {
// We distiguish between Version0 cookie and other versions on domain issue
// According to Version0 spec a domain must be just a substring of the hostname
if (!(host.Length + 1 == domain.Length && string.Compare(host, 0, domain, 1, host.Length, StringComparison.OrdinalIgnoreCase) == 0)) {
if (host.Length <= domain.Length ||
string.Compare(host, host.Length-domain.Length, domain, 0, domain.Length, StringComparison.OrdinalIgnoreCase) != 0) {
valid = false;
}
}
}
else if ( !is_local &&
(host_dot == -1 ||
domain.Length != host.Length-host_dot ||
string.Compare(host, host_dot, domain, 0, domain.Length, StringComparison.OrdinalIgnoreCase) != 0)) {
//starting the first dot, the host must match the domain
valid = false;
}
if (valid) {
if(is_local) {
m_domainKey = localDomain.ToLower(CultureInfo.InvariantCulture);
}
else {
m_domainKey = domain.ToLower(CultureInfo.InvariantCulture);
}
}
}
else {
// for implicitly set domain AND at the set_default==false time
// we simply got to match uri.Host against m_domain
if (string.Compare(host, m_domain, StringComparison.OrdinalIgnoreCase) != 0) {
valid = false;
}
}
if(!valid) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, DomainAttributeName, m_domain));
}
return false;
}
}
//Check/Set Path
if (set_default && m_path_implicit == true) {
//assuming that uri path is always valid and contains at least one '/'
switch (m_cookieVariant) {
case CookieVariant.Plain:
m_path = path;
break;
case CookieVariant.Rfc2109:
m_path = path.Substring(0, path.LastIndexOf('/')); //may be empty
break;
case CookieVariant.Rfc2965:
default: //hope future versions will have same 'Path' semantic?
m_path = path.Substring(0, path.LastIndexOf('/')+1);
break;
}
}
else {
//check current path (implicit/explicit) against given uri
if (!path.StartsWith(CookieParser.CheckQuoted(m_path))) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PathAttributeName, m_path));
}
return false;
}
}
// set the default port if Port attribute was present but had no value
if (set_default && (m_port_implicit == false && m_port.Length == 0)) {
m_port_list = new int[1] {port};
}
if(m_port_implicit == false) {
// Port must match agaist the one from the uri
valid = false;
foreach (int p in m_port_list) {
if (p == port) {
valid = true;
break;
}
}
if (!valid) {
if (isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port));
}
return false;
}
}
return true;
}
//Very primitive test to make sure that the name does not have illegal characters
// As per RFC 952 (relaxed on first char could be a digit and string can have '_')
private static bool DomainCharsTest(string name) {
if (name == null || name.Length == 0) {
return false;
}
for(int i=0; i < name.Length; ++i) {
char ch = name[i];
if (!(
(ch >= '0' && ch <= '9') ||
(ch == '.' || ch == '-') ||
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch == '_')
))
return false;
}
return true;
}
///
/// [To be supplied.]
///
public string Port {
get {
return m_port;
}
set {
m_port_implicit = false;
if ((value == null || value.Length == 0)) {
//"Port" is present but has no value.
m_port = string.Empty;
}
else {
m_port = value;
// Parse port list
if (value[0] != '\"' || value[value.Length-1] != '\"') {
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port));
}
string[] ports = value.Split(PortSplitDelimiters);
m_port_list = new int[ports.Length];
for (int i = 0; i < ports.Length; ++i) {
m_port_list[i] = -1;
if(ports[i].Length == 0) {
//ignore spaces this way, and leave port=-1 in the slot
continue;
}
if (!Int32.TryParse(ports[i], out m_port_list[i]))
throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port));
}
m_version = MaxSupportedVersion;
m_cookieVariant = CookieVariant.Rfc2965;
}
}
}
internal int[] PortList {
get {
//It must be null if Port Attribute was ommitted in the response
return m_port_list;
}
}
private string _Port {
get {
return m_port_implicit ? string.Empty :
(SpecialAttributeLiteral
+ PortAttributeName
+ ((m_port.Length == 0) ? string.Empty : (EqualsLiteral + m_port))
);
}
}
///
/// [To be supplied.]
///
public bool Secure {
get {
return m_secure;
}
set {
m_secure = value;
}
}
///
/// [To be supplied.]
///
public DateTime TimeStamp {
get {
return m_timeStamp;
}
}
///
/// [To be supplied.]
///
public string Value {
get {
return m_value;
}
set {
m_value = (value == null? String.Empty : value);
}
}
internal CookieVariant Variant {
get {
return m_cookieVariant;
}
set {
// only set by HttpListenerRequest::Cookies_get()
GlobalLog.Assert(value == CookieVariant.Rfc2965, "Cookie#{0}::set_Variant()|value:{1}" , ValidationHelper.HashString(this), value);
m_cookieVariant = value;
}
}
// m_domainKey member is set internally in VerifySetDefaults()
// If it is not set then verification function was not called
// and this should never happen
internal string DomainKey {
get {
return m_domain_implicit ? Domain : m_domainKey;
}
}
//public Version Version {
///
/// [To be supplied.]
///
public int Version {
get {
return m_version;
}
set {
if (value<0) {
throw new ArgumentOutOfRangeException("value");
}
m_version = value;
if (value>0 && m_cookieVariant
/// [To be supplied.]
///
public override bool Equals(object comparand) {
if (!(comparand is Cookie)) {
return false;
}
Cookie other = (Cookie)comparand;
return (string.Compare(Name, other.Name, StringComparison.OrdinalIgnoreCase) == 0)
&& (string.Compare(Value, other.Value, StringComparison.Ordinal) == 0)
&& (string.Compare(Path, other.Path, StringComparison.Ordinal) == 0)
&& (string.Compare(Domain, other.Domain, StringComparison.OrdinalIgnoreCase) == 0)
&& (Version == other.Version)
;
}
///
/// [To be supplied.]
///
public override int GetHashCode() {
//
//string hashString = Name + "=" + Value + ";" + Path + "; " + Domain + "; " + Version;
//int hash = 0;
//
//foreach (char ch in hashString) {
// hash = unchecked(hash << 1 ^ (int)ch);
//}
//return hash;
return (Name + "=" + Value + ";" + Path + "; " + Domain + "; " + Version).GetHashCode();
}
///
/// [To be supplied.]
///
public override string ToString() {
string domain = _Domain;
string path = _Path;
string port = _Port;
string version = _Version;
string result =
((version.Length == 0)? string.Empty : (version + SeparatorLiteral))
+ Name + EqualsLiteral + Value
+ ((path.Length == 0) ? string.Empty : (SeparatorLiteral + path))
+ ((domain.Length == 0) ? string.Empty : (SeparatorLiteral + domain))
+ ((port.Length == 0) ? string.Empty : (SeparatorLiteral + port))
;
if (result == "=") {
return string.Empty;
}
return result;
}
internal string ToServerString() {
string result = Name + EqualsLiteral + Value;
if (m_comment!=null && m_comment.Length>0) {
result += SeparatorLiteral + CommentAttributeName + EqualsLiteral + m_comment;
}
if (m_commentUri!=null) {
result += SeparatorLiteral + CommentUrlAttributeName + EqualsLiteral + QuotesLiteral + m_commentUri.ToString() + QuotesLiteral;
}
if (m_discard) {
result += SeparatorLiteral + DiscardAttributeName;
}
if (!Plain && !m_domain_implicit && m_domain!=null && m_domain.Length>0) {
result += SeparatorLiteral + DomainAttributeName + EqualsLiteral + m_domain;
}
int seconds = (Expires-DateTime.UtcNow).Seconds;
if (seconds>0) {
result += SeparatorLiteral + MaxAgeAttributeName + EqualsLiteral + seconds.ToString(NumberFormatInfo.InvariantInfo);
}
if (!Plain && !m_path_implicit && m_path!=null && m_path.Length>0) {
result += SeparatorLiteral + PathAttributeName + EqualsLiteral + m_path;
}
if (!Plain && !m_port_implicit && m_port!=null && m_port.Length>0) {
// QuotesLiteral are included in m_port
result += SeparatorLiteral + PortAttributeName + EqualsLiteral + m_port;
}
if (m_version>0) {
result += SeparatorLiteral + VersionAttributeName + EqualsLiteral + m_version.ToString(NumberFormatInfo.InvariantInfo);
}
return result==EqualsLiteral ? null : result;
}
//private void Validate() {
// if ((m_name == String.Empty) && (m_value == String.Empty)) {
// throw new CookieException();
// }
// if ((m_name != String.Empty) && (m_name[0] == '$')) {
// throw new CookieException();
// }
//}
#if DEBUG
///
/// [To be supplied.]
///
internal void Dump() {
GlobalLog.Print("Cookie: " + ToString() + "->\n"
+ "\tComment = " + Comment + "\n"
+ "\tCommentUri = " + CommentUri + "\n"
+ "\tDiscard = " + Discard + "\n"
+ "\tDomain = " + Domain + "\n"
+ "\tExpired = " + Expired + "\n"
+ "\tExpires = " + Expires + "\n"
+ "\tName = " + Name + "\n"
+ "\tPath = " + Path + "\n"
+ "\tPort = " + Port + "\n"
+ "\tSecure = " + Secure + "\n"
+ "\tTimeStamp = " + TimeStamp + "\n"
+ "\tValue = " + Value + "\n"
+ "\tVariant = " + Variant + "\n"
+ "\tVersion = " + Version + "\n"
+ "\tHttpOnly = " + HttpOnly + "\n"
);
}
#endif
}
internal enum CookieToken {
// state types
Nothing,
NameValuePair, // X=Y
Attribute, // X
EndToken, // ';'
EndCookie, // ','
End, // EOLN
Equals,
// value types
Comment,
CommentUrl,
CookieName,
Discard,
Domain,
Expires,
MaxAge,
Path,
Port,
Secure,
HttpOnly,
Unknown,
Version
}
//
// CookieTokenizer
//
// Used to split a single or multi-cookie (header) string into individual
// tokens
//
internal class CookieTokenizer {
// fields
bool m_eofCookie;
int m_index;
int m_length;
string m_name;
bool m_quoted;
int m_start;
CookieToken m_token;
int m_tokenLength;
string m_tokenStream;
string m_value;
// constructors
internal CookieTokenizer(string tokenStream) {
m_length = tokenStream.Length;
m_tokenStream = tokenStream;
}
// properties
internal bool EndOfCookie {
get {
return m_eofCookie;
}
set {
m_eofCookie = value;
}
}
internal bool Eof {
get {
return m_index >= m_length;
}
}
internal string Name {
get {
return m_name;
}
set {
m_name = value;
}
}
internal bool Quoted {
get {
return m_quoted;
}
set {
m_quoted = value;
}
}
internal CookieToken Token {
get {
return m_token;
}
set {
m_token = value;
}
}
internal string Value {
get {
return m_value;
}
set {
m_value = value;
}
}
// methods
//
// Extract
//
// extract the current token
//
internal string Extract() {
string tokenString = string.Empty;
if (m_tokenLength != 0) {
tokenString = m_tokenStream.Substring(m_start, m_tokenLength);
if (!Quoted) {
tokenString = tokenString.Trim();
}
}
return tokenString;
}
//
// FindNext
//
// Find the start and length of the next token. The token is terminated
// by one of:
//
// - end-of-line
// - end-of-cookie: unquoted comma separates multiple cookies
// - end-of-token: unquoted semi-colon
// - end-of-name: unquoted equals
//
// Inputs:
// ignoreComma
// true if parsing doesn't stop at a comma. This is only true when
// we know we're parsing an original cookie that has an expires=
// attribute, because the format of the time/date used in expires
// is:
// Wdy, dd-mmm-yyyy HH:MM:SS GMT
//
// ignoreEquals
// true if parsing doesn't stop at an equals sign. The LHS of the
// first equals sign is an attribute name. The next token may
// include one or more equals signs. E.g.,
//
// SESSIONID=ID=MSNx45&q=33
//
// Outputs:
// m_index
// incremented to the last position in m_tokenStream contained by
// the current token
//
// m_start
// incremented to the start of the current token
//
// m_tokenLength
// set to the length of the current token
//
// Assumes:
// Nothing
//
// Returns:
// type of CookieToken found:
//
// End - end of the cookie string
// EndCookie - end of current cookie in (potentially) a
// multi-cookie string
// EndToken - end of name=value pair, or end of an attribute
// Equals - end of name=
//
// Throws:
// Nothing
//
internal CookieToken FindNext(bool ignoreComma, bool ignoreEquals) {
m_tokenLength = 0;
m_start = m_index;
while ((m_index < m_length) && Char.IsWhiteSpace(m_tokenStream[m_index])) {
++m_index;
++m_start;
}
CookieToken token = CookieToken.End;
int increment = 1;
if (!Eof) {
if (m_tokenStream[m_index] == '"') {
Quoted = true;
++m_index;
bool quoteOn = false;
while (m_index < m_length) {
char currChar = m_tokenStream[m_index];
if (!quoteOn && currChar == '"')
break;
if (quoteOn)
quoteOn = false;
else if (currChar == '\\')
quoteOn = true;
++m_index;
}
if (m_index < m_length) {
++m_index;
}
m_tokenLength = m_index - m_start;
increment = 0;
// if we are here, reset ignoreComma
// In effect, we ignore everything after quoted string till next delimiter
ignoreComma = false;
}
while ((m_index < m_length)
&& (m_tokenStream[m_index] != ';')
&& (ignoreEquals || (m_tokenStream[m_index] != '='))
&& (ignoreComma || (m_tokenStream[m_index] != ','))) {
// Fixing 2 things:
// 1) ignore day of week in cookie string
// 2) revert ignoreComma once meet it, so won't miss the next cookie)
if (m_tokenStream[m_index] == ',') {
m_start = m_index+1;
m_tokenLength = -1;
ignoreComma = false;
}
++m_index;
m_tokenLength += increment;
}
if (!Eof) {
switch (m_tokenStream[m_index]) {
case ';':
token = CookieToken.EndToken;
break;
case '=':
token = CookieToken.Equals;
break;
default:
token = CookieToken.EndCookie;
break;
}
++m_index;
}
}
return token;
}
//
// Next
//
// Get the next cookie name/value or attribute
//
// Cookies come in the following formats:
//
// 1. Version0
// Set-Cookie: [][=][]
// [; expires=]
// [; path=]
// [; domain=]
// [; secure]
// Cookie: =
//
// Notes: and/or may be blank
// is the RFC 822/1123 date format that
// incorporates commas, e.g.
// "Wednesday, 09-Nov-99 23:12:40 GMT"
//
// 2. RFC 2109
// Set-Cookie: 1#{
// =
// [; comment=]
// [; domain=]
// [; max-age=]
// [; path=]
// [; secure]
// ; Version=
// }
// Cookie: $Version=
// 1#{
// ; =
// [; path=]
// [; domain=]
// }
//
// 3. RFC 2965
// Set-Cookie2: 1#{
// =
// [; comment=]
// [; commentURL=]
// [; discard]
// [; domain=]
// [; max-age=]
// [; path=]
// [; ports=]
// [; secure]
// ; Version=
// }
// Cookie: $Version=
// 1#{
// ; =
// [; path=]
// [; domain=]
// [; port=""]
// }
// [Cookie2: $Version=]
//
// Inputs:
// first
// true if this is the first name/attribute that we have looked for
// in the cookie stream
//
// Outputs:
//
// Assumes:
// Nothing
//
// Returns:
// type of CookieToken found:
//
// - Attribute
// - token was single-value. May be empty. Caller should check
// Eof or EndCookie to determine if any more action needs to
// be taken
//
// - NameValuePair
// - Name and Value are meaningful. Either may be empty
//
// Throws:
// Nothing
//
internal CookieToken Next(bool first, bool parseResponseCookies) {
Reset();
CookieToken terminator = FindNext(false, false);
if (terminator == CookieToken.EndCookie) {
EndOfCookie = true;
}
if ((terminator == CookieToken.End) || (terminator == CookieToken.EndCookie)) {
if ((Name = Extract()).Length != 0) {
Token = TokenFromName(parseResponseCookies);
return CookieToken.Attribute;
}
return terminator;
}
Name = Extract();
if (first) {
Token = CookieToken.CookieName;
}
else {
Token = TokenFromName(parseResponseCookies);
}
if (terminator == CookieToken.Equals) {
terminator = FindNext(!first && (Token == CookieToken.Expires), true);
if (terminator == CookieToken.EndCookie) {
EndOfCookie = true;
}
Value = Extract();
return CookieToken.NameValuePair;
}
else {
return CookieToken.Attribute;
}
}
//
// Reset
//
// set up this tokenizer for finding the next name/value pair or
// attribute, or end-of-[token, cookie, or line]
//
internal void Reset() {
m_eofCookie = false;
m_name = string.Empty;
m_quoted = false;
m_start = m_index;
m_token = CookieToken.Nothing;
m_tokenLength = 0;
m_value = string.Empty;
}
private struct RecognizedAttribute {
string m_name;
CookieToken m_token;
internal RecognizedAttribute(string name, CookieToken token) {
m_name = name;
m_token = token;
}
internal CookieToken Token {
get {
return m_token;
}
}
internal bool IsEqualTo(string value) {
return string.Compare(m_name, value, StringComparison.OrdinalIgnoreCase) == 0;
}
}
//
// recognized attributes in order of expected commonality
//
static RecognizedAttribute[] RecognizedAttributes = {
new RecognizedAttribute(Cookie.PathAttributeName, CookieToken.Path),
new RecognizedAttribute(Cookie.MaxAgeAttributeName, CookieToken.MaxAge),
new RecognizedAttribute(Cookie.ExpiresAttributeName, CookieToken.Expires),
new RecognizedAttribute(Cookie.VersionAttributeName, CookieToken.Version),
new RecognizedAttribute(Cookie.DomainAttributeName, CookieToken.Domain),
new RecognizedAttribute(Cookie.SecureAttributeName, CookieToken.Secure),
new RecognizedAttribute(Cookie.DiscardAttributeName, CookieToken.Discard),
new RecognizedAttribute(Cookie.PortAttributeName, CookieToken.Port),
new RecognizedAttribute(Cookie.CommentAttributeName, CookieToken.Comment),
new RecognizedAttribute(Cookie.CommentUrlAttributeName, CookieToken.CommentUrl),
new RecognizedAttribute(Cookie.HttpOnlyAttributeName, CookieToken.HttpOnly),
};
static RecognizedAttribute[] RecognizedServerAttributes = {
new RecognizedAttribute('$' + Cookie.PathAttributeName, CookieToken.Path),
new RecognizedAttribute('$' + Cookie.VersionAttributeName, CookieToken.Version),
new RecognizedAttribute('$' + Cookie.DomainAttributeName, CookieToken.Domain),
new RecognizedAttribute('$' + Cookie.PortAttributeName, CookieToken.Port),
new RecognizedAttribute('$' + Cookie.HttpOnlyAttributeName, CookieToken.HttpOnly),
};
internal CookieToken TokenFromName(bool parseResponseCookies) {
if (!parseResponseCookies) {
for (int i = 0; i < RecognizedServerAttributes.Length; ++i) {
if (RecognizedServerAttributes[i].IsEqualTo(Name)) {
return RecognizedServerAttributes[i].Token;
}
}
}
else {
for (int i = 0; i < RecognizedAttributes.Length; ++i) {
if (RecognizedAttributes[i].IsEqualTo(Name)) {
return RecognizedAttributes[i].Token;
}
}
}
return CookieToken.Unknown;
}
}
//
// CookieParser
//
// Takes a cookie header, makes cookies
//
internal class CookieParser {
// fields
CookieTokenizer m_tokenizer;
Cookie m_savedCookie;
// constructors
internal CookieParser(string cookieString) {
m_tokenizer = new CookieTokenizer(cookieString);
}
// properties
// methods
//
// Get
//
// Gets the next cookie
//
// Inputs:
// Nothing
//
// Outputs:
// Nothing
//
// Assumes:
// Nothing
//
// Returns:
// new cookie object, or null if there's no more
//
// Throws:
// Nothing
//
internal Cookie Get() {
Cookie cookie = null;
// only first ocurence of an attribute value must be counted
bool commentSet = false;
bool commentUriSet = false;
bool domainSet = false;
bool expiresSet = false;
bool pathSet = false;
bool portSet = false; //special case as it may have no value in header
bool versionSet = false;
bool secureSet = false;
bool discardSet = false;
do {
CookieToken token = m_tokenizer.Next(cookie==null, true);
if (cookie==null && (token==CookieToken.NameValuePair || token==CookieToken.Attribute)) {
cookie = new Cookie();
if (cookie.InternalSetName(m_tokenizer.Name) == false) {
//will be rejected
cookie.InternalSetName(string.Empty);
}
cookie.Value = m_tokenizer.Value;
}
else {
switch (token) {
case CookieToken.NameValuePair:
switch (m_tokenizer.Token) {
case CookieToken.Comment:
if (!commentSet) {
commentSet = true;
cookie.Comment = m_tokenizer.Value;
}
break;
case CookieToken.CommentUrl:
if (!commentUriSet) {
commentUriSet = true;
Uri parsed;
if (Uri.TryCreate(CheckQuoted(m_tokenizer.Value), UriKind.Absolute, out parsed)) {
cookie.CommentUri = parsed;
}
}
break;
case CookieToken.Domain:
if (!domainSet) {
domainSet = true;
cookie.Domain = CheckQuoted(m_tokenizer.Value);
cookie.IsQuotedDomain = m_tokenizer.Quoted;
}
break;
case CookieToken.Expires:
if (!expiresSet) {
expiresSet = true;
// ParseCookieDate() does many formats for the date.
// Also note that the parser will eat day of the week
// plus comma and leading spaces
DateTime expires;
if (HttpDateParse.ParseCookieDate(CheckQuoted(m_tokenizer.Value), out expires)) {
cookie.Expires = expires;
}
else {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
case CookieToken.MaxAge:
if (!expiresSet) {
expiresSet = true;
int parsed;
if (int.TryParse(CheckQuoted(m_tokenizer.Value), out parsed)) {
cookie.Expires = DateTime.Now.AddSeconds((double)parsed);
}
else {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
case CookieToken.Path:
if (!pathSet) {
pathSet = true;
cookie.Path = m_tokenizer.Value;
}
break;
case CookieToken.Port:
if (!portSet) {
portSet = true;
try {
cookie.Port = m_tokenizer.Value;
}
catch {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
case CookieToken.Version:
if (!versionSet) {
versionSet = true;
int parsed;
if (int.TryParse(CheckQuoted(m_tokenizer.Value), out parsed)) {
cookie.Version = parsed;
cookie.IsQuotedVersion = m_tokenizer.Quoted;
}
else {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
}
break;
case CookieToken.Attribute:
switch (m_tokenizer.Token) {
case CookieToken.Discard:
if (!discardSet) {
discardSet = true;
cookie.Discard = true;
}
break;
case CookieToken.Secure:
if (!secureSet) {
secureSet = true;
cookie.Secure = true;
}
break;
case CookieToken.HttpOnly:
cookie.HttpOnly = true;
break;
case CookieToken.Port:
if (!portSet) {
portSet = true;
cookie.Port = string.Empty;
}
break;
}
break;
}
}
} while (!m_tokenizer.Eof && !m_tokenizer.EndOfCookie);
return cookie;
}
// twin parsing method, different enough that it's better to split it into
// a different method
internal Cookie GetServer() {
Cookie cookie = m_savedCookie;
m_savedCookie = null;
// only first ocurence of an attribute value must be counted
bool domainSet = false;
bool pathSet = false;
bool portSet = false; //special case as it may have no value in header
do {
bool first = cookie==null || cookie.Name==null || cookie.Name.Length==0;
CookieToken token = m_tokenizer.Next(first, false);
if (first && (token==CookieToken.NameValuePair || token==CookieToken.Attribute)) {
if (cookie==null) {
cookie = new Cookie();
}
if (cookie.InternalSetName(m_tokenizer.Name) == false) {
//will be rejected
cookie.InternalSetName(string.Empty);
}
cookie.Value = m_tokenizer.Value;
}
else {
switch (token) {
case CookieToken.NameValuePair:
switch (m_tokenizer.Token) {
case CookieToken.Domain:
if (!domainSet) {
domainSet = true;
cookie.Domain = CheckQuoted(m_tokenizer.Value);
cookie.IsQuotedDomain = m_tokenizer.Quoted;
}
break;
case CookieToken.Path:
if (!pathSet) {
pathSet = true;
cookie.Path = m_tokenizer.Value;
}
break;
case CookieToken.Port:
if (!portSet) {
portSet = true;
try {
cookie.Port = m_tokenizer.Value;
}
catch (CookieException) {
//this cookie will be rejected
cookie.InternalSetName(string.Empty);
}
}
break;
case CookieToken.Version:
// this is a new cookie, this token is for the next cookie.
m_savedCookie = new Cookie();
int parsed;
if (int.TryParse(m_tokenizer.Value, out parsed)) {
m_savedCookie.Version = parsed;
}
return cookie;
case CookieToken.Unknown:
// this is a new cookie, the token is for the next cookie.
m_savedCookie = new Cookie();
if (m_savedCookie.InternalSetName(m_tokenizer.Name) == false) {
//will be rejected
m_savedCookie.InternalSetName(string.Empty);
}
m_savedCookie.Value = m_tokenizer.Value;
return cookie;
}
break;
case CookieToken.Attribute:
switch (m_tokenizer.Token) {
case CookieToken.Port:
if (!portSet) {
portSet = true;
cookie.Port = string.Empty;
}
break;
}
break;
}
}
} while (!m_tokenizer.Eof && !m_tokenizer.EndOfCookie);
return cookie;
}
internal static string CheckQuoted(string value) {
if (value.Length < 2 || value[0] != '\"' || value[value.Length-1] != '\"')
return value;
return value.Length == 2? string.Empty: value.Substring(1, value.Length-2);
}
}
internal class Comparer: IComparer {
int IComparer.Compare(object ol, object or) {
Cookie left = (Cookie) ol;
Cookie right = (Cookie) or;
int result;
if ((result = string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase)) != 0) {
return result;
}
if ((result = string.Compare(left.Domain, right.Domain, StringComparison.OrdinalIgnoreCase)) != 0) {
return result;
}
//
//NB: The only path is case sensitive as per spec.
// However, on Win Platform that may break some lazy applications.
//
if ((result = string.Compare(left.Path, right.Path, StringComparison.Ordinal)) != 0) {
return result;
}
// They are equal here even if variants are still different.
return 0;
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- EntityConnectionStringBuilderItem.cs
- GZipDecoder.cs
- ExpressionBinding.cs
- BinaryNode.cs
- LinkGrep.cs
- HttpListener.cs
- ContextMarshalException.cs
- HtmlInputControl.cs
- Ticks.cs
- AxDesigner.cs
- XmlILStorageConverter.cs
- DataGridTextBoxColumn.cs
- FontFamilyConverter.cs
- BuildResult.cs
- HttpListenerTimeoutManager.cs
- ViewPort3D.cs
- FormsAuthenticationModule.cs
- DataGridViewAutoSizeModeEventArgs.cs
- SqlNamer.cs
- CheckoutException.cs
- UnsafeNativeMethods.cs
- CompiledIdentityConstraint.cs
- WorkflowInspectionServices.cs
- HttpTransportManager.cs
- LayoutInformation.cs
- ContextBase.cs
- ImmutableObjectAttribute.cs
- RowToFieldTransformer.cs
- OrderByExpression.cs
- SoapClientProtocol.cs
- PersonalizationState.cs
- Mapping.cs
- WaitHandleCannotBeOpenedException.cs
- TdsParserHelperClasses.cs
- JpegBitmapDecoder.cs
- QueryExpr.cs
- PlanCompiler.cs
- CodeArrayCreateExpression.cs
- DbTransaction.cs
- InkCanvasAutomationPeer.cs
- TraceUtility.cs
- Win32.cs
- TextProperties.cs
- SortedSet.cs
- SpecialFolderEnumConverter.cs
- StylusTip.cs
- FormViewUpdatedEventArgs.cs
- Base64Stream.cs
- XmlQualifiedName.cs
- ThicknessAnimationBase.cs
- StyleXamlTreeBuilder.cs
- FolderBrowserDialog.cs
- _NegoStream.cs
- MethodSignatureGenerator.cs
- WaitForChangedResult.cs
- Ray3DHitTestResult.cs
- TypePropertyEditor.cs
- XmlSchemaObject.cs
- TypeNameHelper.cs
- WebBrowserNavigatedEventHandler.cs
- WindowsListViewGroupSubsetLink.cs
- AssemblyNameProxy.cs
- InvalidateEvent.cs
- CachedCompositeFamily.cs
- SpeechSynthesizer.cs
- COM2FontConverter.cs
- PathSegmentCollection.cs
- HttpListenerResponse.cs
- ProxyWebPartManagerDesigner.cs
- TitleStyle.cs
- PropertyGrid.cs
- FlowDocument.cs
- _ListenerAsyncResult.cs
- ErrorFormatter.cs
- UserPreferenceChangingEventArgs.cs
- ScrollEventArgs.cs
- ConnectionsZoneDesigner.cs
- OleDbConnection.cs
- WarningException.cs
- Timer.cs
- ExpanderAutomationPeer.cs
- LinkedResourceCollection.cs
- TrackingMemoryStream.cs
- RuntimeConfigLKG.cs
- ContextProperty.cs
- TabItemWrapperAutomationPeer.cs
- processwaithandle.cs
- CachedFontFace.cs
- ThemeableAttribute.cs
- KeyedHashAlgorithm.cs
- WorkflowInstance.cs
- Timeline.cs
- LocationChangedEventArgs.cs
- EdmProviderManifest.cs
- BitmapEncoder.cs
- IpcChannel.cs
- MimeFormReflector.cs
- BadImageFormatException.cs
- WebPartZoneCollection.cs
- WindowsSolidBrush.cs