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)) != "");
57 line = ReadHttpLine (responseStream);
59 protocol = line.Split (' ');
61 switch (protocol[0]) {
63 this.version = HttpVersion.Version10;
66 this.version = HttpVersion.Version11;
69 throw new WebException ("Unrecognized HTTP Version: " + line);
72 this.statusCode = (HttpStatusCode) Int32.Parse (protocol[1]);
73 } while (this.statusCode == HttpStatusCode.Continue);
75 while ((line = ReadHttpLine (responseStream)).Length != 0) {
76 if (!Char.IsWhiteSpace (line[0])) { // new header
77 header = line.Split (new char[] {':'}, 2);
78 if (header.Length != 2)
79 throw new WebException ("Bad HTTP Header");
80 if (last != null) { // not the first header
81 if (last.Equals ("Set-Cookie"))
82 SetCookie (value.ToString());
83 else if (last.Equals ("Set-Cookie2"))
84 SetCookie2 (value.ToString());
85 else //don't save Set-Cookie headers
86 this.webHeaders[last] = value.ToString();
89 value = new Text.StringBuilder (header[1].Trim());
90 if (last == "Transfer-Encoding" && header [1].IndexOf ("chunked") != -1)
94 value.Append (line.Trim());
97 responseStream.Chunked = chunked;
98 this.webHeaders[last] = value.ToString(); // otherwise we miss the last header
100 responseStream.BytesLeft = ContentLength;
103 protected HttpWebResponse (SerializationInfo serializationInfo, StreamingContext streamingContext)
105 uri = (Uri) serializationInfo.GetValue ("uri", typeof (Uri));
106 webHeaders = (WebHeaderCollection) serializationInfo.GetValue ("webHeaders",
107 typeof (WebHeaderCollection));
108 cookieCollection = (CookieCollection) serializationInfo.GetValue ("cookieCollection",
109 typeof (CookieCollection));
110 method = serializationInfo.GetString ("method");
111 version = (Version) serializationInfo.GetValue ("version", typeof (Version));
112 statusCode = (HttpStatusCode) serializationInfo.GetValue ("statusCode", typeof (HttpStatusCode));
113 statusDescription = serializationInfo.GetString ("statusDescription");
114 chunked = serializationInfo.GetBoolean ("chunked");
119 public string CharacterSet {
120 // Content-Type = "Content-Type" ":" media-type
121 // media-type = type "/" subtype *( ";" parameter )
122 // parameter = attribute "=" value
123 // 3.7.1. default is ISO-8859-1
126 string contentType = ContentType;
127 if (contentType == null)
129 string val = contentType.ToLower ();
130 int pos = val.IndexOf ("charset=");
134 int pos2 = val.IndexOf (';', pos);
136 ? contentType.Substring (pos)
137 : contentType.Substring (pos, pos2 - pos);
141 public string ContentEncoding {
144 return webHeaders ["Content-Encoding"];
148 public override long ContentLength {
152 return Int64.Parse (webHeaders ["Content-Length"]);
153 } catch (Exception) {
159 public override string ContentType {
162 return webHeaders ["Content-Type"];
166 public CookieCollection Cookies {
170 if (cookieCollection == null)
171 cookieCollection = new CookieCollection ();
172 return cookieCollection;
176 // ?? don't understand how you can set cookies on a response.
177 throw new NotSupportedException ();
181 public override WebHeaderCollection Headers {
188 public DateTime LastModified {
192 string dtStr = webHeaders ["Last-Modified"];
193 return MonoHttpDate.Parse (dtStr);
194 } catch (Exception) {
200 public string Method {
207 public Version ProtocolVersion {
214 public override Uri ResponseUri {
221 public string Server {
224 return webHeaders ["Server"];
228 public HttpStatusCode StatusCode {
235 public string StatusDescription {
238 return statusDescription;
244 public override int GetHashCode ()
247 return base.GetHashCode ();
250 public string GetResponseHeader (string headerName)
253 return webHeaders [headerName];
256 public override Stream GetResponseStream ()
259 if (method.Equals ("HEAD")) // see par 4.3 & 9.4
261 return responseStream;
264 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
265 StreamingContext streamingContext)
268 serializationInfo.AddValue ("uri", uri);
269 serializationInfo.AddValue ("webHeaders", webHeaders);
270 serializationInfo.AddValue ("cookieCollection", cookieCollection);
271 serializationInfo.AddValue ("method", method);
272 serializationInfo.AddValue ("version", version);
273 serializationInfo.AddValue ("statusCode", statusCode);
274 serializationInfo.AddValue ("statusDescription", statusDescription);
275 serializationInfo.AddValue ("chunked", chunked);
286 public override void Close ()
288 ((IDisposable) this).Dispose ();
291 void IDisposable.Dispose ()
294 GC.SuppressFinalize (this);
297 protected virtual void Dispose (bool disposing)
301 this.disposed = true;
304 // release managed resources
307 cookieCollection = null;
310 statusDescription = null;
313 // release unmanaged resources
314 Stream stream = responseStream;
315 responseStream = null;
320 private void CheckDisposed ()
323 throw new ObjectDisposedException (GetType ().FullName);
326 private static string ReadHttpLine (Stream stream)
328 StringBuilder line = new StringBuilder();
329 byte last = (byte)'\n';
330 bool read_last = false;
333 while ((c = stream.ReadByte ()) != -1) {
335 if ((last = (byte) stream.ReadByte ()) == '\n') // headers; not at EOS
340 line.Append ((char) c);
342 line.Append (Convert.ToChar (last));
347 return line.ToString();
350 private void SetCookie (string cookie_str)
352 string[] parts = null;
353 Collections.Queue options = null;
354 Cookie cookie = null;
356 options = new Collections.Queue (cookie_str.Split (';'));
357 parts = ((string)options.Dequeue()).Split ('='); // NAME=VALUE must be first
359 cookie = new Cookie (parts[0], parts[1]);
361 while (options.Count > 0) {
362 parts = ((string)options.Dequeue()).Split ('=');
363 switch (parts[0].ToUpper()) { // cookie options are case-insensitive
365 if (cookie.Comment == null)
366 cookie.Comment = parts[1];
369 if (cookie.CommentUri == null)
370 cookie.CommentUri = new Uri(parts[1]);
373 cookie.Discard = true;
376 if (cookie.Domain == null)
377 cookie.Domain = parts[1];
379 case "MAX-AGE": // RFC Style Set-Cookie2
380 if (cookie.Expires == DateTime.MinValue)
381 cookie.Expires = cookie.TimeStamp.AddSeconds (Int32.Parse (parts[1]));
383 case "EXPIRES": // Netscape Style Set-Cookie
384 if (cookie.Expires == DateTime.MinValue)
385 cookie.Expires = DateTime.Parse (parts[1]);
388 if (cookie.Path == null)
389 cookie.Path = parts[1];
392 if (cookie.Port == null)
393 cookie.Port = parts[1];
396 cookie.Secure = true;
399 cookie.Version = Int32.Parse (parts[1]);
404 if (cookieCollection == null)
405 cookieCollection = new CookieCollection();
407 if (cookie.Domain == null)
408 cookie.Domain = uri.Host;
410 cookieCollection.Add (cookie);
413 private void SetCookie2 (string cookies_str)
415 string[] cookies = cookies_str.Split (',');
417 foreach (string cookie_str in cookies)
418 SetCookie (cookie_str);
422 class HttpWebResponseStream : NetworkStream
428 bool readingChunkSize;
429 EventHandler onClose;
431 // If ContentLength provided, the number of bytes left to read
433 internal long BytesLeft = -1;
435 public HttpWebResponseStream (Socket socket, EventHandler onClose)
436 : base (socket, FileAccess.Read, false)
438 this.onClose = onClose;
443 public bool Chunked {
444 get { return chunked; }
445 set { chunked = value; }
448 protected override void Dispose (bool disposing)
455 /* This does not work !??
456 if (Socket.Connected)
457 Socket.Shutdown (SocketShutdown.Receive);
461 onClose (this, EventArgs.Empty);
465 base.Dispose (disposing);
468 void ReadChunkSize ()
473 // 8 hex digits should be enough
474 for (int i = 0; i < 10; i++) {
475 char c = Char.ToUpper ((char) ReadByte ());
481 throw new IOException ("Bad stream: 2 CR");
484 if (c == '\n' && cr == true) {
490 throw new IOException ("Bad stream: got LF but no CR");
493 if (i < 8 && ((c >= '0' && c <= '9') || c >= 'A' && c <= 'F')) {
495 if (c >= 'A' && c <= 'F')
496 size += c - 'A' + 10;
502 throw new IOException ("Bad stream: got " + c);
506 throw new IOException ("Bad stream: no CR or LF after chunk size");
512 int GetMaxSizeFromChunkLeft (int requestedSize)
515 return requestedSize;
517 if (chunkSize == -1 || chunkLeft == 0) {
519 if (chunkSize == -1 || chunkLeft == 0) {
520 readingChunkSize = true;
524 readingChunkSize = false;
530 return (chunkLeft < requestedSize) ? chunkLeft : requestedSize;
533 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
534 AsyncCallback callback, object state)
540 throw new ArgumentNullException ("buffer is null");
542 int len = buffer.Length;
543 if (offset < 0 || offset >= len)
544 throw new ArgumentOutOfRangeException ("offset exceeds the size of buffer");
546 if (offset + size < 0 || offset+size > len)
547 throw new ArgumentOutOfRangeException ("offset+size exceeds the size of buffer");
549 if (!readingChunkSize)
550 size = GetMaxSizeFromChunkLeft (size);
553 retval = base.BeginRead (buffer, offset, size, callback, state);
555 throw new IOException ("BeginReceive failure");
561 public override int EndRead (IAsyncResult ar)
570 throw new ArgumentNullException ("async result is null");
573 res = base.EndRead (ar);
576 } catch (Exception e) {
577 throw new IOException ("EndRead failure", e);
580 AdjustChunkLeft (res);
584 public override int Read (byte [] buffer, int offset, int size)
589 throw new ArgumentNullException ("buffer is null");
591 if (offset < 0 || offset >= buffer.Length)
592 throw new ArgumentOutOfRangeException ("offset exceeds the size of buffer");
594 if (offset + size < 0 || offset + size > buffer.Length)
595 throw new ArgumentOutOfRangeException ("offset+size exceeds the size of buffer");
597 if (!readingChunkSize)
598 size = GetMaxSizeFromChunkLeft (size);
604 res = base.Read (buffer, offset, size);
607 } catch (Exception e) {
608 throw new IOException ("Read failure", e);
611 AdjustChunkLeft (res);
616 void CheckDisposed ()
619 throw new ObjectDisposedException (GetType ().FullName);
622 void AdjustChunkLeft (int read)