2 // System.Net.HttpWebResponse
5 // Lawrence Pit (loz@cable.a2000.nl)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 // (c) 2002 Lawrence Pit
9 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
14 using System.Net.Sockets;
15 using System.Runtime.Serialization;
21 public class HttpWebResponse : WebResponse, ISerializable, IDisposable
24 private WebHeaderCollection webHeaders;
25 private CookieCollection cookieCollection = null;
26 private string method = null;
27 private Version version = null;
28 private HttpStatusCode statusCode;
29 private string statusDescription = null;
32 private HttpWebResponseStream responseStream;
33 private bool disposed = false;
37 internal HttpWebResponse (Uri uri, string method, Socket socket, int timeout, EventHandler onClose)
39 Text.StringBuilder value = null;
42 string[] protocol, header;
46 this.webHeaders = new WebHeaderCollection();
48 responseStream = new HttpWebResponseStream (socket, onClose);
49 if (!socket.Poll (timeout, SelectMode.SelectRead))
50 throw new WebException("The request timed out", WebExceptionStatus.Timeout);
54 if (statusCode == HttpStatusCode.Continue)
55 while ((line = ReadHttpLine (responseStream)) != "")
56 Console.WriteLine ("Ignoro: " + line);
58 line = ReadHttpLine (responseStream);
60 protocol = line.Split (' ');
62 switch (protocol[0]) {
64 this.version = HttpVersion.Version10;
67 this.version = HttpVersion.Version11;
70 throw new WebException ("Unrecognized HTTP Version: " + line);
73 this.statusCode = (HttpStatusCode) Int32.Parse (protocol[1]);
74 } while (this.statusCode == HttpStatusCode.Continue);
76 while ((line = ReadHttpLine (responseStream)).Length != 0) {
77 if (!Char.IsWhiteSpace (line[0])) { // new header
78 header = line.Split (new char[] {':'}, 2);
79 if (header.Length != 2)
80 throw new WebException ("Bad HTTP Header");
81 if (last != null) { // not the first header
82 if (last.Equals ("Set-Cookie"))
83 SetCookie (value.ToString());
84 else if (last.Equals ("Set-Cookie2"))
85 SetCookie2 (value.ToString());
86 else //don't save Set-Cookie headers
87 this.webHeaders[last] = value.ToString();
90 value = new Text.StringBuilder (header[1].Trim());
91 if (last == "Transfer-Encoding" && header [1].IndexOf ("chunked") != -1)
95 value.Append (line.Trim());
98 responseStream.Chunked = chunked;
99 this.webHeaders[last] = value.ToString(); // otherwise we miss the last header
102 protected HttpWebResponse (SerializationInfo serializationInfo, StreamingContext streamingContext)
104 uri = (Uri) serializationInfo.GetValue ("uri", typeof (Uri));
105 webHeaders = (WebHeaderCollection) serializationInfo.GetValue ("webHeaders",
106 typeof (WebHeaderCollection));
107 cookieCollection = (CookieCollection) serializationInfo.GetValue ("cookieCollection",
108 typeof (CookieCollection));
109 method = serializationInfo.GetString ("method");
110 version = (Version) serializationInfo.GetValue ("version", typeof (Version));
111 statusCode = (HttpStatusCode) serializationInfo.GetValue ("statusCode", typeof (HttpStatusCode));
112 statusDescription = serializationInfo.GetString ("statusDescription");
113 chunked = serializationInfo.GetBoolean ("chunked");
118 public string CharacterSet {
119 // Content-Type = "Content-Type" ":" media-type
120 // media-type = type "/" subtype *( ";" parameter )
121 // parameter = attribute "=" value
122 // 3.7.1. default is ISO-8859-1
125 string contentType = ContentType;
126 if (contentType == null)
128 string val = contentType.ToLower ();
129 int pos = val.IndexOf ("charset=");
133 int pos2 = val.IndexOf (';', pos);
135 ? contentType.Substring (pos)
136 : contentType.Substring (pos, pos2 - pos);
140 public string ContentEncoding {
143 return webHeaders ["Content-Encoding"];
147 public override long ContentLength {
151 return Int64.Parse (webHeaders ["Content-Length"]);
152 } catch (Exception) {
158 public override string ContentType {
161 return webHeaders ["Content-Type"];
165 public CookieCollection Cookies {
169 if (cookieCollection == null)
170 cookieCollection = new CookieCollection ();
171 return cookieCollection;
175 // ?? don't understand how you can set cookies on a response.
176 throw new NotSupportedException ();
180 public override WebHeaderCollection Headers {
187 public DateTime LastModified {
191 string dtStr = webHeaders ["Last-Modified"];
192 return MonoHttpDate.Parse (dtStr);
193 } catch (Exception) {
199 public string Method {
206 public Version ProtocolVersion {
213 public override Uri ResponseUri {
220 public string Server {
223 return webHeaders ["Server"];
227 public HttpStatusCode StatusCode {
234 public string StatusDescription {
237 return statusDescription;
243 public override int GetHashCode ()
246 return base.GetHashCode ();
249 public string GetResponseHeader (string headerName)
252 return webHeaders [headerName];
255 public override Stream GetResponseStream ()
258 if (method.Equals ("HEAD")) // see par 4.3 & 9.4
260 return responseStream;
263 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
264 StreamingContext streamingContext)
267 serializationInfo.AddValue ("uri", uri);
268 serializationInfo.AddValue ("webHeaders", webHeaders);
269 serializationInfo.AddValue ("cookieCollection", cookieCollection);
270 serializationInfo.AddValue ("method", method);
271 serializationInfo.AddValue ("version", version);
272 serializationInfo.AddValue ("statusCode", statusCode);
273 serializationInfo.AddValue ("statusDescription", statusDescription);
274 serializationInfo.AddValue ("chunked", chunked);
285 public override void Close ()
287 ((IDisposable) this).Dispose ();
290 void IDisposable.Dispose ()
293 GC.SuppressFinalize (this);
296 protected virtual void Dispose (bool disposing)
300 this.disposed = true;
303 // release managed resources
306 cookieCollection = null;
309 statusDescription = null;
312 // release unmanaged resources
313 Stream stream = responseStream;
314 responseStream = null;
319 private void CheckDisposed ()
322 throw new ObjectDisposedException (GetType ().FullName);
325 private static string ReadHttpLine (Stream stream)
327 StringBuilder line = new StringBuilder();
328 byte last = (byte)'\n';
329 bool read_last = false;
332 while ((c = stream.ReadByte ()) != -1) {
334 if ((last = (byte) stream.ReadByte ()) == '\n') // headers; not at EOS
339 line.Append ((char) c);
341 line.Append (Convert.ToChar (last));
346 return line.ToString();
349 private void SetCookie (string cookie_str)
351 string[] parts = null;
352 Collections.Queue options = null;
353 Cookie cookie = null;
355 options = new Collections.Queue (cookie_str.Split (';'));
356 parts = ((string)options.Dequeue()).Split ('='); // NAME=VALUE must be first
358 cookie = new Cookie (parts[0], parts[1]);
360 while (options.Count > 0) {
361 parts = ((string)options.Dequeue()).Split ('=');
362 switch (parts[0].ToUpper()) { // cookie options are case-insensitive
364 if (cookie.Comment == null)
365 cookie.Comment = parts[1];
368 if (cookie.CommentUri == null)
369 cookie.CommentUri = new Uri(parts[1]);
372 cookie.Discard = true;
375 if (cookie.Domain == null)
376 cookie.Domain = parts[1];
378 case "MAX-AGE": // RFC Style Set-Cookie2
379 if (cookie.Expires == DateTime.MinValue)
380 cookie.Expires = cookie.TimeStamp.AddSeconds (Int32.Parse (parts[1]));
382 case "EXPIRES": // Netscape Style Set-Cookie
383 if (cookie.Expires == DateTime.MinValue)
384 cookie.Expires = DateTime.Parse (parts[1]);
387 if (cookie.Path == null)
388 cookie.Path = parts[1];
391 if (cookie.Port == null)
392 cookie.Port = parts[1];
395 cookie.Secure = true;
398 cookie.Version = Int32.Parse (parts[1]);
403 if (cookieCollection == null)
404 cookieCollection = new CookieCollection();
406 if (cookie.Domain == null)
407 cookie.Domain = uri.Host;
409 cookieCollection.Add (cookie);
412 private void SetCookie2 (string cookies_str)
414 string[] cookies = cookies_str.Split (',');
416 foreach (string cookie_str in cookies)
417 SetCookie (cookie_str);
421 class HttpWebResponseStream : NetworkStream
427 bool readingChunkSize;
428 EventHandler onClose;
430 public HttpWebResponseStream (Socket socket, EventHandler onClose)
431 : base (socket, FileAccess.Read, false)
433 this.onClose = onClose;
438 public bool Chunked {
439 get { return chunked; }
440 set { chunked = value; }
443 protected override void Dispose (bool disposing)
450 /* This does not work !??
451 if (Socket.Connected)
452 Socket.Shutdown (SocketShutdown.Receive);
456 onClose (this, EventArgs.Empty);
460 base.Dispose (disposing);
463 void ReadChunkSize ()
468 // 8 hex digits should be enough
469 for (int i = 0; i < 10; i++) {
470 char c = Char.ToUpper ((char) ReadByte ());
476 throw new IOException ("Bad stream: 2 CR");
479 if (c == '\n' && cr == true) {
485 throw new IOException ("Bad stream: got LF but no CR");
488 if (i < 8 && ((c >= '0' && c <= '9') || c >= 'A' && c <= 'F')) {
490 if (c >= 'A' && c <= 'F')
491 size += c - 'A' + 10;
497 throw new IOException ("Bad stream: got " + c);
501 throw new IOException ("Bad stream: no CR or LF after chunk size");
507 int GetMaxSizeFromChunkLeft (int requestedSize)
510 return requestedSize;
512 if (chunkSize == -1 || chunkLeft == 0) {
514 if (chunkSize == -1 || chunkLeft == 0) {
515 readingChunkSize = true;
519 readingChunkSize = false;
525 return (chunkLeft < requestedSize) ? chunkLeft : requestedSize;
528 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
529 AsyncCallback callback, object state)
535 throw new ArgumentNullException ("buffer is null");
537 int len = buffer.Length;
538 if (offset < 0 || offset >= len)
539 throw new ArgumentOutOfRangeException ("offset exceeds the size of buffer");
541 if (offset + size < 0 || offset+size > len)
542 throw new ArgumentOutOfRangeException ("offset+size exceeds the size of buffer");
544 if (!readingChunkSize)
545 size = GetMaxSizeFromChunkLeft (size);
548 retval = base.BeginRead (buffer, offset, size, callback, state);
550 throw new IOException ("BeginReceive failure");
556 public override int EndRead (IAsyncResult ar)
562 throw new ArgumentNullException ("async result is null");
565 res = base.EndRead (ar);
566 } catch (Exception e) {
567 throw new IOException ("EndRead failure", e);
570 AdjustChunkLeft (res);
574 public override int Read (byte [] buffer, int offset, int size)
580 throw new ArgumentNullException ("buffer is null");
582 if (offset < 0 || offset >= buffer.Length)
583 throw new ArgumentOutOfRangeException ("offset exceeds the size of buffer");
585 if (offset + size < 0 || offset + size > buffer.Length)
586 throw new ArgumentOutOfRangeException ("offset+size exceeds the size of buffer");
588 if (!readingChunkSize)
589 size = GetMaxSizeFromChunkLeft (size);
592 res = base.Read (buffer, offset, size);
593 } catch (Exception e) {
594 throw new IOException ("Read failure", e);
597 AdjustChunkLeft (res);
602 void CheckDisposed ()
605 throw new ObjectDisposedException (GetType ().FullName);
608 void AdjustChunkLeft (int read)