cca9dce9ebf6972d707dc8cb80d336f127d1373c
[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 (host.Length < domain.Length)
262                                 return false;
263
264                         string subdomain = host.Substring (host.Length - domain.Length);
265                         return (String.Compare (subdomain, domain, true, CultureInfo.InvariantCulture) == 0);
266                 }
267
268                 public CookieCollection GetCookies (Uri uri)
269                 {
270                         if (uri == null)
271                                 throw new ArgumentNullException ("uri");
272
273                         CheckExpiration ();
274                         CookieCollection coll = new CookieCollection ();
275                         if (cookies == null)
276                                 return coll;
277
278                         foreach (Cookie cookie in cookies) {
279                                 string domain = cookie.Domain;
280                                 string host = uri.Host;
281                                 if (!CheckDomain (domain, host))
282                                         continue;
283
284                                 if (cookie.Port != "" && cookie.Ports != null && uri.Port != -1) {
285                                         if (Array.IndexOf (cookie.Ports, uri.Port) == -1)
286                                                 continue;
287                                 }
288
289                                 string path = cookie.Path;
290                                 string uripath = uri.AbsolutePath;
291                                 if (path != "" && path != "/") {
292                                         if (uripath != path) {
293                                                 if (!uripath.StartsWith (path))
294                                                         continue;
295
296                                                 if (path [path.Length - 1] != '/' && uripath.Length > path.Length &&
297                                                     uripath [path.Length] != '/')
298                                                         continue;
299                                         }
300                                 }
301
302                                 if (cookie.Secure && uri.Scheme != "https")
303                                         continue;
304
305                                 coll.Add (cookie);
306                         }
307                         
308                         return coll;
309                 }
310
311                 public void SetCookies (Uri uri, string cookieHeader)
312                 {
313                         if (uri == null)
314                                 throw new ArgumentNullException ("uri");
315                         
316                         if (cookieHeader == null)
317                                 throw new ArgumentNullException ("cookieHeader");
318                         
319                         ParseAndAddCookies (uri, cookieHeader);
320                 }
321
322                 // GetCookieValue, GetCookieName and ParseAndAddCookies copied from HttpRequest.cs
323                 static string GetCookieValue (string str, int length, ref int i)
324                 {
325                         if (i >= length)
326                                 return null;
327
328                         int k = i;
329                         while (k < length && Char.IsWhiteSpace (str [k]))
330                                 k++;
331
332                         int begin = k;
333                         while (k < length && str [k] != ';')
334                                 k++;
335
336                         i = k;
337                         return str.Substring (begin, i - begin).Trim ();
338                 }
339
340                 static string GetCookieName (string str, int length, ref int i)
341                 {
342                         if (i >= length)
343                                 return null;
344
345                         int k = i;
346                         while (k < length && Char.IsWhiteSpace (str [k]))
347                                 k++;
348
349                         int begin = k;
350                         while (k < length && str [k] != ';' &&  str [k] != '=')
351                                 k++;
352
353                         i = k + 1;
354                         return str.Substring (begin, k - begin).Trim ();
355                 }
356
357                 static string GetDir (string path)
358                 {
359                         if (path == null || path == "")
360                                 return "/";
361
362                         int last = path.LastIndexOf ('/');
363                         if (last == -1)
364                                 return "/" + path;
365
366                         return path.Substring (0, last + 1);
367                 }
368                 
369                 void ParseAndAddCookies (Uri uri, string header)
370                 {
371                         if (header.Length == 0)
372                                 return;
373
374                         string [] name_values = header.Trim ().Split (';');
375                         int length = name_values.Length;
376                         Cookie cookie = null;
377                         int pos;
378                         CultureInfo inv = CultureInfo.InvariantCulture;
379                         bool havePath = false;
380                         bool haveDomain = false;
381
382                         for (int i = 0; i < length; i++) {
383                                 pos = 0;
384                                 string name_value = name_values [i].Trim ();
385                                 string name = GetCookieName (name_value, name_value.Length, ref pos);
386                                 if (name == null || name == "")
387                                         throw new CookieException ("Name is empty.");
388
389                                 string value = GetCookieValue (name_value, name_value.Length, ref pos);
390                                 if (cookie != null) {
391                                         if (!havePath && String.Compare (name, "$Path", true, inv) == 0 ||
392                                             String.Compare (name, "path", true, inv) == 0) {
393                                                 havePath = true;
394                                                 cookie.Path = value;
395                                                 continue;
396                                         }
397                                         
398                                         if (!haveDomain && String.Compare (name, "$Domain", true, inv) == 0 ||
399                                             String.Compare (name, "domain", true, inv) == 0) {
400                                                 cookie.Domain = value;
401                                                 haveDomain = true;
402                                                 continue;
403                                         }
404
405                                         if (!havePath)
406                                                 cookie.Path = GetDir (uri.AbsolutePath);
407
408                                         if (!haveDomain)
409                                                 cookie.Domain = uri.Host;
410
411                                         havePath = false;
412                                         haveDomain = false;
413                                         Add (cookie);
414                                         cookie = null;
415                                 }
416                                 cookie = new Cookie (name, value);
417                         }
418
419                         if (cookie != null) {
420                                 if (!havePath)
421                                         cookie.Path = GetDir (uri.AbsolutePath);
422
423                                 if (!haveDomain)
424                                         cookie.Domain = uri.Host;
425
426                                 Add (cookie);
427                         }
428                 }
429
430         } // CookieContainer
431
432 } // System.Net
433