[OSX] Reset errors to 0 if the OS tells us that (1) the chain is ok, (2) the certific...
[mono.git] / mcs / class / System / System.Net / Cookie.cs
1 //
2 // System.Net.Cookie.cs
3 //
4 // Authors:
5 //      Lawrence Pit (loz@cable.a2000.nl)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //      Daniel Nauck    (dna(at)mono-project(dot)de)
8 //      Sebastien Pouliot  <sebastien@ximian.com>
9 //
10 // Copyright (C) 2004,2009 Novell, Inc (http://www.novell.com)
11 //
12
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System;
35 using System.Text;
36 using System.Globalization;
37 using System.Collections;
38
39 namespace System.Net {
40
41         // Supported cookie formats are:
42         // Netscape: http://home.netscape.com/newsref/std/cookie_spec.html
43         // RFC 2109: http://www.ietf.org/rfc/rfc2109.txt
44         // RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
45         [Serializable]
46         public sealed class Cookie 
47         {
48                 string comment;
49                 Uri commentUri;
50                 bool discard;
51                 string domain;
52                 DateTime expires;
53                 bool httpOnly;
54                 string name;
55                 string path;
56                 string port;
57                 int [] ports;
58                 bool secure;
59                 DateTime timestamp;
60                 string val;
61                 int version;
62                 
63                 static char [] reservedCharsName = new char [] {' ', '=', ';', ',', '\n', '\r', '\t'};
64                 static char [] portSeparators = new char [] {'"', ','};
65                 static string tspecials = "()<>@,;:\\\"/[]?={} \t";   // from RFC 2965, 2068
66
67                 public Cookie ()
68                 {
69                         expires = DateTime.MinValue;
70                         timestamp = DateTime.Now;
71                         domain = String.Empty;
72                         name = String.Empty;
73                         val = String.Empty;
74                         comment = String.Empty;
75                         port = String.Empty;
76                 }
77
78                 public Cookie (string name, string value)
79                         : this ()
80                 {
81                         Name = name;
82                         Value = value;
83                 }
84
85                 public Cookie (string name, string value, string path) 
86                         : this (name, value) 
87                 {
88                         Path = path;
89                 }
90
91                 public Cookie (string name, string value, string path, string domain)
92                         : this (name, value, path)
93                 {
94                         Domain = domain;
95                 }
96
97                 public string Comment {
98                         get { return comment; }
99                         set { comment = value == null ? String.Empty : value; }
100                 }
101
102                 public Uri CommentUri {
103                         get { return commentUri; }
104                         set { commentUri = value; }
105                 }
106
107                 public bool Discard {
108                         get { return discard; }
109                         set { discard = value; }
110                 }
111
112                 public string Domain {
113                         get { return domain; }
114                         set {
115                                 if (String.IsNullOrEmpty (value)) {
116                                         domain = String.Empty;
117                                         ExactDomain = true;
118                                 } else {
119                                         domain = value;
120                                         IPAddress test;
121                                         if (IPAddress.TryParse (value, out test))
122                                                 ExactDomain = true;
123                                         else
124                                                 ExactDomain = false;
125                                 }
126                         }
127                 }
128
129                 internal bool ExactDomain { get; set; }
130
131                 public bool Expired {
132                         get { 
133                                 return expires <= DateTime.Now && 
134                                        expires != DateTime.MinValue;
135                         }
136                         set {  
137                                 if (value)
138                                         expires = DateTime.Now;
139                         }
140                 }
141
142                 public DateTime Expires {
143                         get { return expires; }
144                         set { expires = value; }
145                 }
146
147                 public bool HttpOnly {
148                         get { return httpOnly; }
149                         set { httpOnly = value; }
150                 }
151
152                 public string Name {
153                         get { return name; }
154                         set { 
155                                 if (String.IsNullOrEmpty (value))
156                                         throw new CookieException ("Name cannot be empty");
157                                 
158                                 if (value [0] == '$' || value.IndexOfAny (reservedCharsName) != -1) {
159                                         // see CookieTest, according to MS implementation
160                                         // the name value changes even though it's incorrect
161                                         name = String.Empty;
162                                         throw new CookieException ("Name contains invalid characters");
163                                 }
164                                         
165                                 name = value; 
166                         }
167                 }
168
169                 public string Path {
170                         get { return (path == null) ? String.Empty : path; }
171                         set { path = (value == null) ? String.Empty : value; }
172                 }
173
174                 public string Port {
175                         get { return port; }
176                         set { 
177                                 if (String.IsNullOrEmpty (value)) {
178                                         port = String.Empty;
179                                         return;
180                                 }
181                                 if (value [0] != '"' || value [value.Length - 1] != '"') {
182                                         throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Port must be enclosed by double quotes.");
183                                 }
184                                 port = value; 
185                                 string [] values = port.Split (portSeparators);
186                                 ports = new int[values.Length];
187                                 for (int i = 0; i < ports.Length; i++) {
188                                         ports [i] = Int32.MinValue;
189                                         if (values [i].Length == 0)
190                                                 continue;
191                                         try {                                           
192                                                 ports [i] = Int32.Parse (values [i]);
193                                         } catch (Exception e) {
194                                                 throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Invalid value: " + values [i], e);
195                                         }
196                                 }
197                                 Version = 1;
198                         }
199                 }
200
201                 internal int [] Ports {
202                         get { return ports; }
203                         set { ports = value; }
204                 }
205
206                 public bool Secure {
207                         get { return secure; }
208                         set { secure = value; }
209                 }
210
211                 public DateTime TimeStamp {
212                         get { return timestamp; }
213                 }
214
215                 public string Value {
216                         get { return val; }
217                         set { 
218                                 if (value == null) {
219                                         val = String.Empty;
220                                         return;
221                                 }
222                                 
223                                 // LAMESPEC: According to .Net specs the Value property should not accept 
224                                 // the semicolon and comma characters, yet it does. For now we'll follow
225                                 // the behaviour of MS.Net instead of the specs.
226                                 /*
227                                 if (value.IndexOfAny(reservedCharsValue) != -1)
228                                         throw new CookieException("Invalid value. Value cannot contain semicolon or comma characters.");
229                                 */
230                                 
231                                 val = value; 
232                         }
233                 }
234                 
235                 public int Version {
236                         get { return version; }
237                         set { 
238                                 if ((value < 0) || (value > 10)) 
239                                         version = 0;
240                                 else 
241                                         version = value; 
242                         }
243                 }
244
245                 public override bool Equals (Object comparand) 
246                 {
247                         System.Net.Cookie c = comparand as System.Net.Cookie;                   
248                         
249                         return c != null &&
250                                String.Compare (this.name, c.name, true, CultureInfo.InvariantCulture) == 0 &&
251                                String.Compare (this.val, c.val, false, CultureInfo.InvariantCulture) == 0 &&
252                                String.Compare (this.Path, c.Path, false, CultureInfo.InvariantCulture) == 0 &&
253                                String.Compare (this.domain, c.domain, true, CultureInfo.InvariantCulture) == 0 &&
254                                this.version == c.version;
255                 }
256
257                 public override int GetHashCode ()
258                 {
259                         return hash(CaseInsensitiveHashCodeProvider.DefaultInvariant.GetHashCode(name),
260                                     val.GetHashCode (),
261                                     Path.GetHashCode (),
262                                     CaseInsensitiveHashCodeProvider.DefaultInvariant.GetHashCode (domain),
263                                     version);
264                 }
265
266                 private static int hash (int i, int j, int k, int l, int m) 
267                 {
268                         return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25) ^ (m << 20 | m >> 12);
269                 }
270
271                 // returns a string that can be used to send a cookie to an Origin Server
272                 // i.e., only used for clients
273                 // see para 4.2.2 of RFC 2109 and para 3.3.4 of RFC 2965
274                 // see also bug #316017
275                 public override string ToString () 
276                 {
277                         return ToString (null);
278                 }
279
280                 internal string ToString (Uri uri)
281                 {
282                         if (name.Length == 0) 
283                                 return String.Empty;
284
285                         StringBuilder result = new StringBuilder (64);
286         
287                         if (version > 0)
288                                 result.Append ("$Version=").Append (version).Append ("; ");             
289                                 
290                         result.Append (name).Append ("=").Append (val);
291                         
292                         if (version == 0)
293                                 return result.ToString ();
294
295                         if (!String.IsNullOrEmpty (path))
296                                 result.Append ("; $Path=").Append (path);
297
298                         bool append_domain = (uri == null) || (uri.Host != domain);
299                         if (append_domain && !String.IsNullOrEmpty (domain))
300                                 result.Append ("; $Domain=").Append (domain);                   
301         
302                         if (port != null && port.Length != 0)
303                                 result.Append ("; $Port=").Append (port);       
304                                                 
305                         return result.ToString ();
306                 }
307
308                 internal string ToClientString () 
309                 {
310                         if (name.Length == 0) 
311                                 return String.Empty;
312
313                         StringBuilder result = new StringBuilder (64);
314         
315                         if (version > 0) 
316                                 result.Append ("Version=").Append (version).Append (";");
317                                 
318                         result.Append (name).Append ("=").Append (val);
319
320                         if (path != null && path.Length != 0)
321                                 result.Append (";Path=").Append (QuotedString (path));
322                                 
323                         if (domain != null && domain.Length != 0)
324                                 result.Append (";Domain=").Append (QuotedString (domain));                      
325         
326                         if (port != null && port.Length != 0)
327                                 result.Append (";Port=").Append (port); 
328                                                 
329                         return result.ToString ();
330                 }
331
332                 // See par 3.6 of RFC 2616
333                 string QuotedString (string value)
334                 {
335                         if (version == 0 || IsToken (value))
336                                 return value;
337                         else 
338                                 return "\"" + value.Replace("\"", "\\\"") + "\"";
339                 }                                   
340
341                 bool IsToken (string value) 
342                 {
343                         int len = value.Length;
344                         for (int i = 0; i < len; i++) {
345                                 char c = value [i];
346                                 if (c < 0x20 || c >= 0x7f || tspecials.IndexOf (c) != -1)
347                                         return false;
348                         }
349                         return true;
350                 }           
351         }
352 }
353