Update mcs/class/Commons.Xml.Relaxng/Commons.Xml.Relaxng/RelaxngPattern.cs
[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 //      Sebastien Pouliot  <sebastien@ximian.com>
8 //  Marek Safar (marek.safar@gmail.com)
9 //
10 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
11 // (c) Copyright 2004 Ximian, Inc. (http://www.ximian.com)
12 // Copyright (C) 2009 Novell, Inc (http://www.novell.com)
13 // Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
14
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System;
37 using System.Collections;
38 using System.Globalization;
39 using System.Runtime.Serialization;
40 using System.Text;
41 using System.Text.RegularExpressions;
42
43 namespace System.Net 
44 {
45         [Serializable]
46 #if MOONLIGHT
47         #if INSIDE_SYSTEM
48         internal sealed class CookieContainer {
49         #else 
50         public sealed class CookieContainer {
51         #endif
52 #else
53         public class CookieContainer {
54 #endif
55                 public const int DefaultCookieLengthLimit = 4096;
56                 public const int DefaultCookieLimit = 300;
57                 public const int DefaultPerDomainCookieLimit = 20;
58
59                 int capacity = DefaultCookieLimit;
60                 int perDomainCapacity = DefaultPerDomainCookieLimit;
61                 int maxCookieSize = DefaultCookieLengthLimit;
62                 CookieCollection cookies;
63                                 
64                 // ctors
65                 public CookieContainer ()
66                 { 
67                 } 
68         
69                 public CookieContainer (int capacity)
70                 {
71                         if (capacity <= 0)
72                                 throw new ArgumentException ("Must be greater than zero", "Capacity");
73
74                         this.capacity = capacity;
75                 }
76                 
77                 public CookieContainer (int capacity, int perDomainCapacity, int maxCookieSize)
78                         : this (capacity)
79                 {
80                         if (perDomainCapacity != Int32.MaxValue && (perDomainCapacity <= 0 || perDomainCapacity > capacity))
81                                 throw new ArgumentOutOfRangeException ("perDomainCapacity",
82                                         string.Format ("PerDomainCapacity must be " +
83                                         "greater than {0} and less than {1}.", 0,
84                                         capacity));
85
86                         if (maxCookieSize <= 0)
87                                 throw new ArgumentException ("Must be greater than zero", "MaxCookieSize");
88
89                         this.perDomainCapacity = perDomainCapacity;
90                         this.maxCookieSize = maxCookieSize;
91                 }
92
93                 // properties
94                 
95                 public int Count { 
96                         get { return (cookies == null) ? 0 : cookies.Count; }
97                 }
98                 
99                 public int Capacity {
100                         get { return capacity; }
101                         set { 
102                                 if (value < 0 || (value < perDomainCapacity && perDomainCapacity != Int32.MaxValue))
103                                         throw new ArgumentOutOfRangeException ("value",
104                                                 string.Format ("Capacity must be greater " +
105                                                 "than {0} and less than {1}.", 0,
106                                                 perDomainCapacity));
107                                 capacity = value;
108                         }
109                 }
110                 
111                 public int MaxCookieSize {
112                         get { return maxCookieSize; }
113                         set {
114                                 if (value <= 0)
115                                         throw new ArgumentOutOfRangeException ("value");
116                                 maxCookieSize = value;
117                         }
118                 }
119                 
120                 public int PerDomainCapacity {
121                         get { return perDomainCapacity; }
122                         set {
123                                 if (value != Int32.MaxValue && (value <= 0 || value > capacity))
124                                         throw new ArgumentOutOfRangeException ("value");
125                                 perDomainCapacity = value;
126                         }
127                 }
128                 
129                 public void Add (Cookie cookie) 
130                 {
131                         if (cookie == null)
132                                 throw new ArgumentNullException ("cookie");
133
134                         if (cookie.Domain.Length == 0)
135                                 throw new ArgumentException ("Cookie domain not set.", "cookie.Domain");
136
137                         if (cookie.Value.Length > maxCookieSize)
138                                 throw new CookieException ("value is larger than MaxCookieSize.");
139
140                         // .NET's Add (Cookie) is fundamentally broken and does not copy properties
141                         // like Secure, HttpOnly and Expires so we clone the parts that .NET
142                         // does keep before calling AddCookie
143                         Cookie c = new Cookie (cookie.Name, cookie.Value);
144                         c.Path = (cookie.Path.Length == 0) ? "/" : cookie.Path;
145                         c.Domain = cookie.Domain;
146                         c.ExactDomain = cookie.ExactDomain;
147                         c.Version = cookie.Version;
148                         
149                         AddCookie (c);
150                 }
151
152                 void AddCookie (Cookie cookie)
153                 {
154                         if (cookies == null)
155                                 cookies = new CookieCollection ();
156
157                         if (cookies.Count >= capacity)
158                                 RemoveOldest (null);
159
160                         // try to avoid counting per-domain
161                         if (cookies.Count >= perDomainCapacity) {
162                                 if (CountDomain (cookie.Domain) >= perDomainCapacity)
163                                         RemoveOldest (cookie.Domain);
164                         }
165
166                         // clone the important parts of the cookie
167                         Cookie c = new Cookie (cookie.Name, cookie.Value);
168                         c.Path = (cookie.Path.Length == 0) ? "/" : cookie.Path;
169                         c.Domain = cookie.Domain;
170                         c.ExactDomain = cookie.ExactDomain;
171                         c.Version = cookie.Version;
172                         c.Expires = cookie.Expires;
173                         c.CommentUri = cookie.CommentUri;
174                         c.Comment = cookie.Comment;
175                         c.Discard = cookie.Discard;
176                         c.HttpOnly = cookie.HttpOnly;
177                         c.Secure = cookie.Secure;
178
179                         cookies.Add (c);
180                         CheckExpiration ();
181
182                 }
183
184                 int CountDomain (string domain)
185                 {
186                         int count = 0;
187                         foreach (Cookie c in cookies) {
188                                 if (CheckDomain (domain, c.Domain, true))
189                                         count++;
190                         }
191                         return count;
192                 }
193
194                 void RemoveOldest (string domain)
195                 {
196                         int n = 0;
197                         DateTime oldest = DateTime.MaxValue;
198                         for (int i = 0; i < cookies.Count; i++) {
199                                 Cookie c = cookies [i];
200                                 if ((c.TimeStamp < oldest) && ((domain == null) || (domain == c.Domain))) {
201                                         oldest = c.TimeStamp;
202                                         n = i;
203                                 }
204                         }
205                         cookies.List.RemoveAt (n);
206                 }
207
208                 // Only needs to be called from AddCookie (Cookie) and GetCookies (Uri)
209                 void CheckExpiration ()
210                 {
211                         if (cookies == null)
212                                 return;
213
214                         for (int i = cookies.Count - 1; i >= 0; i--) {
215                                 Cookie cookie = cookies [i];
216                                 if (cookie.Expired)
217                                         cookies.List.RemoveAt (i);
218                         }
219                 }
220
221                 public void Add (CookieCollection cookies)
222                 {
223                         if (cookies == null)
224                                 throw new ArgumentNullException ("cookies");
225
226                         foreach (Cookie cookie in cookies)
227                                 Add (cookie);
228                 }
229
230                 void Cook (Uri uri, Cookie cookie)
231                 {
232                         if (String.IsNullOrEmpty (cookie.Name))
233                                 throw new CookieException ("Invalid cookie: name");
234
235                         if (cookie.Value == null)
236                                 throw new CookieException ("Invalid cookie: value");
237
238                         if (uri != null && cookie.Domain.Length == 0)
239                                 cookie.Domain = uri.Host;
240
241                         if (cookie.Version == 0 && String.IsNullOrEmpty (cookie.Path)) {
242                                 if (uri != null) {
243                                         cookie.Path = uri.AbsolutePath;
244                                 } else {
245                                         cookie.Path = "/";
246                                 }
247                         }
248
249                         if (cookie.Port.Length == 0 && uri != null && !uri.IsDefaultPort) {
250                                 cookie.Ports = new [] { uri.Port };
251                         }
252                 }
253
254                 public void Add (Uri uri, Cookie cookie)
255                 {
256                         if (uri == null)
257                                 throw new ArgumentNullException ("uri");
258
259                         if (cookie == null)
260                                 throw new ArgumentNullException ("cookie");
261
262                         if (!cookie.Expired) {
263                                 Cook (uri, cookie);
264                                 AddCookie (cookie);
265                         }
266                 }
267
268                 public void Add (Uri uri, CookieCollection cookies)
269                 {
270                         if (uri == null)
271                                 throw new ArgumentNullException ("uri");
272
273                         if (cookies == null)
274                                 throw new ArgumentNullException ("cookies");
275
276                         foreach (Cookie cookie in cookies) {
277                                 if (!cookie.Expired) {
278                                         Cook (uri, cookie);
279                                         AddCookie (cookie);
280                                 }
281                         }
282                 }               
283
284                 public string GetCookieHeader (Uri uri)
285                 {
286                         if (uri == null)
287                                 throw new ArgumentNullException ("uri");
288
289                         CookieCollection coll = GetCookies (uri);
290                         if (coll.Count == 0)
291                                 return "";
292
293                         StringBuilder result = new StringBuilder ();
294                         foreach (Cookie cookie in coll) {
295                                 // don't include the domain since it can be infered from the URI
296                                 // include empty path as '/'
297                                 result.Append (cookie.ToString (uri));
298                                 result.Append ("; ");
299                         }
300
301                         if (result.Length > 0)
302                                 result.Length -= 2; // remove trailing semicolon and space
303
304                         return result.ToString ();
305                 }
306
307                 static bool CheckDomain (string domain, string host, bool exact)
308                 {
309                         if (domain.Length == 0)
310                                 return false;
311
312                         if (exact)
313                                 return (String.Compare (host, domain, StringComparison.InvariantCultureIgnoreCase) == 0);
314
315                         // check for allowed sub-domains - without string allocations
316                         if (!host.EndsWith (domain, StringComparison.InvariantCultureIgnoreCase))
317                                 return false;
318                         // mono.com -> www.mono.com is OK but supermono.com NOT OK
319                         if (domain [0] == '.')
320                                 return true;
321                         int p = host.Length - domain.Length - 1;
322                         if (p < 0)
323                                 return false;
324                         return (host [p] == '.');
325                 }
326
327                 public CookieCollection GetCookies (Uri uri)
328                 {
329                         if (uri == null)
330                                 throw new ArgumentNullException ("uri");
331
332                         CheckExpiration ();
333                         CookieCollection coll = new CookieCollection ();
334                         if (cookies == null)
335                                 return coll;
336
337                         foreach (Cookie cookie in cookies) {
338                                 string domain = cookie.Domain;
339                                 if (!CheckDomain (domain, uri.Host, cookie.ExactDomain))
340                                         continue;
341
342                                 if (cookie.Port.Length > 0 && cookie.Ports != null && uri.Port != -1) {
343                                         if (Array.IndexOf (cookie.Ports, uri.Port) == -1)
344                                                 continue;
345                                 }
346
347                                 string path = cookie.Path;
348                                 string uripath = uri.AbsolutePath;
349                                 if (path != "" && path != "/") {
350                                         if (uripath != path) {
351                                                 if (!uripath.StartsWith (path))
352                                                         continue;
353
354                                                 if (path [path.Length - 1] != '/' && uripath.Length > path.Length &&
355                                                     uripath [path.Length] != '/')
356                                                         continue;
357                                         }
358                                 }
359
360                                 if (cookie.Secure && uri.Scheme != "https")
361                                         continue;
362
363                                 coll.Add (cookie);
364                         }
365
366                         coll.Sort ();
367                         return coll;
368                 }
369
370                 public void SetCookies (Uri uri, string cookieHeader)
371                 {
372                         if (uri == null)
373                                 throw new ArgumentNullException ("uri");
374                         
375                         if (cookieHeader == null)
376                                 throw new ArgumentNullException ("cookieHeader");                       
377                         
378                         if (cookieHeader.Length == 0)
379                                 return;
380                         
381                         // Cookies must be separated by ',' (like documented on MSDN)
382                         // but expires uses DAY, DD-MMM-YYYY HH:MM:SS GMT, so simple ',' search is wrong.
383                         // See http://msdn.microsoft.com/en-us/library/aa384321%28VS.85%29.aspx
384                         string [] jar = cookieHeader.Split (',');
385                         string tmpCookie;
386                         for (int i = 0; i < jar.Length; i++) {
387                                 tmpCookie = jar [i];
388
389                                 if (jar.Length > i + 1
390                                         && Regex.IsMatch (jar[i],
391                                                 @".*expires\s*=\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)",
392                                                 RegexOptions.IgnoreCase) 
393                                         && Regex.IsMatch (jar[i+1],
394                                                 @"\s\d{2}-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{4} \d{2}:\d{2}:\d{2} GMT",
395                                                 RegexOptions.IgnoreCase)) {
396                                         tmpCookie = new StringBuilder (tmpCookie).Append (",").Append (jar [++i]).ToString ();
397                                 }
398
399                                 try {
400                                         Cookie c = Parse (tmpCookie);
401
402                                         // add default values from URI if missing from the string
403                                         if (c.Path.Length == 0) {
404                                                 c.Path = uri.AbsolutePath;
405                                         } else if (!uri.AbsolutePath.StartsWith (c.Path)) {
406                                                 string msg = String.Format ("'Path'='{0}' is invalid with URI", c.Path);
407                                                 throw new CookieException (msg);
408                                         }
409
410                                         if (c.Domain.Length == 0) {
411                                                 c.Domain = uri.Host;
412                                                 // don't consider domain "a.b.com" as ".a.b.com"
413                                                 c.ExactDomain = true;
414                                         }
415
416                                         AddCookie (c);
417                                 }
418                                 catch (Exception e) {
419                                         string msg = String.Format ("Could not parse cookies for '{0}'.", uri);
420                                         throw new CookieException (msg, e);
421                                 }
422                         }
423                 }
424
425                 static Cookie Parse (string s)
426                 {
427                         string [] parts = s.Split (';');
428                         Cookie c = new Cookie ();
429                         for (int i = 0; i < parts.Length; i++) {
430                                 string key, value;
431                                 int sep = parts[i].IndexOf ('=');
432                                 if (sep == -1) {
433                                         key = parts [i].Trim ();
434                                         value = String.Empty;
435                                 } else {
436                                         key = parts [i].Substring (0, sep).Trim ();
437                                         value = parts [i].Substring (sep + 1).Trim ();
438                                 }
439
440                                 switch (key.ToLowerInvariant ()) {
441                                 case "path":
442                                 case "$path":
443                                         if (c.Path.Length == 0)
444                                                 c.Path = value;
445                                         break;
446                                 case "domain":
447                                 case "$domain":
448                                         if (c.Domain.Length == 0) {
449                                                 c.Domain = value;
450                                                 // here mono.com means "*.mono.com"
451                                                 c.ExactDomain = false;
452                                         }
453                                         break;
454                                 case "expires":
455                                 case "$expires":
456                                         if (c.Expires == DateTime.MinValue)
457                                                 c.Expires = DateTime.SpecifyKind (DateTime.ParseExact (value,
458                                                         @"ddd, dd-MMM-yyyy HH:mm:ss G\MT", CultureInfo.InvariantCulture), DateTimeKind.Utc);
459                                                 break;
460                                 case "httponly":
461                                         c.HttpOnly = true;
462                                         break;
463                                 case "secure":
464                                         c.Secure = true;
465                                         break;
466                                 default:
467                                         if (c.Name.Length == 0) {
468                                                 c.Name = key;
469                                                 c.Value = value;
470                                         }
471                                         break;
472                                 }
473                         }
474                         return c;
475                 }
476         }
477 }
478