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