2009-08-03 Gonzalo Paniagua Javier <gonzalo@novell.com>
[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 //
9 // (c) Copyright 2004 Novell, 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.Text;
35 using System.Globalization;
36 using System.Collections;
37
38 namespace System.Net {
39
40         // Supported cookie formats are:
41         // Netscape: http://home.netscape.com/newsref/std/cookie_spec.html
42         // RFC 2109: http://www.ietf.org/rfc/rfc2109.txt
43         // RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
44         [Serializable]
45         public sealed class Cookie 
46         {
47                 string comment;
48                 Uri commentUri;
49                 bool discard;
50                 string domain;
51 //              bool expired;
52                 DateTime expires;
53 #if NET_2_0             
54                 bool httpOnly;
55 #endif          
56                 string name;
57                 string path;
58                 string port;
59                 int [] ports;
60                 bool secure;
61                 DateTime timestamp;
62                 string val;
63                 int version;
64                 
65                 static char [] reservedCharsName = new char [] {' ', '=', ';', ',', '\n', '\r', '\t'};
66                 static char [] portSeparators = new char [] {'"', ','};
67                 static string tspecials = "()<>@,;:\\\"/[]?={} \t";   // from RFC 2965, 2068
68
69                 public Cookie ()
70                 {
71                         expires = DateTime.MinValue;
72                         timestamp = DateTime.Now;
73                         domain = "";
74                         name = "";
75                         val = "";
76                         comment = "";
77                         port = "";
78                 }
79
80                 public Cookie (string name, string value)
81                         : this ()
82                 {
83                         Name = name;
84                         Value = value;
85                 }
86
87                 public Cookie (string name, string value, string path) 
88                         : this (name, value) 
89                 {
90                         Path = path;
91                 }
92
93                 public Cookie (string name, string value, string path, string domain)
94                         : this (name, value, path)
95                 {
96                         Domain = domain;
97                 }
98
99                 public string Comment {
100                         get { return comment; }
101                         set { comment = value == null ? String.Empty : value; }
102                 }
103
104                 public Uri CommentUri {
105                         get { return commentUri; }
106                         set { commentUri = value; }
107                 }
108
109                 public bool Discard {
110                         get { return discard; }
111                         set { discard = value; }
112                 }
113
114                 public string Domain {
115                         get { return domain; }
116                         set { domain = value == null ? String.Empty : value; }
117                 }
118
119                 public bool Expired {
120                         get { 
121                                 return expires <= DateTime.Now && 
122                                        expires != DateTime.MinValue;
123                         }
124                         set {  
125                                 if (value)
126                                         expires = DateTime.Now;
127                         }
128                 }
129
130                 public DateTime Expires {
131                         get { return expires; }
132                         set { expires = value; }
133                 }
134
135 #if NET_2_0     
136                 public bool HttpOnly
137                 {
138                         get { return httpOnly; }
139                         set { httpOnly = value; }
140                 }
141 #endif
142
143                 public string Name {
144                         get { return name; }
145                         set { 
146                                 if (value == null || value.Length == 0) {
147                                         throw new CookieException ("Name cannot be empty");
148                                 }                       
149                                 
150                                 if (value [0] == '$' || value.IndexOfAny (reservedCharsName) != -1) {
151                                         // see CookieTest, according to MS implementation
152                                         // the name value changes even though it's incorrect
153                                         name = String.Empty;
154                                         throw new CookieException ("Name contains invalid characters");
155                                 }
156                                         
157                                 name = value; 
158                         }
159                 }
160
161                 public string Path {
162                         get { return (path == null || path == "") ? String.Empty : path; }
163                         set { path = (value == null) ? String.Empty : value; }
164                 }
165
166                 public string Port {
167                         get { return port; }
168                         set { 
169                                 if (value == null || value.Length == 0) {
170                                         port = String.Empty;
171                                         return;
172                                 }
173                                 if (value [0] != '"' || value [value.Length - 1] != '"') {
174                                         throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Port must be enclosed by double quotes.");
175                                 }
176                                 port = value; 
177                                 string [] values = port.Split (portSeparators);
178                                 ports = new int[values.Length];
179                                 for (int i = 0; i < ports.Length; i++) {
180                                         ports [i] = Int32.MinValue;
181                                         if (values [i].Length == 0)
182                                                 continue;
183                                         try {                                           
184                                                 ports [i] = Int32.Parse (values [i]);
185                                         } catch (Exception e) {
186                                                 throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Invalid value: " + values [i], e);
187                                         }
188                                 }
189                                 Version = 1;
190                         }
191                 }
192
193                 internal int [] Ports {
194                         get { return ports; }
195                 }
196
197                 public bool Secure {
198                         get { return secure; }
199                         set { secure = value; }
200                 }
201
202                 public DateTime TimeStamp {
203                         get { return timestamp; }
204                 }
205
206                 public string Value {
207                         get { return val; }
208                         set { 
209                                 if (value == null) {
210                                         val = String.Empty;
211                                         return;
212                                 }
213                                 
214                                 // LAMESPEC: According to .Net specs the Value property should not accept 
215                                 // the semicolon and comma characters, yet it does. For now we'll follow
216                                 // the behaviour of MS.Net instead of the specs.
217                                 /*
218                                 if (value.IndexOfAny(reservedCharsValue) != -1)
219                                         throw new CookieException("Invalid value. Value cannot contain semicolon or comma characters.");
220                                 */
221                                 
222                                 val = value; 
223                         }
224                 }
225                 
226                 public int Version {
227                         get { return version; }
228                         set { 
229                                 if ((value < 0) || (value > 10)) 
230                                         version = 0;
231                                 else 
232                                         version = value; 
233                         }
234                 }
235
236                 public override bool Equals (Object obj) 
237                 {
238                         System.Net.Cookie c = obj as System.Net.Cookie;                 
239                         
240                         return c != null &&
241                                String.Compare (this.name, c.name, true, CultureInfo.InvariantCulture) == 0 &&
242                                String.Compare (this.val, c.val, false, CultureInfo.InvariantCulture) == 0 &&
243                                String.Compare (this.Path, c.Path, false, CultureInfo.InvariantCulture) == 0 &&
244                                String.Compare (this.domain, c.domain, true, CultureInfo.InvariantCulture) == 0 &&
245                                this.version == c.version;
246                 }
247
248                 public override int GetHashCode ()
249                 {
250                         return hash(CaseInsensitiveHashCodeProvider.DefaultInvariant.GetHashCode(name),
251                                     val.GetHashCode (),
252                                     Path.GetHashCode (),
253                                     CaseInsensitiveHashCodeProvider.DefaultInvariant.GetHashCode (domain),
254                                     version);
255                 }
256
257                 private static int hash (int i, int j, int k, int l, int m) 
258                 {
259                         return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25) ^ (m << 20 | m >> 12);
260                 }
261
262                 // returns a string that can be used to send a cookie to an Origin Server
263                 // i.e., only used for clients
264                 // see para 4.2.2 of RFC 2109 and para 3.3.4 of RFC 2965
265                 // see also bug #316017
266                 public override string ToString () 
267                 {
268                         if (name.Length == 0) 
269                                 return String.Empty;
270
271                         StringBuilder result = new StringBuilder (64);
272         
273                         if (version > 0)
274                                 result.Append ("$Version=").Append (version).Append ("; ");             
275                                 
276                         result.Append (name).Append ("=").Append (val);
277                         
278                         if (version == 0)
279                                 return result.ToString ();
280
281                         if (path != null && path.Length != 0)
282                                 result.Append ("; $Path=").Append (path);
283                                 
284                         if (domain != null && domain.Length != 0)
285                                 result.Append ("; $Domain=").Append (domain);                   
286         
287                         if (port != null && port.Length != 0)
288                                 result.Append ("; $Port=").Append (port);       
289                                                 
290                         return result.ToString ();
291                 }
292
293                 internal string ToClientString () 
294                 {
295                         if (name.Length == 0) 
296                                 return String.Empty;
297
298                         StringBuilder result = new StringBuilder (64);
299         
300                         if (version > 0) 
301                                 result.Append ("Version=").Append (version).Append (";");
302                                 
303                         result.Append (name).Append ("=").Append (val);
304
305                         if (path != null && path.Length != 0)
306                                 result.Append (";Path=").Append (QuotedString (path));
307                                 
308                         if (domain != null && domain.Length != 0)
309                                 result.Append (";Domain=").Append (QuotedString (domain));                      
310         
311                         if (port != null && port.Length != 0)
312                                 result.Append (";Port=").Append (port); 
313                                                 
314                         return result.ToString ();
315                 }
316
317                 // See par 3.6 of RFC 2616
318                 string QuotedString (string value)
319                 {
320                         if (version == 0 || IsToken (value))
321                                 return value;
322                         else 
323                                 return "\"" + value.Replace("\"", "\\\"") + "\"";
324                 }                                   
325
326                 bool IsToken (string value) 
327                 {
328                         int len = value.Length;
329                         for (int i = 0; i < len; i++) {
330                                 char c = value [i];
331                                 if (c < 0x20 || c >= 0x7f || tspecials.IndexOf (c) != -1)
332                                         return false;
333                         }
334                         return true;
335                 }           
336         }
337 }
338