New test.
[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         public class CookieContainer
44         {
45                 public const int DefaultCookieLengthLimit = 4096;
46                 public const int DefaultCookieLimit = 300;
47                 public const int DefaultPerDomainCookieLimit = 20;
48
49                 int count;
50                 int capacity = DefaultCookieLimit;
51                 int perDomainCapacity = DefaultPerDomainCookieLimit;
52                 int maxCookieSize = DefaultCookieLengthLimit;
53                 CookieCollection cookies;
54                                 
55                 // ctors
56                 public CookieContainer ()
57                 { 
58                 } 
59         
60                 public CookieContainer (int capacity)
61                 {
62                         if (capacity <= 0)
63                                 throw new ArgumentException ("Must be greater than zero", "capacity");
64
65                         this.capacity = capacity;
66                 }
67                 
68                 public CookieContainer (int capacity, int perDomainCapacity, int maxCookieSize)
69                         : this (capacity)
70                 {
71                         if (perDomainCapacity != Int32.MaxValue && (perDomainCapacity <= 0 || perDomainCapacity > capacity))
72                                 throw new ArgumentException ("Invalid value", "perDomaniCapacity");
73
74                         if (maxCookieSize <= 0)
75                                 throw new ArgumentException ("Must be greater than zero", "maxCookieSize");
76
77                         this.perDomainCapacity = perDomainCapacity;
78                         this.maxCookieSize = maxCookieSize;
79                 }
80
81                 // properties
82                 
83                 public int Count { 
84                         get { return count; }
85                 }
86                 
87                 public int Capacity {
88                         get { return capacity; }
89                         set { 
90                                 if (value < 0 || (value < perDomainCapacity && perDomainCapacity != Int32.MaxValue))
91                                         throw new ArgumentOutOfRangeException ("value");
92
93                                 if (value < maxCookieSize)
94                                         maxCookieSize = value;
95
96                                 capacity = value;                                                       
97                         }
98                 }
99                 
100                 public int MaxCookieSize {
101                         get { return maxCookieSize; }
102                         set {
103                                 if (value <= 0)
104                                         throw new ArgumentOutOfRangeException ("value");                                
105                                 maxCookieSize = value;
106                         }
107                 }
108                 
109                 public int PerDomainCapacity {
110                         get { return perDomainCapacity; }
111                         set {
112                                 if (value != Int32.MaxValue && (value <= 0 || value > capacity))
113                                         throw new ArgumentOutOfRangeException ("value");                                        
114
115                                 perDomainCapacity = value;
116                         }
117                 }
118                 
119                 public void Add (Cookie cookie) 
120                 {
121                         if (cookie == null)
122                                 throw new ArgumentNullException ("cookie");
123
124                         if (cookie.Domain == "")
125                                 throw new ArgumentException ("Cookie domain not set.", "cookie");
126
127                         if (cookie.Value.Length > maxCookieSize)
128                                 throw new CookieException ("value is larger than MaxCookieSize.");
129
130                         AddCookie (cookie);
131                 }
132
133                 void AddCookie (Cookie cookie)
134                 {
135                         if (cookies == null)
136                                 cookies = new CookieCollection ();
137
138                         if (count + 1 > capacity)
139                                 throw new CookieException ("Capacity exceeded");
140
141                         cookies.Add (cookie);
142                         count = cookies.Count;
143                         CheckExpiration ();
144
145                 }
146
147                 // Only needs to be called from AddCookie (Cookie) and GetCookies (Uri)
148                 void CheckExpiration ()
149                 {
150                         if (cookies == null)
151                                 return;
152
153                         ArrayList removed = null;
154                         for (int i = cookies.Count - 1; i >= 0; i--) {
155                                 Cookie cookie = cookies [i];
156                                 if (cookie.Expired) {
157                                         if (removed == null)
158                                                 removed = new ArrayList ();
159                                         removed.Add (i);
160                                 }
161                         }
162
163                         if (removed != null) {
164                                 // We went backwards above, so this works.
165                                 ArrayList list = cookies.List;
166                                 foreach (int n in removed) {
167                                         list.RemoveAt (n);
168                                 }
169                         }
170                 }
171
172                 public void Add (CookieCollection cookies)
173                 {
174                         if (cookies == null)
175                                 throw new ArgumentNullException ("cookies");
176
177                         foreach (Cookie cookie in cookies)
178                                 Add (cookie);
179                 }
180
181                 void Cook (Uri uri, Cookie cookie)
182                 {
183                         if (cookie.Name == null || cookie.Name == "")
184                                 throw new CookieException ("Invalid cookie: name");
185
186                         if (cookie.Value == null)
187                                 throw new CookieException ("Invalid cookie: value");
188
189                         if (uri != null && cookie.Domain == "")
190                                 cookie.Domain = uri.Host;
191
192                         if (cookie.Version == 0 && (cookie.Path == null || cookie.Path == "")) {
193                                 if (uri != null) {
194                                         cookie.Path = uri.AbsolutePath;
195                                 } else {
196                                         cookie.Path = "/";
197                                 }
198                         }
199
200                         if (cookie.Port == "" && uri != null && !uri.IsDefaultPort) {
201                                 cookie.Port = "\"" + uri.Port.ToString () + "\"";
202                         }
203                 }
204
205                 public void Add (Uri uri, Cookie cookie)
206                 {
207                         if (uri == null)
208                                 throw new ArgumentNullException ("uri");
209
210                         if (cookie == null)
211                                 throw new ArgumentNullException ("cookie");
212
213                         Cook (uri, cookie);
214                         AddCookie (cookie);
215                 }
216
217                 public void Add (Uri uri, CookieCollection cookies)
218                 {
219                         if (uri == null)
220                                 throw new ArgumentNullException ("uri");
221
222                         if (cookies == null)
223                                 throw new ArgumentNullException ("cookies");
224
225                         foreach (Cookie c in cookies) {
226                                 Cook (uri, c);
227                                 AddCookie (c);
228                         }
229                 }               
230
231                 public string GetCookieHeader (Uri uri)
232                 {
233                         if (uri == null)
234                                 throw new ArgumentNullException ("uri");
235
236                         CookieCollection coll = GetCookies (uri);
237                         if (coll.Count == 0)
238                                 return "";
239
240                         StringBuilder result = new StringBuilder ();
241                         foreach (Cookie cookie in coll) {
242                                 result.Append (cookie.ToString ());
243                                 result.Append (';');
244                         }
245
246                         if (result.Length > 0)
247                                 result.Length--; // remove trailing semicolon
248
249                         return result.ToString ();
250                 }
251
252                 static bool CheckDomain (string domain, string host)
253                 {
254                         if (domain != "" && domain [0] != '.')
255                                 return (String.Compare (domain, host, true, CultureInfo.InvariantCulture) == 0);
256
257                         int dot = host.IndexOf ('.');
258                         if (dot == -1)
259                                 return (String.Compare (host, domain, true, CultureInfo.InvariantCulture) == 0);
260
261                         if (dot == 0 && String.Compare ("." + host, domain, true, CultureInfo.InvariantCulture) == 0)
262                             return true;
263         
264                         if (host.Length < domain.Length)
265                                 return false;
266
267                         string subdomain = host.Substring (host.Length - domain.Length);
268                         return (String.Compare (subdomain, domain, true, CultureInfo.InvariantCulture) == 0);
269                 }
270
271                 public CookieCollection GetCookies (Uri uri)
272                 {
273                         if (uri == null)
274                                 throw new ArgumentNullException ("uri");
275
276                         CheckExpiration ();
277                         CookieCollection coll = new CookieCollection ();
278                         if (cookies == null)
279                                 return coll;
280
281                         foreach (Cookie cookie in cookies) {
282                                 string domain = cookie.Domain;
283                                 string host = uri.Host;
284                                 if (!CheckDomain (domain, host))
285                                         continue;
286
287                                 if (cookie.Port != "" && cookie.Ports != null && uri.Port != -1) {
288                                         if (Array.IndexOf (cookie.Ports, uri.Port) == -1)
289                                                 continue;
290                                 }
291
292                                 string path = cookie.Path;
293                                 string uripath = uri.AbsolutePath;
294                                 if (path != "" && path != "/") {
295                                         if (uripath != path) {
296                                                 if (!uripath.StartsWith (path))
297                                                         continue;
298
299                                                 if (path [path.Length - 1] != '/' && uripath.Length > path.Length &&
300                                                     uripath [path.Length] != '/')
301                                                         continue;
302                                         }
303                                 }
304
305                                 if (cookie.Secure && uri.Scheme != "https")
306                                         continue;
307
308                                 coll.Add (cookie);
309                         }
310
311                         coll.SortByPath ();
312                         return coll;
313                 }
314
315                 public void SetCookies (Uri uri, string cookieHeader)
316                 {
317                         if (uri == null)
318                                 throw new ArgumentNullException ("uri");
319                         
320                         if (cookieHeader == null)
321                                 throw new ArgumentNullException ("cookieHeader");
322                         
323                         ParseAndAddCookies (uri, cookieHeader);
324                 }
325
326                 // GetCookieValue, GetCookieName and ParseAndAddCookies copied from HttpRequest.cs
327                 static string GetCookieValue (string str, int length, ref int i)
328                 {
329                         if (i >= length)
330                                 return null;
331
332                         int k = i;
333                         while (k < length && Char.IsWhiteSpace (str [k]))
334                                 k++;
335
336                         int begin = k;
337                         while (k < length && str [k] != ';')
338                                 k++;
339
340                         i = k;
341                         return str.Substring (begin, i - begin).Trim ();
342                 }
343
344                 static string GetCookieName (string str, int length, ref int i)
345                 {
346                         if (i >= length)
347                                 return null;
348
349                         int k = i;
350                         while (k < length && Char.IsWhiteSpace (str [k]))
351                                 k++;
352
353                         int begin = k;
354                         while (k < length && str [k] != ';' &&  str [k] != '=')
355                                 k++;
356
357                         i = k + 1;
358                         return str.Substring (begin, k - begin).Trim ();
359                 }
360
361                 static string GetDir (string path)
362                 {
363                         if (path == null || path == "")
364                                 return "/";
365
366                         int last = path.LastIndexOf ('/');
367                         if (last == -1)
368                                 return "/" + path;
369
370                         return path.Substring (0, last + 1);
371                 }
372                 
373                 void ParseAndAddCookies (Uri uri, string header)
374                 {
375                         if (header.Length == 0)
376                                 return;
377
378                         string [] name_values = header.Trim ().Split (';');
379                         int length = name_values.Length;
380                         Cookie cookie = null;
381                         int pos;
382                         CultureInfo inv = CultureInfo.InvariantCulture;
383                         bool havePath = false;
384                         bool haveDomain = false;
385
386                         for (int i = 0; i < length; i++) {
387                                 pos = 0;
388                                 string name_value = name_values [i].Trim ();
389                                 string name = GetCookieName (name_value, name_value.Length, ref pos);
390                                 if (name == null || name == "")
391                                         throw new CookieException ("Name is empty.");
392
393                                 string value = GetCookieValue (name_value, name_value.Length, ref pos);
394                                 if (cookie != null) {
395                                         if (!havePath && String.Compare (name, "$Path", true, inv) == 0 ||
396                                             String.Compare (name, "path", true, inv) == 0) {
397                                                 havePath = true;
398                                                 cookie.Path = value;
399                                                 continue;
400                                         }
401                                         
402                                         if (!haveDomain && String.Compare (name, "$Domain", true, inv) == 0 ||
403                                             String.Compare (name, "domain", true, inv) == 0) {
404                                                 cookie.Domain = value;
405                                                 haveDomain = true;
406                                                 continue;
407                                         }
408
409                                         if (!havePath)
410                                                 cookie.Path = GetDir (uri.AbsolutePath);
411
412                                         if (!haveDomain)
413                                                 cookie.Domain = uri.Host;
414
415                                         havePath = false;
416                                         haveDomain = false;
417                                         Add (cookie);
418                                         cookie = null;
419                                 }
420                                 cookie = new Cookie (name, value);
421                         }
422
423                         if (cookie != null) {
424                                 if (!havePath)
425                                         cookie.Path = GetDir (uri.AbsolutePath);
426
427                                 if (!haveDomain)
428                                         cookie.Domain = uri.Host;
429
430                                 Add (cookie);
431                         }
432                 }
433
434         } // CookieContainer
435
436 } // System.Net
437