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