2009-08-17 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / System / System.Net / CookieContainer.cs
1 //
2 // System.Net.CookieContainer
3 //
4 // Authors:
5 //      Lawrence Pit (loz@cable.a2000.nl)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
9 // (c) Copyright 2004 Ximian, Inc. (http://www.ximian.com)
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.Collections;
35 using System.Globalization;
36 using System.Runtime.Serialization;
37 using System.Text;
38
39 namespace System.Net 
40 {
41         [Serializable]
42         [MonoTODO ("Need to remove older/unused cookies if it reaches the maximum capacity")]
43 #if NET_2_1
44         public sealed class CookieContainer {
45 #else
46         public class CookieContainer {
47 #endif
48                 public const int DefaultCookieLengthLimit = 4096;
49                 public const int DefaultCookieLimit = 300;
50                 public const int DefaultPerDomainCookieLimit = 20;
51
52                 int count;
53                 int capacity = DefaultCookieLimit;
54                 int perDomainCapacity = DefaultPerDomainCookieLimit;
55                 int maxCookieSize = DefaultCookieLengthLimit;
56                 CookieCollection cookies;
57                                 
58                 // ctors
59                 public CookieContainer ()
60                 { 
61                 } 
62         
63                 public CookieContainer (int capacity)
64                 {
65                         if (capacity <= 0)
66 #if NET_2_0
67                                 throw new ArgumentException ("Must be greater than zero", "Capacity");
68 #else
69                                 throw new ArgumentException ("Capacity");
70 #endif
71
72                         this.capacity = capacity;
73                 }
74                 
75                 public CookieContainer (int capacity, int perDomainCapacity, int maxCookieSize)
76                         : this (capacity)
77                 {
78                         if (perDomainCapacity != Int32.MaxValue && (perDomainCapacity <= 0 || perDomainCapacity > capacity))
79 #if NET_2_0
80                                 throw new ArgumentOutOfRangeException ("perDomainCapacity",
81                                         string.Format ("PerDomainCapacity must be " +
82                                         "greater than {0} and less than {1}.", 0,
83                                         capacity));
84 #else
85                                 throw new ArgumentException ("PerDomainCapacity");
86 #endif
87
88                         if (maxCookieSize <= 0)
89 #if NET_2_0
90                                 throw new ArgumentException ("Must be greater than zero", "MaxCookieSize");
91 #else
92                                 throw new ArgumentException ("MaxCookieSize");
93 #endif
94
95                         this.perDomainCapacity = perDomainCapacity;
96                         this.maxCookieSize = maxCookieSize;
97                 }
98
99                 // properties
100                 
101                 public int Count { 
102                         get { return count; }
103                 }
104                 
105                 public int Capacity {
106                         get { return capacity; }
107                         set { 
108                                 if (value < 0 || (value < perDomainCapacity && perDomainCapacity != Int32.MaxValue))
109                                         throw new ArgumentOutOfRangeException ("value",
110                                                 string.Format ("Capacity must be greater " +
111                                                 "than {0} and less than {1}.", 0,
112                                                 perDomainCapacity));
113                                 capacity = value;
114                         }
115                 }
116                 
117                 public int MaxCookieSize {
118                         get { return maxCookieSize; }
119                         set {
120                                 if (value <= 0)
121                                         throw new ArgumentOutOfRangeException ("value");
122                                 maxCookieSize = value;
123                         }
124                 }
125                 
126                 public int PerDomainCapacity {
127                         get { return perDomainCapacity; }
128                         set {
129                                 if (value != Int32.MaxValue && (value <= 0 || value > capacity))
130                                         throw new ArgumentOutOfRangeException ("value");
131                                 perDomainCapacity = value;
132                         }
133                 }
134                 
135                 public void Add (Cookie cookie) 
136                 {
137                         if (cookie == null)
138                                 throw new ArgumentNullException ("cookie");
139
140                         if (cookie.Domain == "")
141 #if NET_2_0
142                                 throw new ArgumentException ("Cookie domain not set.", "cookie.Domain");
143 #else
144                                 throw new ArgumentException ("cookie.Domain");
145 #endif
146
147                         if (cookie.Value.Length > maxCookieSize)
148                                 throw new CookieException ("value is larger than MaxCookieSize.");
149
150                         AddCookie (cookie);
151                 }
152
153                 void AddCookie (Cookie cookie)
154                 {
155                         if (cookies == null)
156                                 cookies = new CookieCollection ();
157
158                         if (count + 1 > capacity)
159                                 throw new CookieException ("Capacity exceeded");
160
161                         cookies.Add (cookie);
162                         count = cookies.Count;
163                         CheckExpiration ();
164
165                 }
166
167                 // Only needs to be called from AddCookie (Cookie) and GetCookies (Uri)
168                 void CheckExpiration ()
169                 {
170                         if (cookies == null)
171                                 return;
172
173                         ArrayList removed = null;
174                         for (int i = cookies.Count - 1; i >= 0; i--) {
175                                 Cookie cookie = cookies [i];
176                                 if (cookie.Expired) {
177                                         if (removed == null)
178                                                 removed = new ArrayList ();
179                                         removed.Add (i);
180                                 }
181                         }
182
183                         if (removed != null) {
184                                 // We went backwards above, so this works.
185                                 ArrayList list = cookies.List;
186                                 foreach (int n in removed) {
187                                         list.RemoveAt (n);
188                                 }
189                         }
190                 }
191
192                 public void Add (CookieCollection cookies)
193                 {
194                         if (cookies == null)
195                                 throw new ArgumentNullException ("cookies");
196
197                         foreach (Cookie cookie in cookies)
198                                 Add (cookie);
199                 }
200
201                 void Cook (Uri uri, Cookie cookie)
202                 {
203                         if (cookie.Name == null || cookie.Name == "")
204                                 throw new CookieException ("Invalid cookie: name");
205
206                         if (cookie.Value == null)
207                                 throw new CookieException ("Invalid cookie: value");
208
209                         if (uri != null && cookie.Domain == "")
210                                 cookie.Domain = uri.Host;
211
212                         if (cookie.Version == 0 && (cookie.Path == null || cookie.Path == "")) {
213                                 if (uri != null) {
214                                         cookie.Path = uri.AbsolutePath;
215                                 } else {
216                                         cookie.Path = "/";
217                                 }
218                         }
219
220                         if (cookie.Port == "" && uri != null && !uri.IsDefaultPort) {
221                                 cookie.Port = "\"" + uri.Port.ToString () + "\"";
222                         }
223                 }
224
225                 public void Add (Uri uri, Cookie cookie)
226                 {
227                         if (uri == null)
228                                 throw new ArgumentNullException ("uri");
229
230                         if (cookie == null)
231                                 throw new ArgumentNullException ("cookie");
232
233                         Cook (uri, cookie);
234                         AddCookie (cookie);
235                 }
236
237                 public void Add (Uri uri, CookieCollection cookies)
238                 {
239                         if (uri == null)
240                                 throw new ArgumentNullException ("uri");
241
242                         if (cookies == null)
243                                 throw new ArgumentNullException ("cookies");
244
245                         foreach (Cookie c in cookies) {
246                                 Cook (uri, c);
247                                 AddCookie (c);
248                         }
249                 }               
250
251                 public string GetCookieHeader (Uri uri)
252                 {
253                         if (uri == null)
254                                 throw new ArgumentNullException ("uri");
255
256                         CookieCollection coll = GetCookies (uri);
257                         if (coll.Count == 0)
258                                 return "";
259
260                         StringBuilder result = new StringBuilder ();
261                         foreach (Cookie cookie in coll) {
262                                 result.Append (cookie.ToString ());
263                                 result.Append ("; ");
264                         }
265
266                         if (result.Length > 0)
267                                 result.Length -= 2; // remove trailing semicolon and space
268
269                         return result.ToString ();
270                 }
271
272                 static bool CheckDomain (string domain, string host)
273                 {
274                         if (domain == String.Empty)
275                                 return false;
276
277                         int hlen = host.Length;
278                         int dlen = domain.Length;
279                         if (hlen < dlen)
280                                 return false;
281
282                         if (hlen == dlen)
283                                 return (String.Compare (domain, host, true, CultureInfo.InvariantCulture) == 0);
284
285                         if (domain [0] != '.') {
286                                 domain = "." + domain;
287                                 dlen++;
288                         }
289
290                         string subdomain = host.Substring (hlen - dlen);
291                         return (String.Compare (subdomain, domain, true, CultureInfo.InvariantCulture) == 0);
292                 }
293
294                 public CookieCollection GetCookies (Uri uri)
295                 {
296                         if (uri == null)
297                                 throw new ArgumentNullException ("uri");
298
299                         CheckExpiration ();
300                         CookieCollection coll = new CookieCollection ();
301                         if (cookies == null)
302                                 return coll;
303
304                         foreach (Cookie cookie in cookies) {
305                                 string domain = cookie.Domain;
306                                 if (!CheckDomain (domain, uri.Host))
307                                         continue;
308
309                                 if (cookie.Port != "" && cookie.Ports != null && uri.Port != -1) {
310                                         if (Array.IndexOf (cookie.Ports, uri.Port) == -1)
311                                                 continue;
312                                 }
313
314                                 string path = cookie.Path;
315                                 string uripath = uri.AbsolutePath;
316                                 if (path != "" && path != "/") {
317                                         if (uripath != path) {
318                                                 if (!uripath.StartsWith (path))
319                                                         continue;
320
321                                                 if (path [path.Length - 1] != '/' && uripath.Length > path.Length &&
322                                                     uripath [path.Length] != '/')
323                                                         continue;
324                                         }
325                                 }
326
327                                 if (cookie.Secure && uri.Scheme != "https")
328                                         continue;
329
330                                 coll.Add (cookie);
331                         }
332
333                         coll.SortByPath ();
334                         return coll;
335                 }
336
337                 public void SetCookies (Uri uri, string cookieHeader)
338                 {
339                         if (uri == null)
340                                 throw new ArgumentNullException ("uri");
341                         
342                         if (cookieHeader == null)
343                                 throw new ArgumentNullException ("cookieHeader");
344                         
345                         ParseAndAddCookies (uri, cookieHeader);
346                 }
347
348                 // GetCookieValue, GetCookieName and ParseAndAddCookies copied from HttpRequest.cs
349                 static string GetCookieValue (string str, int length, ref int i)
350                 {
351                         if (i >= length)
352                                 return null;
353
354                         int k = i;
355                         while (k < length && Char.IsWhiteSpace (str [k]))
356                                 k++;
357
358                         int begin = k;
359                         while (k < length && str [k] != ';')
360                                 k++;
361
362                         i = k;
363                         return str.Substring (begin, i - begin).Trim ();
364                 }
365
366                 static string GetCookieName (string str, int length, ref int i)
367                 {
368                         if (i >= length)
369                                 return null;
370
371                         int k = i;
372                         while (k < length && Char.IsWhiteSpace (str [k]))
373                                 k++;
374
375                         int begin = k;
376                         while (k < length && str [k] != ';' &&  str [k] != '=')
377                                 k++;
378
379                         i = k + 1;
380                         return str.Substring (begin, k - begin).Trim ();
381                 }
382
383                 static string GetDir (string path)
384                 {
385                         if (path == null || path == "")
386                                 return "/";
387
388                         int last = path.LastIndexOf ('/');
389                         if (last == -1)
390                                 return "/" + path;
391
392                         return path.Substring (0, last + 1);
393                 }
394                 
395                 void ParseAndAddCookies (Uri uri, string header)
396                 {
397                         if (header.Length == 0)
398                                 return;
399
400                         string [] name_values = header.Trim ().Split (';');
401                         int length = name_values.Length;
402                         Cookie cookie = null;
403                         int pos;
404                         CultureInfo inv = CultureInfo.InvariantCulture;
405                         bool havePath = false;
406                         bool haveDomain = false;
407
408                         for (int i = 0; i < length; i++) {
409                                 pos = 0;
410                                 string name_value = name_values [i].Trim ();
411                                 string name = GetCookieName (name_value, name_value.Length, ref pos);
412                                 if (name == null || name == "")
413                                         throw new CookieException ("Name is empty.");
414
415                                 string value = GetCookieValue (name_value, name_value.Length, ref pos);
416                                 if (cookie != null) {
417                                         if (!havePath && String.Compare (name, "$Path", true, inv) == 0 ||
418                                             String.Compare (name, "path", true, inv) == 0) {
419                                                 havePath = true;
420                                                 cookie.Path = value;
421                                                 continue;
422                                         }
423                                         
424                                         if (!haveDomain && String.Compare (name, "$Domain", true, inv) == 0 ||
425                                             String.Compare (name, "domain", true, inv) == 0) {
426                                                 cookie.Domain = value;
427                                                 haveDomain = true;
428                                                 continue;
429                                         }
430
431                                         if (!havePath)
432                                                 cookie.Path = GetDir (uri.AbsolutePath);
433
434                                         if (!haveDomain)
435                                                 cookie.Domain = uri.Host;
436
437                                         havePath = false;
438                                         haveDomain = false;
439                                         Add (cookie);
440                                         cookie = null;
441                                 }
442                                 cookie = new Cookie (name, value);
443                         }
444
445                         if (cookie != null) {
446                                 if (!havePath)
447                                         cookie.Path = GetDir (uri.AbsolutePath);
448
449                                 if (!haveDomain)
450                                         cookie.Domain = uri.Host;
451
452                                 Add (cookie);
453                         }
454                 }
455
456         } // CookieContainer
457
458 } // System.Net
459