|
//------------------------------------------------------------------------------
// <copyright file="cookiecontainer.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
// Relevant cookie specs:
//
// PERSISTENT CLIENT STATE HTTP COOKIES (1996)
// From <http://web.archive.org/web/20020803110822/http://wp.netscape.com/newsref/std/cookie_spec.html>
//
// RFC2109 HTTP State Management Mechanism (February 1997)
// From <http://tools.ietf.org/html/rfc2109>
//
// RFC2965 HTTP State Management Mechanism (October 2000)
// From <http://tools.ietf.org/html/rfc2965>
//
// RFC6265 HTTP State Management Mechanism (April 2011)
// From <http://tools.ietf.org/html/rfc6265>
//
// The Version attribute of the cookie header is defined and used only in RFC2109 and RFC2965 cookie
// specs and specifies Version=1. The Version attribute is not used in the Netscape cookie spec
// (considered as Version=0). Nor is it used in the most recent cookie spec, RFC6265, introduced in 2011.
// RFC6265 deprecates all previous cookie specs including the Version attribute.
//
// Cookies without an explicit Domain attribute will only match a potential uri that matches the original
// uri from where the cookie came from.
//
// For explicit Domain attribute in the cookie, the following rules apply:
//
// Version=0 (Netscape, RFC6265) allows the Domain attribute of the cookie to match any tail substring
// of the host uri.
//
// Version=1 related cookie specs only allows the Domain attribute to match the host uri based on a
// more restricted set of rules.
//
// According to RFC2109/RFC2965, the cookie will be rejected for matching if:
// * The value for the Domain attribute contains no embedded dots or does not start with a dot.
// * The value for the request-host does not domain-match the Domain attribute.
// " The request-host is a FQDN (not IP address) and has the form HD, where D is the value of the Domain
// attribute, and H is a string that contains one or more dots.
//
// Examples:
// * A cookie from request-host y.x.foo.com for Domain=.foo.com would be rejected, because H is y.x
// and contains a dot.
//
// * A cookie from request-host x.foo.com for Domain=.foo.com would be accepted.
//
// * A cookie with Domain=.com or Domain=.com., will always be rejected, because there is no embedded dot.
//
// * A cookie with Domain=ajax.com will be rejected because the value for Domain does not begin with a dot.
namespace System.Net {
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Globalization;
using System.Net.NetworkInformation;
using System.Text;
internal struct HeaderVariantInfo {
string m_name;
CookieVariant m_variant;
internal HeaderVariantInfo(string name, CookieVariant variant) {
m_name = name;
m_variant = variant;
}
internal string Name {
get {
return m_name;
}
}
internal CookieVariant Variant {
get {
return m_variant;
}
}
}
//
// CookieContainer
//
// Manage cookies for a user (implicit). Based on RFC 2965
//
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[Serializable]
public class CookieContainer {
public const int DefaultCookieLimit = 300;
public const int DefaultPerDomainCookieLimit = 20;
public const int DefaultCookieLengthLimit = 4096;
static readonly HeaderVariantInfo [] HeaderInfo = {
new HeaderVariantInfo(HttpKnownHeaderNames.SetCookie, CookieVariant.Rfc2109),
new HeaderVariantInfo(HttpKnownHeaderNames.SetCookie2, CookieVariant.Rfc2965)
};
// fields
Hashtable m_domainTable = new Hashtable();
int m_maxCookieSize = DefaultCookieLengthLimit;
int m_maxCookies = DefaultCookieLimit;
int m_maxCookiesPerDomain = DefaultPerDomainCookieLimit;
int m_count = 0;
string m_fqdnMyDomain = String.Empty;
// constructors
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public CookieContainer() {
string domain = IPGlobalProperties.InternalGetIPGlobalProperties().DomainName;
if (domain != null && domain.Length > 1)
{
m_fqdnMyDomain = '.' + domain;
}
//Otherwise it will remain string.Empty
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public CookieContainer(int capacity) : this(){
if (capacity <= 0) {
throw new ArgumentException(SR.GetString(SR.net_toosmall), "Capacity");
}
m_maxCookies = capacity;
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public CookieContainer(int capacity, int perDomainCapacity, int maxCookieSize) : this(capacity) {
if (perDomainCapacity != Int32.MaxValue && (perDomainCapacity <= 0 || perDomainCapacity > capacity)) {
throw new ArgumentOutOfRangeException("perDomainCapacity", SR.GetString(SR.net_cookie_capacity_range, "PerDomainCapacity", 0, capacity));
}
m_maxCookiesPerDomain = perDomainCapacity;
if (maxCookieSize <= 0) {
throw new ArgumentException(SR.GetString(SR.net_toosmall), "MaxCookieSize");
}
m_maxCookieSize = maxCookieSize;
}
// properties
/// <devdoc>
/// <para>Note that after shrinking the capacity Count can become greater than Capacity.</para>
/// </devdoc>
public int Capacity {
get {
return m_maxCookies;
}
set {
if (value <= 0 || (value < m_maxCookiesPerDomain && m_maxCookiesPerDomain != Int32.MaxValue)) {
throw new ArgumentOutOfRangeException("value", SR.GetString(SR.net_cookie_capacity_range, "Capacity", 0, m_maxCookiesPerDomain));
}
if (value < m_maxCookies) {
m_maxCookies = value;
AgeCookies(null);
}
m_maxCookies = value;
}
}
/// <devdoc>
/// <para>returns the total number of cookies in the container.</para>
/// </devdoc>
public int Count {
get {
return m_count;
}
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public int MaxCookieSize {
get {
return m_maxCookieSize;
}
set {
if (value <= 0) {
throw new ArgumentOutOfRangeException("value");
}
m_maxCookieSize = value;
}
}
/// <devdoc>
/// <para>After shrinking domain capacity each domain will less hold than new domain capacity</para>
/// </devdoc>
public int PerDomainCapacity {
get {
return m_maxCookiesPerDomain;
}
set {
if (value <= 0 || (value > m_maxCookies && value != Int32.MaxValue)) {
throw new ArgumentOutOfRangeException("value");
}
if (value < m_maxCookiesPerDomain) {
m_maxCookiesPerDomain = value;
AgeCookies(null);
}
m_maxCookiesPerDomain = value;
}
}
// methods
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
//This method will construct faked URI, Domain property is required for param.
public void Add(Cookie cookie) {
if (cookie == null) {
throw new ArgumentNullException("cookie");
}
if (cookie.Domain.Length == 0) {
throw new ArgumentException(SR.GetString(SR.net_emptystringcall), "cookie.Domain");
}
Uri uri;
StringBuilder uriSb = new StringBuilder();
// We cannot add an invalid cookie into the container.
// Trying to prepare Uri for the cookie verification
uriSb.Append(cookie.Secure ? Uri.UriSchemeHttps : Uri.UriSchemeHttp).Append(Uri.SchemeDelimiter);
// If the original cookie has an explicitly set domain, copy it over
// to the new cookie
if (!cookie.DomainImplicit) {
if (cookie.Domain[0] == '.') {
uriSb.Append("0"); // Uri cctor should eat this, faked host.
}
}
uriSb.Append(cookie.Domain);
// Either keep Port as implici or set it according to original cookie
if (cookie.PortList != null) {
uriSb.Append(":").Append(cookie.PortList[0]);
}
// Path must be present, set to root by default
uriSb.Append(cookie.Path);
if (!Uri.TryCreate(uriSb.ToString(), UriKind.Absolute, out uri))
throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Domain", cookie.Domain));
// We don't know cookie verification status -> re-create cookie and verify it
Cookie new_cookie = cookie.Clone();
new_cookie.VerifySetDefaults(new_cookie.Variant, uri, IsLocalDomain(uri.Host), m_fqdnMyDomain, true, true);
Add(new_cookie, true);
}
private void AddRemoveDomain(string key, PathList value) {
// Hashtable support multiple readers and one writer
// Synchronize writers
lock (m_domainTable.SyncRoot) {
if (value == null) {
m_domainTable.Remove(key);
}
else {
m_domainTable[key] = value;
}
}
}
// This method is called *only* when cookie verification is done,
// so unlike with public Add(Cookie cookie) the cookie is in sane condition
internal void Add(Cookie cookie, bool throwOnError) {
PathList pathList;
if (cookie.Value.Length > m_maxCookieSize) {
if (throwOnError) {
throw new CookieException(SR.GetString(SR.net_cookie_size, cookie.ToString(), m_maxCookieSize));
}
return;
}
try {
lock (m_domainTable.SyncRoot)
{
pathList = (PathList)m_domainTable[cookie.DomainKey];
if (pathList == null)
{
pathList = new PathList();
AddRemoveDomain(cookie.DomainKey, pathList);
}
}
int domain_count = pathList.GetCookiesCount();
CookieCollection cookies;
lock (pathList.SyncRoot)
{
cookies = (CookieCollection)pathList[cookie.Path];
if (cookies == null)
{
cookies = new CookieCollection();
pathList[cookie.Path] = cookies;
}
}
if(cookie.Expired) {
//Explicit removal command (Max-Age == 0)
lock (cookies) {
int idx = cookies.IndexOf(cookie);
if (idx != -1) {
cookies.RemoveAt(idx);
--m_count;
}
}
}
else {
//This is about real cookie adding, check Capacity first
if (domain_count >= m_maxCookiesPerDomain && !AgeCookies(cookie.DomainKey)) {
return; //cannot age -> reject new cookie
}
else if (this.m_count >= m_maxCookies && !AgeCookies(null)) {
return; //cannot age -> reject new cookie
}
//about to change the collection
lock (cookies) {
m_count += cookies.InternalAdd(cookie, true);
}
}
}
catch (Exception e) {
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
throw;
}
if (throwOnError) {
throw new CookieException(SR.GetString(SR.net_container_add_cookie), e);
}
}
}
//
// This function, once called, must delete at least one cookie
// If there are expired cookies in given scope they are cleaned up
// If nothing found the least used Collection will be found and removed
// from the container.
//
// Also note that expired cookies are also removed during request preparation
// (this.GetCookies method)
//
// Param. 'domain' == null means to age in the whole container
//
private bool AgeCookies(string domain) {
// border case => shrinked to zero
if(m_maxCookies == 0 || m_maxCookiesPerDomain == 0) {
m_domainTable = new Hashtable();
m_count = 0;
return false;
}
int removed = 0;
DateTime oldUsed = DateTime.MaxValue;
DateTime tempUsed;
CookieCollection lruCc = null;
string lruDomain = null;
string tempDomain = null;
PathList pathList;
int domain_count = 0;
int itemp = 0;
float remainingFraction = 1.0F;
// the container was shrinked, might need additional cleanup for each domain
if (m_count > m_maxCookies) {
// Means the fraction of the container to be left
// Each domain will be cut accordingly
remainingFraction = (float)m_maxCookies/(float)m_count;
}
lock (m_domainTable.SyncRoot) {
foreach (DictionaryEntry entry in m_domainTable) {
if (domain == null) {
tempDomain = (string) entry.Key;
pathList = (PathList) entry.Value; //aliasing to trick foreach
}
else {
tempDomain = domain;
pathList = (PathList) m_domainTable[domain];
}
domain_count = 0; // cookies in the domain
lock (pathList.SyncRoot) {
foreach (CookieCollection cc in pathList.Values) {
itemp = ExpireCollection(cc);
removed += itemp;
m_count -= itemp; //update this container count;
domain_count += cc.Count;
// we also find the least used cookie collection in ENTIRE container
// we count the collection as LRU only if it holds 1+ elements
if (cc.Count > 0 && (tempUsed = cc.TimeStamp(CookieCollection.Stamp.Check)) < oldUsed) {
lruDomain = tempDomain;
lruCc = cc;
oldUsed = tempUsed;
}
}
}
// Check if we have reduced to the limit of the domain by expiration only
int min_count = Math.Min((int)(domain_count*remainingFraction), Math.Min(m_maxCookiesPerDomain, m_maxCookies)-1);
if (domain_count > min_count) {
//That case require sorting all domain collections by timestamp
Array cookies;
Array stamps;
lock (pathList.SyncRoot) {
cookies = Array.CreateInstance(typeof(CookieCollection), pathList.Count);
stamps = Array.CreateInstance(typeof(DateTime), pathList.Count);
foreach (CookieCollection cc in pathList.Values) {
stamps.SetValue(cc.TimeStamp(CookieCollection.Stamp.Check), itemp);
cookies.SetValue(cc, itemp);
++itemp;
}
}
Array.Sort(stamps, cookies);
itemp = 0;
for (int i = 0; i < cookies.Length; ++i) {
CookieCollection cc = (CookieCollection)cookies.GetValue(i);
lock (cc) {
while (domain_count > min_count && cc.Count > 0) {
cc.RemoveAt(0);
--domain_count;
--m_count;
++removed;
}
}
if (domain_count <= min_count ) {
break;
}
}
if (domain_count > min_count && domain != null) {
//cannot complete aging of explicit domain (no cookie adding allowed)
return false;
}
}
}
}
// we have completed aging of specific domain
if (domain != null) {
return true;
}
// The rest is for entire container aging
// We must get at least one free slot.
//Don't need to appy LRU if we already cleaned something
if (removed != 0) {
return true;
}
if (oldUsed == DateTime.MaxValue) {
//Something strange. Either capacity is 0 or all collections are locked with cc.Used
return false;
}
// Remove oldest cookies from the least used collection
lock (lruCc) {
while (m_count >= m_maxCookies && lruCc.Count > 0) {
lruCc.RemoveAt(0);
--m_count;
}
}
return true;
}
//return number of cookies removed from the collection
private int ExpireCollection(CookieCollection cc) {
lock (cc) {
int oldCount = cc.Count;
int idx = oldCount-1;
//Cannot use enumerator as we are going to alter collection
while (idx >= 0) {
Cookie cookie = cc[idx];
if (cookie.Expired) {
cc.RemoveAt(idx);
}
--idx;
}
return oldCount - cc.Count;
}
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
// <
public void Add(CookieCollection cookies) {
if (cookies == null) {
throw new ArgumentNullException("cookies");
}
foreach (Cookie c in cookies) {
Add(c);
}
}
//
// This will try (if needed) get the full domain name of the host given the Uri
// NEVER call this function from internal methods with 'fqdnRemote' == NULL
// Since this method counts security issue for DNS and hence will slow
// the performance
//
internal bool IsLocalDomain(string host) {
int dot = host.IndexOf('.');
if (dot == -1) {
// No choice but to treat it as a host on the local domain
// This also covers 'localhost' and 'loopback'
return true;
}
// quick test for usual case - loopback addresses for IPv4 and IPv6
if ((host == "127.0.0.1") || (host == "::1") || (host == "0:0:0:0:0:0:0:1"))
{
return true;
}
// test domain membership
if (string.Compare(m_fqdnMyDomain, 0, host, dot, m_fqdnMyDomain.Length, StringComparison.OrdinalIgnoreCase ) == 0)
{
return true;
}
// test for "127.###.###.###" without using regex
string[] ipParts = host.Split('.');
if (ipParts != null && ipParts.Length == 4 && ipParts[0] == "127")
{
int i;
for (i = 1; i < 4; i++)
{
switch (ipParts[i].Length)
{
case 3:
if (ipParts[i][2] < '0' || ipParts[i][2] > '9')
{
break;
}
goto case 2;
case 2:
if (ipParts[i][1] < '0' || ipParts[i][1] > '9')
{
break;
}
goto case 1;
case 1:
if (ipParts[i][0] < '0' || ipParts[i][0] > '9')
{
break;
}
continue;
}
break;
}
if (i == 4)
{
return true;
}
}
return false;
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void Add(Uri uri, Cookie cookie) {
if (uri == null) {
throw new ArgumentNullException("uri");
}
if(cookie == null) {
throw new ArgumentNullException("cookie");
}
Cookie new_cookie = cookie.Clone();
new_cookie.VerifySetDefaults(new_cookie.Variant, uri, IsLocalDomain(uri.Host), m_fqdnMyDomain, true, true);
Add(new_cookie, true);
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void Add(Uri uri, CookieCollection cookies) {
if (uri == null) {
throw new ArgumentNullException("uri");
}
if(cookies == null) {
throw new ArgumentNullException("cookies");
}
bool isLocalDomain = IsLocalDomain(uri.Host);
foreach (Cookie c in cookies) {
Cookie new_cookie = c.Clone();
new_cookie.VerifySetDefaults(new_cookie.Variant, uri, isLocalDomain, m_fqdnMyDomain, true, true);
Add(new_cookie, true);
}
}
internal CookieCollection CookieCutter(Uri uri, string headerName, string setCookieHeader, bool isThrow) {
GlobalLog.Print("CookieContainer#" + ValidationHelper.HashString(this) + "::CookieCutter() uri:" + uri + " headerName:" + headerName + " setCookieHeader:" + setCookieHeader + " isThrow:" + isThrow);
CookieCollection cookies = new CookieCollection();
CookieVariant variant = CookieVariant.Unknown;
if (headerName == null) {
variant = CookieVariant.Default;
}
else for (int i = 0; i < HeaderInfo.Length; ++i) {
if ((String.Compare(headerName, HeaderInfo[i].Name, StringComparison.OrdinalIgnoreCase ) == 0)) {
variant = HeaderInfo[i].Variant;
}
}
bool isLocalDomain = IsLocalDomain(uri.Host);
try {
CookieParser parser = new CookieParser(setCookieHeader);
do {
Cookie cookie = parser.Get();
GlobalLog.Print("CookieContainer#" + ValidationHelper.HashString(this) + "::CookieCutter() CookieParser returned cookie:" + ValidationHelper.ToString(cookie));
if (cookie == null) {
if (parser.EndofHeader()) {
break;
}
continue;
}
//Parser marks invalid cookies this way
if (ValidationHelper.IsBlankString(cookie.Name)) {
if(isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_format));
}
//Otherwise, ignore (reject) cookie
continue;
}
// this will set the default values from the response URI
// AND will check for cookie validity
if(!cookie.VerifySetDefaults(variant, uri, isLocalDomain, m_fqdnMyDomain, true, isThrow)) {
continue;
}
// If many same cookies arrive we collapse them into just one, hence setting
// parameter isStrict = true below
cookies.InternalAdd(cookie, true);
} while (true);
}
catch (Exception e) {
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
throw;
}
if(isThrow) {
throw new CookieException(SR.GetString(SR.net_cookie_parse_header, uri.AbsoluteUri), e);
}
}
foreach (Cookie c in cookies) {
Add(c, isThrow);
}
return cookies;
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public CookieCollection GetCookies(Uri uri) {
if (uri == null) {
throw new ArgumentNullException("uri");
}
return InternalGetCookies(uri);
}
internal CookieCollection InternalGetCookies(Uri uri) {
bool isSecure = (uri.Scheme == Uri.UriSchemeHttps);
int port = uri.Port;
CookieCollection cookies = new CookieCollection();
List<string> domainAttributeMatchAnyCookieVariant = new List<string>();
List<string> domainAttributeMatchOnlyCookieVariantPlain = new List<string>();
string fqdnRemote = uri.Host;
// Add initial candidates to match Domain attribute of possible cookies.
// For these Domains, cookie can have any CookieVariant enum value.
domainAttributeMatchAnyCookieVariant.Add(fqdnRemote);
domainAttributeMatchAnyCookieVariant.Add("." + fqdnRemote);
int dot = fqdnRemote.IndexOf('.');
if (dot == -1) {
// DNS.resolve may return short names even for other inet domains ;-(
// We _don't_ know what the exact domain is, so try also grab short hostname cookies.
// grab long name from the local domain
if (m_fqdnMyDomain != null && m_fqdnMyDomain.Length != 0) {
domainAttributeMatchAnyCookieVariant.Add(fqdnRemote + m_fqdnMyDomain);
// grab the local domain itself
domainAttributeMatchAnyCookieVariant.Add(m_fqdnMyDomain);
}
}
else {
// grab the host domain
domainAttributeMatchAnyCookieVariant.Add(fqdnRemote.Substring(dot));
// The following block is only for compatibility with Version0 spec.
// Still, we'll add only Plain-Variant cookies if found under below keys
if (fqdnRemote.Length > 2) {
// We ignore the '.' at the end on the name
int last = fqdnRemote.LastIndexOf('.', fqdnRemote.Length-2);
//AND keys with <2 dots inside.
if (last > 0) {
last = fqdnRemote.LastIndexOf('.', last-1);
}
if (last != -1) {
while ((dot < last) && (dot = fqdnRemote.IndexOf('.', dot+1)) != -1) {
// These candidates can only match CookieVariant.Plain cookies.
domainAttributeMatchOnlyCookieVariantPlain.Add(fqdnRemote.Substring(dot));
}
}
}
}
BuildCookieCollectionFromDomainMatches(uri, isSecure, port, cookies, domainAttributeMatchAnyCookieVariant, false);
BuildCookieCollectionFromDomainMatches(uri, isSecure, port, cookies, domainAttributeMatchOnlyCookieVariantPlain, true);
return cookies;
}
private void BuildCookieCollectionFromDomainMatches(Uri uri, bool isSecure, int port, CookieCollection cookies, List<string> domainAttribute, bool matchOnlyPlainCookie) {
for (int i = 0; i < domainAttribute.Count; i++) {
bool found = false;
bool defaultAdded = false;
PathList pathList;
lock (m_domainTable.SyncRoot) {
pathList = (PathList)m_domainTable[domainAttribute[i]];
}
if (pathList == null) {
continue;
}
lock (pathList.SyncRoot) {
foreach (DictionaryEntry entry in pathList) {
string path = (string)entry.Key;
if (uri.AbsolutePath.StartsWith(CookieParser.CheckQuoted(path))) {
found = true;
CookieCollection cc = (CookieCollection)entry.Value;
cc.TimeStamp(CookieCollection.Stamp.Set);
MergeUpdateCollections(cookies, cc, port, isSecure, matchOnlyPlainCookie);
if (path == "/") {
defaultAdded = true;
}
}
else if (found) {
break;
}
}
}
if (!defaultAdded) {
CookieCollection cc = (CookieCollection)pathList["/"];
if (cc != null) {
cc.TimeStamp(CookieCollection.Stamp.Set);
MergeUpdateCollections(cookies, cc, port, isSecure, matchOnlyPlainCookie);
}
}
// Remove unused domain
// (This is the only place that does domain removal)
if (pathList.Count == 0) {
AddRemoveDomain(domainAttribute[i], null);
}
}
}
private void MergeUpdateCollections(CookieCollection destination, CookieCollection source, int port, bool isSecure, bool isPlainOnly) {
// we may change it
lock (source) {
//cannot use foreach as we going update 'source'
for (int idx = 0 ; idx < source.Count; ++idx) {
bool to_add = false;
Cookie cookie = source[idx];
if (cookie.Expired) {
//If expired, remove from container and don't add to the destination
source.RemoveAt(idx);
--m_count;
--idx;
}
else {
//Add only if port does match to this request URI
//or was not present in the original response
if(isPlainOnly && cookie.Variant != CookieVariant.Plain) {
;//don;t add
}
else if(cookie.PortList != null)
{
foreach (int p in cookie.PortList) {
if(p == port) {
to_add = true;
break;
}
}
}
else {
//it was implicit Port, always OK to add
to_add = true;
}
//refuse adding secure cookie into 'unsecure' destination
if (cookie.Secure && !isSecure) {
to_add = false;
}
if (to_add) {
// In 'source' are already orederd.
// If two same cookies come from dif 'source' then they
// will follow (not replace) each other.
destination.InternalAdd(cookie, false);
}
}
}
}
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public string GetCookieHeader(Uri uri) {
if (uri == null) {
throw new ArgumentNullException("uri");
}
string dummy;
return GetCookieHeader(uri, out dummy);
}
internal string GetCookieHeader(Uri uri, out string optCookie2) {
CookieCollection cookies = InternalGetCookies(uri);
string cookieString = String.Empty;
string delimiter = String.Empty;
foreach (Cookie cookie in cookies) {
cookieString += delimiter + cookie.ToString();
delimiter = "; ";
}
optCookie2 = cookies.IsOtherVersionSeen ?
(Cookie.SpecialAttributeLiteral +
Cookie.VersionAttributeName +
Cookie.EqualsLiteral +
Cookie.MaxSupportedVersion.ToString(NumberFormatInfo.InvariantInfo)) : String.Empty;
return cookieString;
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
//<
public void SetCookies(Uri uri, string cookieHeader) {
if (uri == null) {
throw new ArgumentNullException("uri");
}
if(cookieHeader == null) {
throw new ArgumentNullException("cookieHeader");
}
CookieCutter(uri, null, cookieHeader, true); //will throw on error
}
}
[Serializable]
internal class PathList {
SortedList m_list = (SortedList.Synchronized(new SortedList(PathListComparer.StaticInstance)));
public PathList() {
}
public int Count {
get {
return m_list.Count;
}
}
public int GetCookiesCount() {
int count = 0;
lock (SyncRoot) {
foreach (CookieCollection cc in m_list.Values) {
count += cc.Count;
}
}
return count;
}
public ICollection Values {
get {
return m_list.Values;
}
}
public object this[string s] {
get {
return m_list[s];
}
set {
lock (SyncRoot) {
m_list[s] = value;
}
}
}
public IEnumerator GetEnumerator() {
return m_list.GetEnumerator();
}
public object SyncRoot {
get {
return m_list.SyncRoot;
}
}
[Serializable]
class PathListComparer : IComparer {
internal static readonly PathListComparer StaticInstance = new PathListComparer();
int IComparer.Compare(object ol, object or) {
string pathLeft = CookieParser.CheckQuoted((string)ol);
string pathRight = CookieParser.CheckQuoted((string)or);
int ll = pathLeft.Length;
int lr = pathRight.Length;
int length = Math.Min(ll, lr);
for (int i = 0; i < length; ++i) {
if (pathLeft[i] != pathRight[i]) {
return pathLeft[i] - pathRight[i];
}
}
return lr - ll;
}
}
}
}
|