Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Web / HttpCookieCollection.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="HttpCookieCollection.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 /*
8  * Collection of Http cookies for request and response intrinsics
9  * 
10  * Copyright (c) 1998 Microsoft Corporation
11  */
12
13 namespace System.Web {
14     using System;
15     using System.Collections.Generic;
16     using System.Collections.Specialized;
17     using System.Linq;
18     using System.Web.Util;
19
20     /// <devdoc>
21     ///    <para>
22     ///       Provides a type-safe
23     ///       way to manipulate HTTP cookies.
24     ///    </para>
25     /// </devdoc>
26     public sealed class HttpCookieCollection : NameObjectCollectionBase {
27         // Response object to notify about changes in collection
28         private HttpResponse _response;
29
30         // cached All[] arrays
31         private HttpCookie[] _all;
32         private String[] _allKeys;
33         private bool    _changed;
34
35         // for implementing granular request validation
36         private ValidateStringCallback _validationCallback;
37         private HashSet<string> _keysAwaitingValidation;
38
39         internal HttpCookieCollection(HttpResponse response, bool readOnly)
40             : base(StringComparer.OrdinalIgnoreCase)  {
41             _response = response;
42             IsReadOnly = readOnly;
43         }
44
45
46         /// <devdoc>
47         ///    <para>
48         ///       Initializes a new instance of the HttpCookieCollection
49         ///       class.
50         ///    </para>
51         /// </devdoc>
52         public HttpCookieCollection(): base(StringComparer.OrdinalIgnoreCase)  {
53         }
54
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) {
60
61             // We explicitly don't copy validation-related fields, as we want the copy to "reset" validation state.
62
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);
68                 BaseAdd(key, value);
69             }
70
71             IsReadOnly = col.IsReadOnly;
72         }
73
74         internal bool Changed {
75             get { return _changed; }
76             set { _changed = value; }
77         }
78         internal void AddCookie(HttpCookie cookie, bool append) {
79             ThrowIfMaxHttpCollectionKeysExceeded();
80
81             _all = null;
82             _allKeys = null;
83
84             if (append) {
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) {
88                     // mark cookie as new
89                     cookie.Added = true;
90                 }
91                 BaseAdd(cookie.Name, cookie);
92             }
93             else {
94                 if (BaseGet(cookie.Name) != null) {                   
95                     // mark the cookie as changed because we are overriding the existing one
96                     cookie.Changed = true;
97                 }
98                 BaseSet(cookie.Name, cookie);
99             }
100         }
101
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);
110             }
111         }
112
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));
118             }
119         }
120
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;
126         }
127
128         private void EnsureKeyValidated(string key, string value) {
129             if (_keysAwaitingValidation == null) {
130                 // If dynamic validation hasn't been enabled, no-op.
131                 return;
132             }
133
134             if (!_keysAwaitingValidation.Contains(key)) {
135                 // If this key has already been validated (or is excluded), no-op.
136                 return;
137             }
138
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);
144             }
145             _keysAwaitingValidation.Remove(key);
146         }
147
148         internal void MakeReadOnly() {
149             IsReadOnly = true;
150         }
151
152         internal void RemoveCookie(String name) {
153             _all = null;
154             _allKeys = null;
155
156             BaseRemove(name);
157
158             _changed = true;
159         }
160
161         internal void Reset() {
162             _all = null;
163             _allKeys = null;
164
165             BaseClear();
166             _changed = true;
167             _keysAwaitingValidation = null;
168         }
169
170         //
171         //  Public APIs to add / remove
172         //
173
174
175         /// <devdoc>
176         ///    <para>
177         ///       Adds a cookie to the collection.
178         ///    </para>
179         /// </devdoc>
180         public void Add(HttpCookie cookie) {
181             if (_response != null)
182                 _response.BeforeCookieCollectionChange();
183
184             AddCookie(cookie, true);
185
186             if (_response != null)
187                 _response.OnCookieAdd(cookie);
188         }
189         
190
191         /// <devdoc>
192         ///    <para>[To be supplied.]</para>
193         /// </devdoc>
194         public void CopyTo(Array dest, int index) {
195             if (_all == null) {
196                 int n = Count;
197                 HttpCookie[] all = new HttpCookie[n];
198
199                 for (int i = 0; i < n; i++)
200                     all[i] = Get(i);
201
202                 _all = all; // wait until end of loop to set _all reference in case Get throws
203             }
204             _all.CopyTo(dest, index);
205         }
206
207
208         /// <devdoc>
209         ///    <para> Updates the value of a cookie.</para>
210         /// </devdoc>
211         public void Set(HttpCookie cookie) {
212             if (_response != null)
213                 _response.BeforeCookieCollectionChange();
214
215             AddCookie(cookie, false);
216
217             if (_response != null)
218                 _response.OnCookieCollectionChange();
219         }
220
221
222         /// <devdoc>
223         ///    <para>
224         ///       Removes a cookie from the collection.
225         ///    </para>
226         /// </devdoc>
227         public void Remove(String name) {
228             if (_response != null)
229                 _response.BeforeCookieCollectionChange();
230
231             RemoveCookie(name);
232
233             if (_response != null)
234                 _response.OnCookieCollectionChange();
235         }
236
237
238         /// <devdoc>
239         ///    <para>
240         ///       Clears all cookies from the collection.
241         ///    </para>
242         /// </devdoc>
243         public void Clear() {
244             Reset();
245         }
246
247         //
248         //  Access by name
249         //
250
251
252         /// <devdoc>
253         /// <para>Returns an <see cref='System.Web.HttpCookie'/> item from the collection.</para>
254         /// </devdoc>
255         public HttpCookie Get(String name) {
256             HttpCookie cookie = (HttpCookie)BaseGet(name);
257
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);
263             }
264
265             if (cookie != null) {
266                 EnsureKeyValidated(name, cookie.Value);
267             }
268
269             return cookie;
270         }
271
272
273         /// <devdoc>
274         ///    <para>Indexed value that enables access to a cookie in the collection.</para>
275         /// </devdoc>
276         public HttpCookie this[String name]
277         {
278             get { return Get(name);}
279         }
280
281         //
282         // Indexed access
283         //
284
285
286         /// <devdoc>
287         ///    <para>
288         ///       Returns an <see cref='System.Web.HttpCookie'/>
289         ///       item from the collection.
290         ///    </para>
291         /// </devdoc>
292         public HttpCookie Get(int index) {
293             HttpCookie cookie = (HttpCookie)BaseGet(index);
294
295             // Call GetKey so that we can pass the key to the validation routine.
296             if (cookie != null) {
297                 EnsureKeyValidated(GetKey(index), cookie.Value);
298             }
299             return cookie;
300         }
301
302
303         /// <devdoc>
304         ///    <para>
305         ///       Returns key name from collection.
306         ///    </para>
307         /// </devdoc>
308         public String GetKey(int index) {
309             return BaseGetKey(index);
310         }
311
312
313         /// <devdoc>
314         ///    <para>
315         ///       Default property.
316         ///       Indexed property that enables access to a cookie in the collection.
317         ///    </para>
318         /// </devdoc>
319         public HttpCookie this[int index]
320         {
321             get { return Get(index);}
322         }
323
324         //
325         // Access to keys and values as arrays
326         //
327         
328         /*
329          * All keys
330          */
331
332         /// <devdoc>
333         ///    <para>
334         ///       Returns
335         ///       an array of all cookie keys in the cookie collection.
336         ///    </para>
337         /// </devdoc>
338         public String[] AllKeys {
339             get {
340                 if (_allKeys == null)
341                     _allKeys = BaseGetAllKeys();
342
343                 return _allKeys;
344             }
345         }
346     }
347 }