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