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