copying the latest Sys.Web.Services from trunk.
[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                         domain = "";
73                         port = "";
74                 }
75
76                 public Cookie (string name, string value)
77                         : this ()
78                 {
79                         Name = name;
80                         Value = value;
81                 }
82
83                 public Cookie (string name, string value, string path) 
84                         : this (name, value) 
85                 {
86                         Path = path;
87                 }
88
89                 public Cookie (string name, string value, string path, string domain)
90                         : this (name, value, path)
91                 {
92                         Domain = domain;
93                 }
94
95                 public string Comment {
96                         get { return comment; }
97                         set { comment = value == null ? String.Empty : value; }
98                 }
99
100                 public Uri CommentUri {
101                         get { return commentUri; }
102                         set { commentUri = value; }
103                 }
104
105                 public bool Discard {
106                         get { return discard; }
107                         set { discard = value; }
108                 }
109
110                 public string Domain {
111                         get { return domain; }
112                         set { domain = value == null ? String.Empty : value; }
113                 }
114
115                 public bool Expired {
116                         get { 
117                                 return expires <= DateTime.Now && 
118                                        expires != DateTime.MinValue;
119                         }
120                         set { 
121                                 expired = value; 
122                                 if (expired) {
123                                         expires = DateTime.Now;
124                                 }
125                         }
126                 }
127
128                 public DateTime Expires {
129                         get { return expires; }
130                         set { expires = value; }
131                 }
132
133                 public string Name {
134                         get { return name; }
135                         set { 
136                                 if (value == null || value.Length == 0) {
137                                         throw new CookieException ("Name cannot be empty");
138                                 }                       
139                                 
140                                 if (value [0] == '$' || value.IndexOfAny (reservedCharsName) != -1) {
141                                         // see CookieTest, according to MS implementation
142                                         // the name value changes even though it's incorrect
143                                         name = String.Empty;
144                                         throw new CookieException ("Name contains invalid characters");
145                                 }
146                                         
147                                 name = value; 
148                         }
149                 }
150
151                 public string Path {
152                         get { return (path == null || path == "") ? "/" : path; }
153                         set { path = (value == null) ? String.Empty : value; }
154                 }
155
156                 public string Port {
157                         get { return port; }
158                         set { 
159                                 if (value == null || value.Length == 0) {
160                                         port = String.Empty;
161                                         return;
162                                 }
163                                 if (value [0] != '"' || value [value.Length - 1] != '"') {
164                                         throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Port must be enclosed by double quotes.");
165                                 }
166                                 port = value; 
167                                 string [] values = port.Split (portSeparators);
168                                 ports = new int[values.Length];
169                                 for (int i = 0; i < ports.Length; i++) {
170                                         ports [i] = Int32.MinValue;
171                                         if (values [i].Length == 0)
172                                                 continue;
173                                         try {                                           
174                                                 ports [i] = Int32.Parse (values [i]);
175                                         } catch (Exception e) {
176                                                 throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Invalid value: " + values [i], e);
177                                         }
178                                 }
179                         }
180                 }
181
182                 internal int [] Ports {
183                         get { return ports; }
184                 }
185
186                 public bool Secure {
187                         get { return secure; }
188                         set { secure = value; }
189                 }
190
191                 public DateTime TimeStamp {
192                         get { return timestamp; }
193                 }
194
195                 public string Value {
196                         get { return val; }
197                         set { 
198                                 if (value == null) {
199                                         val = String.Empty;
200                                         return;
201                                 }
202                                 
203                                 // LAMESPEC: According to .Net specs the Value property should not accept 
204                                 // the semicolon and comma characters, yet it does. For now we'll follow
205                                 // the behaviour of MS.Net instead of the specs.
206                                 /*
207                                 if (value.IndexOfAny(reservedCharsValue) != -1)
208                                         throw new CookieException("Invalid value. Value cannot contain semicolon or comma characters.");
209                                 */
210                                 
211                                 val = value; 
212                         }
213                 }
214                 
215                 public int Version {
216                         get { return version; }
217                         set { 
218                                 if ((value < 0) || (value > 10)) 
219                                         version = 0;
220                                 else 
221                                         version = value; 
222                         }
223                 }
224
225                 public override bool Equals (Object obj) 
226                 {
227                         System.Net.Cookie c = obj as System.Net.Cookie;                 
228                         
229                         return c != null &&
230                                String.Compare (this.name, c.name, true, CultureInfo.InvariantCulture) == 0 &&
231                                String.Compare (this.val, c.val, false, CultureInfo.InvariantCulture) == 0 &&
232                                String.Compare (this.path, c.path, false, CultureInfo.InvariantCulture) == 0 &&
233                                String.Compare (this.domain, c.domain, true, CultureInfo.InvariantCulture) == 0 &&
234                                this.version == c.version;
235                 }
236
237                 public override int GetHashCode ()
238                 {
239                         return hash(name.ToLower ().GetHashCode (),
240                                     val.GetHashCode (),
241                                     path.GetHashCode (),
242                                     domain.ToLower ().GetHashCode (),
243                                     version);
244                 }
245
246                 private static int hash (int i, int j, int k, int l, int m) 
247                 {
248                         return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25) ^ (m << 20 | m >> 12);
249                 }
250
251                 // returns a string that can be used to send a cookie to an Origin Server
252                 // i.e., only used for clients
253                 // see also para 3.3.4 of RFC 1965
254                 public override string ToString () 
255                 {
256                         if (name.Length == 0) 
257                                 return String.Empty;
258
259                         StringBuilder result = new StringBuilder (64);
260         
261                         if (version > 0) {
262                                 result.Append ("$Version=").Append (version).Append (";");
263                         }                               
264                                 
265                         result.Append (name).Append ("=").Append (val);
266
267                         // in the MS.Net implementation path and domain don't show up in
268                         // the result, I guess that's a bug in their implementation...
269                         if (path != null && path.Length != 0)
270                                 result.Append (";$Path=").Append (QuotedString (path));
271                                 
272                         if (domain != null && domain.Length != 0)
273                                 result.Append (";$Domain=").Append (QuotedString (domain));                     
274         
275                         if (port != null && port.Length != 0)
276                                 result.Append (";$Port=").Append (port);        
277                                                 
278                         return result.ToString ();
279                 }
280
281                 // See par 3.6 of RFC 2616
282                 string QuotedString (string value)
283                 {
284                         if (version == 0 || IsToken (value))
285                                 return value;
286                         else 
287                                 return "\"" + value.Replace("\"", "\\\"") + "\"";
288                 }                                   
289
290                 bool IsToken (string value) 
291                 {
292                         int len = value.Length;
293                         for (int i = 0; i < len; i++) {
294                                 char c = value [i];
295                                 if (c < 0x20 || c >= 0x7f || tspecials.IndexOf (c) != -1)
296                                         return false;
297                         }
298                         return true;
299                 }           
300         }
301 }
302