1 //------------------------------------------------------------------------------
2 // <copyright file="HttpCookieCollection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 * Collection of Http cookies for request and response intrinsics
10 * Copyright (c) 1998 Microsoft Corporation
13 namespace System.Web {
15 using System.Collections.Generic;
16 using System.Collections.Specialized;
18 using System.Web.Util;
22 /// Provides a type-safe
23 /// way to manipulate HTTP cookies.
26 public sealed class HttpCookieCollection : NameObjectCollectionBase {
27 // Response object to notify about changes in collection
28 private HttpResponse _response;
30 // cached All[] arrays
31 private HttpCookie[] _all;
32 private String[] _allKeys;
33 private bool _changed;
35 // for implementing granular request validation
36 private ValidateStringCallback _validationCallback;
37 private HashSet<string> _keysAwaitingValidation;
39 internal HttpCookieCollection(HttpResponse response, bool readOnly)
40 : base(StringComparer.OrdinalIgnoreCase) {
42 IsReadOnly = readOnly;
48 /// Initializes a new instance of the HttpCookieCollection
52 public HttpCookieCollection(): base(StringComparer.OrdinalIgnoreCase) {
55 // This copy constructor is used by the granular request validation feature. The collections are mutable once
56 // created, but nobody should ever be mutating them, so it's ok for these to be out of sync. Additionally,
57 // we don't copy _response since this should only ever be called for the request cookies.
58 internal HttpCookieCollection(HttpCookieCollection col)
59 : base(StringComparer.OrdinalIgnoreCase) {
61 // We explicitly don't copy validation-related fields, as we want the copy to "reset" validation state.
63 // Copy the file references from the original collection into this instance
64 for (int i = 0; i < col.Count; i++) {
65 ThrowIfMaxHttpCollectionKeysExceeded();
66 string key = col.BaseGetKey(i);
67 object value = col.BaseGet(i);
71 IsReadOnly = col.IsReadOnly;
74 internal bool Changed {
75 get { return _changed; }
76 set { _changed = value; }
78 internal void AddCookie(HttpCookie cookie, bool append) {
79 ThrowIfMaxHttpCollectionKeysExceeded();
85 // DevID 251951 Cookie is getting duplicated by ASP.NET when they are added via a native module
86 // Need to not double add response cookies from native modules
87 if (!cookie.IsInResponseHeader) {
91 BaseAdd(cookie.Name, cookie);
94 if (BaseGet(cookie.Name) != null) {
95 // mark the cookie as changed because we are overriding the existing one
96 cookie.Changed = true;
98 BaseSet(cookie.Name, cookie);
102 // VSO bug #289778: when copying cookie from Response to Request, there is side effect
103 // which changes Added property and causes dup cookie in response header
104 // This method is meant to append cookie from one collection without changing cookie object
105 internal void Append(HttpCookieCollection cookies) {
106 for (int i = 0; i < cookies.Count; ++i) {
107 //BaseGet method doesn't trigger validation, while Get method does
108 HttpCookie cookie = (HttpCookie) cookies.BaseGet(i);
109 BaseAdd(cookie.Name, cookie);
113 // MSRC 12038: limit the maximum number of items that can be added to the collection,
114 // as a large number of items potentially can result in too many hash collisions that may cause DoS
115 private void ThrowIfMaxHttpCollectionKeysExceeded() {
116 if (Count >= AppSettings.MaxHttpCollectionKeys) {
117 throw new InvalidOperationException(SR.GetString(SR.CollectionCountExceeded_HttpValueCollection, AppSettings.MaxHttpCollectionKeys));
121 internal void EnableGranularValidation(ValidateStringCallback validationCallback) {
122 // Iterate over all the keys, adding each to the set containing the keys awaiting validation.
123 // Unlike dictionaries, HashSet<T> can contain null keys, so don't need to special-case them.
124 _keysAwaitingValidation = new HashSet<string>(Keys.Cast<string>(), StringComparer.OrdinalIgnoreCase);
125 _validationCallback = validationCallback;
128 private void EnsureKeyValidated(string key, string value) {
129 if (_keysAwaitingValidation == null) {
130 // If dynamic validation hasn't been enabled, no-op.
134 if (!_keysAwaitingValidation.Contains(key)) {
135 // If this key has already been validated (or is excluded), no-op.
139 // If validation fails, the callback will throw an exception. If validation succeeds,
140 // we can remove it from the candidates list. A note:
141 // - Eager validation skips null/empty values, so we should, also.
142 if (!String.IsNullOrEmpty(value)) {
143 _validationCallback(key, value);
145 _keysAwaitingValidation.Remove(key);
148 internal void MakeReadOnly() {
152 internal void RemoveCookie(String name) {
161 internal void Reset() {
167 _keysAwaitingValidation = null;
171 // Public APIs to add / remove
177 /// Adds a cookie to the collection.
180 public void Add(HttpCookie cookie) {
181 if (_response != null)
182 _response.BeforeCookieCollectionChange();
184 AddCookie(cookie, true);
186 if (_response != null)
187 _response.OnCookieAdd(cookie);
192 /// <para>[To be supplied.]</para>
194 public void CopyTo(Array dest, int index) {
197 HttpCookie[] all = new HttpCookie[n];
199 for (int i = 0; i < n; i++)
202 _all = all; // wait until end of loop to set _all reference in case Get throws
204 _all.CopyTo(dest, index);
209 /// <para> Updates the value of a cookie.</para>
211 public void Set(HttpCookie cookie) {
212 if (_response != null)
213 _response.BeforeCookieCollectionChange();
215 AddCookie(cookie, false);
217 if (_response != null)
218 _response.OnCookieCollectionChange();
224 /// Removes a cookie from the collection.
227 public void Remove(String name) {
228 if (_response != null)
229 _response.BeforeCookieCollectionChange();
233 if (_response != null)
234 _response.OnCookieCollectionChange();
240 /// Clears all cookies from the collection.
243 public void Clear() {
253 /// <para>Returns an <see cref='System.Web.HttpCookie'/> item from the collection.</para>
255 public HttpCookie Get(String name) {
256 HttpCookie cookie = (HttpCookie)BaseGet(name);
258 if (cookie == null && _response != null) {
259 // response cookies are created on demand
260 cookie = new HttpCookie(name);
261 AddCookie(cookie, true);
262 _response.OnCookieAdd(cookie);
265 if (cookie != null) {
266 EnsureKeyValidated(name, cookie.Value);
274 /// <para>Indexed value that enables access to a cookie in the collection.</para>
276 public HttpCookie this[String name]
278 get { return Get(name);}
288 /// Returns an <see cref='System.Web.HttpCookie'/>
289 /// item from the collection.
292 public HttpCookie Get(int index) {
293 HttpCookie cookie = (HttpCookie)BaseGet(index);
295 // Call GetKey so that we can pass the key to the validation routine.
296 if (cookie != null) {
297 EnsureKeyValidated(GetKey(index), cookie.Value);
305 /// Returns key name from collection.
308 public String GetKey(int index) {
309 return BaseGetKey(index);
315 /// Default property.
316 /// Indexed property that enables access to a cookie in the collection.
319 public HttpCookie this[int index]
321 get { return Get(index);}
325 // Access to keys and values as arrays
335 /// an array of all cookie keys in the cookie collection.
338 public String[] AllKeys {
340 if (_allKeys == null)
341 _allKeys = BaseGetAllKeys();