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