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