2005-03-30 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                 
228                 public override int GetHashCode ()
229                 {
230                         CheckDisposed ();
231                         return base.GetHashCode ();
232                 }
233                 
234                 public string GetResponseHeader (string headerName)
235                 {
236                         CheckDisposed ();
237                         string value = webHeaders [headerName];
238                         return (value != null) ? value : "";
239                 }
240
241                 internal void ReadAll ()
242                 {
243                         WebConnectionStream wce = stream as WebConnectionStream;
244                         if (wce == null)
245                                 return;
246                                 
247                         try {
248                                 wce.ReadAll ();
249                         } catch {}
250                 }
251
252                 public override Stream GetResponseStream ()
253                 {
254                         CheckDisposed ();
255                         if (stream == null)
256                                 return Stream.Null;  
257                         if (0 == String.Compare (method, "HEAD", true)) // see par 4.3 & 9.4
258                                 return Stream.Null;  
259
260                         return stream;
261                 }
262                 
263                 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
264                                                   StreamingContext streamingContext)
265                 {
266                         SerializationInfo info = serializationInfo;
267
268                         info.AddValue ("uri", uri);
269                         info.AddValue ("contentLength", contentLength);
270                         info.AddValue ("contentType", contentType);
271                         info.AddValue ("method", method);
272                         info.AddValue ("statusDescription", statusDescription);
273                         info.AddValue ("cookieCollection", cookieCollection);
274                         info.AddValue ("version", version);
275                         info.AddValue ("statusCode", statusCode);
276                 }               
277
278
279                 // Cleaning up stuff
280
281                 public override void Close ()
282                 {
283                         ((IDisposable) this).Dispose ();
284                 }
285                 
286                 void IDisposable.Dispose ()
287                 {
288                         Dispose (true);
289                         GC.SuppressFinalize (this);  
290                 }
291                 
292                 protected virtual void Dispose (bool disposing) 
293                 {
294                         if (this.disposed)
295                                 return;
296                         this.disposed = true;
297                         
298                         if (disposing) {
299                                 // release managed resources
300                                 uri = null;
301                                 webHeaders = null;
302                                 cookieCollection = null;
303                                 method = null;
304                                 version = null;
305                                 statusDescription = null;
306                         }
307                         
308                         // release unmanaged resources
309                         Stream st = stream;
310                         stream = null;
311                         if (st != null)
312                                 st.Close ();
313                 }
314                 
315                 private void CheckDisposed () 
316                 {
317                         if (disposed)
318                                 throw new ObjectDisposedException (GetType ().FullName);
319                 }
320
321                 void FillCookies ()
322                 {
323                         if (webHeaders == null)
324                                 return;
325
326                         string [] values = webHeaders.GetValues ("Set-Cookie");
327                         if (values != null) {
328                                 foreach (string va in values)
329                                         SetCookie (va);
330                         }
331
332                         values = webHeaders.GetValues ("Set-Cookie2");
333                         if (values != null) {
334                                 foreach (string va in values)
335                                         SetCookie2 (va);
336                         }
337                 }
338
339                 void SetCookie (string header)
340                 {
341                         string name, val;
342                         Cookie cookie = null;
343                         CookieParser parser = new CookieParser (header);
344
345                         while (parser.GetNextNameValue (out name, out val)) {
346                                 if (name == null || name == "")
347                                         continue;
348
349                                 if (cookie == null) {
350                                         cookie = new Cookie (name, val);
351                                         continue;
352                                 }
353
354                                 name = name.ToUpper ();
355                                 switch (name) {
356                                 case "COMMENT":
357                                         if (cookie.Comment == null)
358                                                 cookie.Comment = val;
359                                         break;
360                                 case "COMMENTURL":
361                                         if (cookie.CommentUri == null)
362                                                 cookie.CommentUri = new Uri (val);
363                                         break;
364                                 case "DISCARD":
365                                         cookie.Discard = true;
366                                         break;
367                                 case "DOMAIN":
368                                         if (cookie.Domain == "")
369                                                 cookie.Domain = val;
370                                         break;
371                                 case "MAX-AGE": // RFC Style Set-Cookie2
372                                         if (cookie.Expires == DateTime.MinValue)
373                                                 cookie.Expires = cookie.TimeStamp.AddSeconds (Int32.Parse (val));
374                                         break;
375                                 case "EXPIRES": // Netscape Style Set-Cookie
376                                         if (cookie.Expires != DateTime.MinValue)
377                                                 break;
378                                         try {
379                                                 cookie.Expires = DateTime.ParseExact (val, "r", CultureInfo.InvariantCulture);
380                                         } catch {
381                                                 try { 
382                                                 cookie.Expires = DateTime.ParseExact (val,
383                                                                 "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'",
384                                                                 CultureInfo.InvariantCulture);
385                                                 } catch {
386                                                         cookie.Expires = DateTime.Now.AddDays (1);
387                                                 }
388                                         }
389                                         break;
390                                 case "PATH":
391                                         cookie.Path = val;
392                                         break;
393                                 case "PORT":
394                                         if (cookie.Port == null)
395                                                 cookie.Port = val;
396                                         break;
397                                 case "SECURE":
398                                         cookie.Secure = true;
399                                         break;
400                                 case "VERSION":
401                                         cookie.Version = Int32.Parse (val);
402                                         break;
403                                 }
404                         }
405
406                         if (cookieCollection == null)
407                                 cookieCollection = new CookieCollection ();
408
409                         if (cookie.Domain == "")
410                                 cookie.Domain = uri.Host;
411
412                         cookieCollection.Add (cookie);
413                         if (cookieContainer != null)
414                                 cookieContainer.Add (uri, cookie);
415                 }
416
417                 void SetCookie2 (string cookies_str)
418                 {
419                         string [] cookies = cookies_str.Split (',');
420         
421                         foreach (string cookie_str in cookies)
422                                 SetCookie (cookie_str);
423                 }
424         }       
425
426         class CookieParser {
427                 string header;
428                 int pos;
429                 int length;
430
431                 public CookieParser (string header) : this (header, 0)
432                 {
433                 }
434
435                 public CookieParser (string header, int position)
436                 {
437                         this.header = header;
438                         this.pos = position;
439                         this.length = header.Length;
440                 }
441
442                 public bool GetNextNameValue (out string name, out string val)
443                 {
444                         name = null;
445                         val = null;
446
447                         if (pos >= length)
448                                 return false;
449
450                         name = GetCookieName ();
451                         if (header [pos] == '=') {
452                                 pos++;
453                                 val = GetCookieValue ();
454                                 if (pos < length && header [pos] == ';')
455                                         pos++;
456                         }
457
458                         return true;
459                 }
460
461                 string GetCookieName ()
462                 {
463                         int k = pos;
464                         while (k < length && Char.IsWhiteSpace (header [k]))
465                                 k++;
466
467                         int begin = k;
468                         while (k < length && header [k] != ';' &&  header [k] != '=')
469                                 k++;
470
471                         pos = k;
472                         return header.Substring (begin, k - begin).Trim ();
473                 }
474
475                 string GetCookieValue ()
476                 {
477                         if (pos >= length)
478                                 return null;
479
480                         int k = pos;
481                         while (k < length && Char.IsWhiteSpace (header [k]))
482                                 k++;
483
484                         int begin;
485                         if (header [k] == '"'){
486                                 int j;
487                                 begin = ++k;
488
489                                 while (k < length && header [k] != '"')
490                                         k++;
491
492                                 for (j = k; j < length && header [j] != ';'; j++)
493                                         ;
494                                 pos = j;
495                         } else {
496                                 begin = k;
497                                 while (k < length && header [k] != ';')
498                                         k++;
499                                 pos = k;
500                         }
501                                 
502                         return header.Substring (begin, k - begin).Trim ();
503                 }
504         }
505 }
506