Merge pull request #1203 from esdrubal/protect
[mono.git] / mcs / class / System / System.Net / CookieParser.cs
1 //
2 // System.Net.CookieParser
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) 2002 Lawrence Pit
10 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
11 // (c) 2008 Daniel Nauck
12 //
13
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System;
36 using System.Collections;
37 using System.Collections.Generic;
38 using System.Globalization;
39
40 namespace System.Net {
41
42         struct CookieParser
43         {
44                 static readonly string[] cookieExpiresFormats =
45                         new string[] { "r",
46                                         "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'",
47                                         "ddd, dd'-'MMM'-'yy HH':'mm':'ss 'GMT'" };
48
49                 readonly string header;
50                 readonly int length;
51                 int pos;
52
53                 public CookieParser (string header)
54                 {
55                         this.header = header;
56                         this.length = header.Length;
57                         pos = 0;
58                 }
59
60                 public IEnumerable<Cookie> Parse ()
61                 {
62                         while (pos < length) {
63                                 Cookie cookie;
64                                 try {
65                                         cookie = DoParse ();
66                                 } catch {
67                                         while ((pos < length) && (header [pos] != ','))
68                                                 pos++;
69                                         pos++;
70                                         continue;
71                                 }
72                                 yield return cookie;
73                         }
74                 }
75
76                 Cookie DoParse ()
77                 {
78                         var name = GetCookieName ();
79                         if (pos >= length)
80                                 return new Cookie (name, string.Empty);
81
82                         var value = string.Empty;
83                         if (header [pos] == '=') {
84                                 pos++;
85                                 value = GetCookieValue ();
86                         }
87
88                         var cookie = new Cookie (name, value);
89
90                         if (pos >= length) {
91                                 return cookie;
92                         } else if (header [pos] == ',') {
93                                 pos++;
94                                 return cookie;
95                         } else if ((header [pos++] != ';') || (pos >= length)) {
96                                 return cookie;
97                         }
98
99                         while (pos < length) {
100                                 var argName = GetCookieName ();
101                                 string argVal = string.Empty;
102                                 if ((pos < length) && (header [pos] == '=')) {
103                                         pos++;
104                                         argVal = GetCookieValue ();
105                                 }
106                                 ProcessArg (cookie, argName, argVal);
107
108                                 if (pos >= length)
109                                         break;
110                                 if (header [pos] == ',') {
111                                         pos++;
112                                         break;
113                                 } else if (header [pos] != ';') {
114                                         break;
115                                 }
116
117                                 pos++;
118                         }
119
120                         return cookie;
121                 }
122
123                 void ProcessArg (Cookie cookie, string name, string val)
124                 {
125                         if ((name == null) || (name == string.Empty))
126                                 throw new InvalidOperationException ();
127
128                         switch (name.ToUpperInvariant ()) {
129                         case "COMMENT":
130                                 if (cookie.Comment == null)
131                                         cookie.Comment = val;
132                                 break;
133                         case "COMMENTURL":
134                                 if (cookie.CommentUri == null)
135                                         cookie.CommentUri = new Uri (val);
136                                 break;
137                         case "DISCARD":
138                                 cookie.Discard = true;
139                                 break;
140                         case "DOMAIN":
141                                 if (cookie.Domain == "")
142                                         cookie.Domain = val;
143                                 break;
144                         case "HTTPONLY":
145                                 cookie.HttpOnly = true;
146                                 break;
147                         case "MAX-AGE": // RFC Style Set-Cookie2
148                                 if (cookie.Expires == DateTime.MinValue) {
149                                         try {
150                                         cookie.Expires = cookie.TimeStamp.AddSeconds (UInt32.Parse (val));
151                                         } catch {}
152                                 }
153                                 break;
154                         case "EXPIRES": // Netscape Style Set-Cookie
155                                 if (cookie.Expires != DateTime.MinValue)
156                                         break;
157
158                                 if ((pos < length) && (header [pos] == ',') && IsWeekDay (val)) {
159                                         pos++;
160                                         val = val + ", " + GetCookieValue ();
161                                 }
162
163                                 cookie.Expires = TryParseCookieExpires (val);
164                                 break;
165                         case "PATH":
166                                 cookie.Path = val;
167                                 break;
168                         case "PORT":
169                                 if (cookie.Port == null)
170                                         cookie.Port = val;
171                                 break;
172                         case "SECURE":
173                                 cookie.Secure = true;
174                                 break;
175                         case "VERSION":
176                                 try {
177                                         cookie.Version = (int) UInt32.Parse (val);
178                                 } catch {}
179                                 break;
180                         }
181                 }
182
183                 string GetCookieName ()
184                 {
185                         int k = pos;
186                         while (k < length && Char.IsWhiteSpace (header [k]))
187                                 k++;
188
189                         int begin = k;
190                         while (k < length && header [k] != ';' && header [k] != ',' && header [k] != '=')
191                                 k++;
192
193                         pos = k;
194                         return header.Substring (begin, k - begin).Trim ();
195                 }
196
197                 string GetCookieValue ()
198                 {
199                         if (pos >= length)
200                                 return null;
201
202                         int k = pos;
203                         while (k < length && Char.IsWhiteSpace (header [k]))
204                                 k++;
205
206                         int begin;
207                         if (header [k] == '"'){
208                                 int j;
209                                 begin = k++;
210
211                                 while (k < length && header [k] != '"')
212                                         k++;
213
214                                 for (j = ++k; j < length && header [j] != ';' && header [j] != ','; j++)
215                                         ;
216                                 pos = j;
217                         } else {
218                                 begin = k;
219                                 while (k < length && header [k] != ';' && header [k] != ',')
220                                         k++;
221                                 pos = k;
222                         }
223
224                         return header.Substring (begin, k - begin).Trim ();
225                 }
226
227                 static bool IsWeekDay (string value)
228                 {
229                         switch (value.ToLowerInvariant ()) {
230                         case "mon":
231                         case "tue":
232                         case "wed":
233                         case "thu":
234                         case "fri":
235                         case "sat":
236                         case "sun":
237                         case "monday":
238                         case "tuesday":
239                         case "wednesday":
240                         case "thursday":
241                         case "friday":
242                         case "saturday":
243                         case "sunday":
244                                 return true;
245                         default:
246                                 return false;
247                         }
248                 }
249
250                 static DateTime TryParseCookieExpires (string value)
251                 {
252                         if (String.IsNullOrEmpty (value))
253                                 return DateTime.MinValue;
254
255                         for (int i = 0; i < cookieExpiresFormats.Length; i++) {
256                                 try {
257                                         DateTime cookieExpiresUtc = DateTime.ParseExact (value, cookieExpiresFormats [i], CultureInfo.InvariantCulture);
258
259                                         //convert UTC/GMT time to local time
260                                         cookieExpiresUtc = DateTime.SpecifyKind (cookieExpiresUtc, DateTimeKind.Utc);
261                                         return TimeZone.CurrentTimeZone.ToLocalTime (cookieExpiresUtc);
262                                 } catch {}
263                         }
264
265                         //If we can't parse Expires, use cookie as session cookie (expires is DateTime.MinValue)
266                         return DateTime.MinValue;
267                 }
268         }
269 }
270