File: HttpCookieCollection.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="HttpCookieCollection.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
 * Collection of Http cookies for request and response intrinsics
 * 
 * Copyright (c) 1998 Microsoft Corporation
 */
 
namespace System.Web {
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Web.Util;
 
    /// <devdoc>
    ///    <para>
    ///       Provides a type-safe
    ///       way to manipulate HTTP cookies.
    ///    </para>
    /// </devdoc>
    public sealed class HttpCookieCollection : NameObjectCollectionBase {
        // Response object to notify about changes in collection
        private HttpResponse _response;
 
        // cached All[] arrays
        private HttpCookie[] _all;
        private String[] _allKeys;
        private bool    _changed;
 
        // for implementing granular request validation
        private ValidateStringCallback _validationCallback;
        private HashSet<string> _keysAwaitingValidation;
 
        internal HttpCookieCollection(HttpResponse response, bool readOnly)
            : base(StringComparer.OrdinalIgnoreCase)  {
            _response = response;
            IsReadOnly = readOnly;
        }
 
 
        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the HttpCookieCollection
        ///       class.
        ///    </para>
        /// </devdoc>
        public HttpCookieCollection(): base(StringComparer.OrdinalIgnoreCase)  {
        }
 
        // This copy constructor is used by the granular request validation feature. The collections are mutable once
        // created, but nobody should ever be mutating them, so it's ok for these to be out of sync. Additionally,
        // we don't copy _response since this should only ever be called for the request cookies.
        internal HttpCookieCollection(HttpCookieCollection col)
            : base(StringComparer.OrdinalIgnoreCase) {
 
            // We explicitly don't copy validation-related fields, as we want the copy to "reset" validation state.
 
            // Copy the file references from the original collection into this instance
            for (int i = 0; i < col.Count; i++) {
                ThrowIfMaxHttpCollectionKeysExceeded();
                string key = col.BaseGetKey(i);
                object value = col.BaseGet(i);
                BaseAdd(key, value);
            }
 
            IsReadOnly = col.IsReadOnly;
        }
 
        internal bool Changed {
            get { return _changed; }
            set { _changed = value; }
        }
        internal void AddCookie(HttpCookie cookie, bool append) {
            ThrowIfMaxHttpCollectionKeysExceeded();
 
            _all = null;
            _allKeys = null;
 
            if (append) {
                // DevID 251951	Cookie is getting duplicated by ASP.NET when they are added via a native module
                // Need to not double add response cookies from native modules
                if (!cookie.IsInResponseHeader) {
                    // mark cookie as new
                    cookie.Added = true;
                }
                BaseAdd(cookie.Name, cookie);
            }
            else {
                if (BaseGet(cookie.Name) != null) {                   
                    // mark the cookie as changed because we are overriding the existing one
                    cookie.Changed = true;
                }
                BaseSet(cookie.Name, cookie);
            }
        }
 
        // VSO bug #289778: when copying cookie from Response to Request, there is side effect
        // which changes Added property and causes dup cookie in response header
        // This method is meant to append cookie from one collection without changing cookie object
        internal void Append(HttpCookieCollection cookies) {
            for (int i = 0; i < cookies.Count; ++i) {
                //BaseGet method doesn't trigger validation, while Get method does
                HttpCookie cookie = (HttpCookie) cookies.BaseGet(i);
                BaseAdd(cookie.Name, cookie);
            }
        }
 
        // MSRC 12038: limit the maximum number of items that can be added to the collection,
        // as a large number of items potentially can result in too many hash collisions that may cause DoS
        private void ThrowIfMaxHttpCollectionKeysExceeded() {
            if (Count >= AppSettings.MaxHttpCollectionKeys) {
                throw new InvalidOperationException(SR.GetString(SR.CollectionCountExceeded_HttpValueCollection, AppSettings.MaxHttpCollectionKeys));
            }
        }
 
        internal void EnableGranularValidation(ValidateStringCallback validationCallback) {
            // Iterate over all the keys, adding each to the set containing the keys awaiting validation.
            // Unlike dictionaries, HashSet<T> can contain null keys, so don't need to special-case them.
            _keysAwaitingValidation = new HashSet<string>(Keys.Cast<string>(), StringComparer.OrdinalIgnoreCase);
            _validationCallback = validationCallback;
        }
 
        private void EnsureKeyValidated(string key, string value) {
            if (_keysAwaitingValidation == null) {
                // If dynamic validation hasn't been enabled, no-op.
                return;
            }
 
            if (!_keysAwaitingValidation.Contains(key)) {
                // If this key has already been validated (or is excluded), no-op.
                return;
            }
 
            // If validation fails, the callback will throw an exception. If validation succeeds,
            // we can remove it from the candidates list. A note:
            // - Eager validation skips null/empty values, so we should, also.
            if (!String.IsNullOrEmpty(value)) {
                _validationCallback(key, value);
            }
            _keysAwaitingValidation.Remove(key);
        }
 
        internal void MakeReadOnly() {
            IsReadOnly = true;
        }
 
        internal void RemoveCookie(String name) {
            _all = null;
            _allKeys = null;
 
            BaseRemove(name);
 
            _changed = true;
        }
 
        internal void Reset() {
            _all = null;
            _allKeys = null;
 
            BaseClear();
            _changed = true;
            _keysAwaitingValidation = null;
        }
 
        //
        //  Public APIs to add / remove
        //
 
 
        /// <devdoc>
        ///    <para>
        ///       Adds a cookie to the collection.
        ///    </para>
        /// </devdoc>
        public void Add(HttpCookie cookie) {
            if (_response != null)
                _response.BeforeCookieCollectionChange();
 
            AddCookie(cookie, true);
 
            if (_response != null)
                _response.OnCookieAdd(cookie);
        }
        
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public void CopyTo(Array dest, int index) {
            if (_all == null) {
                int n = Count;
                HttpCookie[] all = new HttpCookie[n];
 
                for (int i = 0; i < n; i++)
                    all[i] = Get(i);
 
                _all = all; // wait until end of loop to set _all reference in case Get throws
            }
            _all.CopyTo(dest, index);
        }
 
 
        /// <devdoc>
        ///    <para> Updates the value of a cookie.</para>
        /// </devdoc>
        public void Set(HttpCookie cookie) {
            if (_response != null)
                _response.BeforeCookieCollectionChange();
 
            AddCookie(cookie, false);
 
            if (_response != null)
                _response.OnCookieCollectionChange();
        }
 
 
        /// <devdoc>
        ///    <para>
        ///       Removes a cookie from the collection.
        ///    </para>
        /// </devdoc>
        public void Remove(String name) {
            if (_response != null)
                _response.BeforeCookieCollectionChange();
 
            RemoveCookie(name);
 
            if (_response != null)
                _response.OnCookieCollectionChange();
        }
 
 
        /// <devdoc>
        ///    <para>
        ///       Clears all cookies from the collection.
        ///    </para>
        /// </devdoc>
        public void Clear() {
            Reset();
        }
 
        //
        //  Access by name
        //
 
 
        /// <devdoc>
        /// <para>Returns an <see cref='System.Web.HttpCookie'/> item from the collection.</para>
        /// </devdoc>
        public HttpCookie Get(String name) {
            HttpCookie cookie = (HttpCookie)BaseGet(name);
 
            if (cookie == null && _response != null) {
                // response cookies are created on demand
                cookie = new HttpCookie(name);
                AddCookie(cookie, true);
                _response.OnCookieAdd(cookie);
            }
 
            if (cookie != null) {
                EnsureKeyValidated(name, cookie.Value);
            }
 
            return cookie;
        }
 
 
        /// <devdoc>
        ///    <para>Indexed value that enables access to a cookie in the collection.</para>
        /// </devdoc>
        public HttpCookie this[String name]
        {
            get { return Get(name);}
        }
 
        //
        // Indexed access
        //
 
 
        /// <devdoc>
        ///    <para>
        ///       Returns an <see cref='System.Web.HttpCookie'/>
        ///       item from the collection.
        ///    </para>
        /// </devdoc>
        public HttpCookie Get(int index) {
            HttpCookie cookie = (HttpCookie)BaseGet(index);
 
            // Call GetKey so that we can pass the key to the validation routine.
            if (cookie != null) {
                EnsureKeyValidated(GetKey(index), cookie.Value);
            }
            return cookie;
        }
 
 
        /// <devdoc>
        ///    <para>
        ///       Returns key name from collection.
        ///    </para>
        /// </devdoc>
        public String GetKey(int index) {
            return BaseGetKey(index);
        }
 
 
        /// <devdoc>
        ///    <para>
        ///       Default property.
        ///       Indexed property that enables access to a cookie in the collection.
        ///    </para>
        /// </devdoc>
        public HttpCookie this[int index]
        {
            get { return Get(index);}
        }
 
        //
        // Access to keys and values as arrays
        //
        
        /*
         * All keys
         */
 
        /// <devdoc>
        ///    <para>
        ///       Returns
        ///       an array of all cookie keys in the cookie collection.
        ///    </para>
        /// </devdoc>
        public String[] AllKeys {
            get {
                if (_allKeys == null)
                    _allKeys = BaseGetAllKeys();
 
                return _allKeys;
            }
        }
    }
}