2006-01-10 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System / System.Net / HttpWebResponse.cs
1 //
2 // System.Net.HttpWebResponse
3 //
4 // Authors:
5 //      Lawrence Pit (loz@cable.a2000.nl)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (c) 2002 Lawrence Pit
9 // (c) 2003 Ximian, 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.Collections;
35 using System.Globalization;
36 using System.IO;
37 using System.Net.Sockets;
38 using System.Runtime.Serialization;
39 using System.Text;
40
41 namespace System.Net 
42 {
43         [Serializable]
44         public class HttpWebResponse : WebResponse, ISerializable, IDisposable
45         {
46                 Uri uri;
47                 WebHeaderCollection webHeaders;
48                 CookieCollection cookieCollection;
49                 string method;
50                 Version version;
51                 HttpStatusCode statusCode;
52                 string statusDescription;
53                 long contentLength = -1;
54                 string contentType;
55                 CookieContainer cookieContainer;
56
57                 bool disposed = false;
58                 Stream stream;
59                 
60                 // Constructors
61                 
62                 internal HttpWebResponse (Uri uri, string method, WebConnectionData data, CookieContainer container)
63                 {
64                         this.uri = uri;
65                         this.method = method;
66                         webHeaders = data.Headers;
67                         version = data.Version;
68                         statusCode = (HttpStatusCode) data.StatusCode;
69                         statusDescription = data.StatusDescription;
70                         stream = data.stream;
71                         if (container != null) {
72                                 this.cookieContainer = container;
73                                 FillCookies ();
74                         } else if (webHeaders != null) {
75                                 webHeaders.RemoveInternal ("Set-Cookie");
76                                 webHeaders.RemoveInternal ("Set-Cookie2");
77                         }
78                 }
79
80                 protected HttpWebResponse (SerializationInfo serializationInfo, StreamingContext streamingContext)
81                 {
82                         SerializationInfo info = serializationInfo;
83
84                         uri = (Uri) info.GetValue ("uri", typeof (Uri));
85                         contentLength = info.GetInt64 ("contentLength");
86                         contentType = info.GetString ("contentType");
87                         method = info.GetString ("method");
88                         statusDescription = info.GetString ("statusDescription");
89                         cookieCollection = (CookieCollection) info.GetValue ("cookieCollection", typeof (CookieCollection));
90                         version = (Version) info.GetValue ("version", typeof (Version));
91                         statusCode = (HttpStatusCode) info.GetValue ("statusCode", typeof (HttpStatusCode));
92                 }
93                 
94                 // Properties
95                 
96                 public string CharacterSet {
97                         // Content-Type   = "Content-Type" ":" media-type
98                         // media-type     = type "/" subtype *( ";" parameter )
99                         // parameter      = attribute "=" value
100                         // 3.7.1. default is ISO-8859-1
101                         get { 
102                                 CheckDisposed ();
103                                 string contentType = ContentType;
104                                 if (contentType == null)
105                                         return "ISO-8859-1";
106                                 string val = contentType.ToLower ();                                    
107                                 int pos = val.IndexOf ("charset=");
108                                 if (pos == -1)
109                                         return "ISO-8859-1";
110                                 pos += 8;
111                                 int pos2 = val.IndexOf (';', pos);
112                                 return (pos2 == -1)
113                                      ? contentType.Substring (pos) 
114                                      : contentType.Substring (pos, pos2 - pos);
115                         }
116                 }
117                 
118                 public string ContentEncoding {
119                         get { 
120                                 CheckDisposed ();
121                                 return webHeaders ["Content-Encoding"];
122                         }
123                 }
124                 
125                 public override long ContentLength {            
126                         get {
127                                 CheckDisposed ();
128                                 if (contentLength != -1)
129                                         return contentLength;
130
131                                 try {
132                                         contentLength = (long) UInt64.Parse (webHeaders ["Content-Length"]); 
133                                 } catch (Exception) {
134                                         return -1;
135                                 }
136
137                                 return contentLength;
138                         }
139                 }
140                 
141                 public override string ContentType {            
142                         get {
143                                 CheckDisposed ();
144                                 if (contentType == null)
145                                         contentType = webHeaders ["Content-Type"];
146
147                                 return contentType;
148                         }
149                 }
150                 
151                 public CookieCollection Cookies {
152                         get { 
153                                 CheckDisposed ();
154                                 
155                                 if (cookieCollection == null)
156                                         cookieCollection = new CookieCollection ();
157                                 return cookieCollection;
158                         }
159                         set {
160                                 CheckDisposed ();
161                                 cookieCollection = value;
162                         }
163                 }
164                 
165                 public override WebHeaderCollection Headers {           
166                         get { 
167                                 CheckDisposed ();
168                                 return webHeaders; 
169                         }
170                 }
171                 
172                 public DateTime LastModified {
173                         get {
174                                 CheckDisposed ();
175                                 try {
176                                         string dtStr = webHeaders ["Last-Modified"];
177                                         return MonoHttpDate.Parse (dtStr);
178                                 } catch (Exception) {
179                                         return DateTime.Now;    
180                                 }
181                         }
182                 }
183                 
184                 public string Method {
185                         get { 
186                                 CheckDisposed ();
187                                 return method; 
188                         }
189                 }
190                 
191                 public Version ProtocolVersion {
192                         get { 
193                                 CheckDisposed ();
194                                 return version; 
195                         }
196                 }
197                 
198                 public override Uri ResponseUri {               
199                         get { 
200                                 CheckDisposed ();
201                                 return uri; 
202                         }
203                 }               
204                 
205                 public string Server {
206                         get { 
207                                 CheckDisposed ();
208                                 return webHeaders ["Server"]; 
209                         }
210                 }
211                 
212                 public HttpStatusCode StatusCode {
213                         get { 
214                                 CheckDisposed ();
215                                 return statusCode; 
216                         }
217                 }
218                 
219                 public string StatusDescription {
220                         get { 
221                                 CheckDisposed ();
222                                 return statusDescription; 
223                         }
224                 }
225
226                 // Methods
227 #if !NET_2_0
228                 public override int GetHashCode ()
229                 {
230                         CheckDisposed ();
231                         return base.GetHashCode ();
232                 }
233 #endif
234                 
235                 public string GetResponseHeader (string headerName)
236                 {
237                         CheckDisposed ();
238                         string value = webHeaders [headerName];
239                         return (value != null) ? value : "";
240                 }
241
242                 internal void ReadAll ()
243                 {
244                         WebConnectionStream wce = stream as WebConnectionStream;
245                         if (wce == null)
246                                 return;
247                                 
248                         try {
249                                 wce.ReadAll ();
250                         } catch {}
251                 }
252
253                 public override Stream GetResponseStream ()
254                 {
255                         CheckDisposed ();
256                         if (stream == null)
257                                 return Stream.Null;  
258                         if (0 == String.Compare (method, "HEAD", true)) // see par 4.3 & 9.4
259                                 return Stream.Null;  
260
261                         return stream;
262                 }
263                 
264                 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
265                                                   StreamingContext streamingContext)
266                 {
267                         SerializationInfo info = serializationInfo;
268
269                         info.AddValue ("uri", uri);
270                         info.AddValue ("contentLength", contentLength);
271                         info.AddValue ("contentType", contentType);
272                         info.AddValue ("method", method);
273                         info.AddValue ("statusDescription", statusDescription);
274                         info.AddValue ("cookieCollection", cookieCollection);
275                         info.AddValue ("version", version);
276                         info.AddValue ("statusCode", statusCode);
277                 }               
278
279
280                 // Cleaning up stuff
281
282                 public override void Close ()
283                 {
284                         ((IDisposable) this).Dispose ();
285                 }
286                 
287                 void IDisposable.Dispose ()
288                 {
289                         Dispose (true);
290                         GC.SuppressFinalize (this);  
291                 }
292
293 #if !NET_2_0
294                 protected virtual
295 #endif
296                 void Dispose (bool disposing) 
297                 {
298                         if (this.disposed)
299                                 return;
300                         this.disposed = true;
301                         
302                         if (disposing) {
303                                 // release managed resources
304                                 uri = null;
305                                 webHeaders = null;
306                                 cookieCollection = null;
307                                 method = null;
308                                 version = null;
309                                 statusDescription = null;
310                         }
311                         
312                         // release unmanaged resources
313                         Stream st = stream;
314                         stream = null;
315                         if (st != null)
316                                 st.Close ();
317                 }
318                 
319                 private void CheckDisposed () 
320                 {
321                         if (disposed)
322                                 throw new ObjectDisposedException (GetType ().FullName);
323                 }
324
325                 void FillCookies ()
326                 {
327                         if (webHeaders == null)
328                                 return;
329
330                         string [] values = webHeaders.GetValues ("Set-Cookie");
331                         if (values != null) {
332                                 foreach (string va in values)
333                                         SetCookie (va);
334                         }
335
336                         values = webHeaders.GetValues ("Set-Cookie2");
337                         if (values != null) {
338                                 foreach (string va in values)
339                                         SetCookie2 (va);
340                         }
341                 }
342
343                 void SetCookie (string header)
344                 {
345                         string name, val;
346                         Cookie cookie = null;
347                         CookieParser parser = new CookieParser (header);
348
349                         while (parser.GetNextNameValue (out name, out val)) {
350                                 if ((name == null || name == "") && cookie == null)
351                                         continue;
352
353                                 if (cookie == null) {
354                                         cookie = new Cookie (name, val);
355                                         continue;
356                                 }
357
358                                 name = name.ToUpper ();
359                                 switch (name) {
360                                 case "COMMENT":
361                                         if (cookie.Comment == null)
362                                                 cookie.Comment = val;
363                                         break;
364                                 case "COMMENTURL":
365                                         if (cookie.CommentUri == null)
366                                                 cookie.CommentUri = new Uri (val);
367                                         break;
368                                 case "DISCARD":
369                                         cookie.Discard = true;
370                                         break;
371                                 case "DOMAIN":
372                                         if (cookie.Domain == "")
373                                                 cookie.Domain = val;
374                                         break;
375                                 case "MAX-AGE": // RFC Style Set-Cookie2
376                                         if (cookie.Expires == DateTime.MinValue) {
377                                                 try {
378                                                 cookie.Expires = cookie.TimeStamp.AddSeconds (UInt32.Parse (val));
379                                                 } catch {}
380                                         }
381                                         break;
382                                 case "EXPIRES": // Netscape Style Set-Cookie
383                                         if (cookie.Expires != DateTime.MinValue)
384                                                 break;
385                                         try {
386                                                 cookie.Expires = DateTime.ParseExact (val, "r", CultureInfo.InvariantCulture);
387                                         } catch {
388                                                 try { 
389                                                 cookie.Expires = DateTime.ParseExact (val,
390                                                                 "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'",
391                                                                 CultureInfo.InvariantCulture);
392                                                 } catch {
393                                                         cookie.Expires = DateTime.Now.AddDays (1);
394                                                 }
395                                         }
396                                         break;
397                                 case "PATH":
398                                         cookie.Path = val;
399                                         break;
400                                 case "PORT":
401                                         if (cookie.Port == null)
402                                                 cookie.Port = val;
403                                         break;
404                                 case "SECURE":
405                                         cookie.Secure = true;
406                                         break;
407                                 case "VERSION":
408                                         try {
409                                                 cookie.Version = (int) UInt32.Parse (val);
410                                         } catch {}
411                                         break;
412                                 }
413                         }
414
415                         if (cookieCollection == null)
416                                 cookieCollection = new CookieCollection ();
417
418                         if (cookie.Domain == "")
419                                 cookie.Domain = uri.Host;
420
421                         cookieCollection.Add (cookie);
422                         if (cookieContainer != null)
423                                 cookieContainer.Add (uri, cookie);
424                 }
425
426                 void SetCookie2 (string cookies_str)
427                 {
428                         string [] cookies = cookies_str.Split (',');
429         
430                         foreach (string cookie_str in cookies)
431                                 SetCookie (cookie_str);
432                 }
433         }       
434
435         class CookieParser {
436                 string header;
437                 int pos;
438                 int length;
439
440                 public CookieParser (string header) : this (header, 0)
441                 {
442                 }
443
444                 public CookieParser (string header, int position)
445                 {
446                         this.header = header;
447                         this.pos = position;
448                         this.length = header.Length;
449                 }
450
451                 public bool GetNextNameValue (out string name, out string val)
452                 {
453                         name = null;
454                         val = null;
455
456                         if (pos >= length)
457                                 return false;
458
459                         name = GetCookieName ();
460                         if (pos < header.Length && header [pos] == '=') {
461                                 pos++;
462                                 val = GetCookieValue ();
463                         }
464
465                         if (pos < length && header [pos] == ';')
466                                 pos++;
467
468                         return true;
469                 }
470
471                 string GetCookieName ()
472                 {
473                         int k = pos;
474                         while (k < length && Char.IsWhiteSpace (header [k]))
475                                 k++;
476
477                         int begin = k;
478                         while (k < length && header [k] != ';' &&  header [k] != '=')
479                                 k++;
480
481                         pos = k;
482                         return header.Substring (begin, k - begin).Trim ();
483                 }
484
485                 string GetCookieValue ()
486                 {
487                         if (pos >= length)
488                                 return null;
489
490                         int k = pos;
491                         while (k < length && Char.IsWhiteSpace (header [k]))
492                                 k++;
493
494                         int begin;
495                         if (header [k] == '"'){
496                                 int j;
497                                 begin = ++k;
498
499                                 while (k < length && header [k] != '"')
500                                         k++;
501
502                                 for (j = k; j < length && header [j] != ';'; j++)
503                                         ;
504                                 pos = j;
505                         } else {
506                                 begin = k;
507                                 while (k < length && header [k] != ';')
508                                         k++;
509                                 pos = k;
510                         }
511                                 
512                         return header.Substring (begin, k - begin).Trim ();
513                 }
514         }
515 }
516