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