1 //------------------------------------------------------------------------------
2 // <copyright file="HttpWebResponse.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
9 using System.IO.Compression;
10 using System.Runtime.Serialization;
11 using System.ComponentModel;
12 using System.Globalization;
13 using System.Security.Cryptography.X509Certificates;
14 using System.Security.Permissions;
15 using System.Diagnostics;
16 using System.Diagnostics.CodeAnalysis;
17 using System.Net.WebSockets;
20 // HttpWebResponse - Handles retrival of HTTP Response headers, und Data reads.
25 /// An HTTP-specific implementation of the
26 /// <see cref='System.Net.WebResponse'/> class.
30 public class HttpWebResponse : WebResponse, ISerializable
32 // response Uri generated by the request.
34 // response Verb gernated by the request
35 private KnownHttpVerb m_Verb;
37 private HttpStatusCode m_StatusCode;
38 private string m_StatusDescription;
39 // ConnectStream - for reading actual data
40 private Stream m_ConnectStream;
41 // For the rare case of Post -> Get redirect where we need to re-process the response.
42 private CoreResponseData m_CoreResponseData;
44 private WebHeaderCollection m_HttpResponseHeaders;
46 // Content Length needed for symantics, -1 if chunked
47 private long m_ContentLength;
49 // for which response ContentType we will look for and parse the CharacterSet
50 private string m_MediaType;
51 private string m_CharacterSet = null;
53 private bool m_IsVersionHttp11;
56 // server certificate for secure connections
57 internal X509Certificate m_Certificate;
58 #endif // !FEATURE_PAL
60 private CookieCollection m_cookies;
61 private bool m_disposed; // = false;
62 private bool m_propertiesDisposed;
63 private bool m_UsesProxySemantics;
64 private bool m_IsMutuallyAuthenticated;
66 private bool m_IsWebSocketResponse;
67 private string m_ConnectionGroupName;
68 private Stream m_WebSocketConnectionStream = null;
70 internal bool IsWebSocketResponse
72 get { return m_IsWebSocketResponse; }
73 set { m_IsWebSocketResponse = value; }
76 internal string ConnectionGroupName
78 get { return m_ConnectionGroupName; }
79 set { m_ConnectionGroupName = value; }
83 // Internal Access to the Response Stream,
84 // public method is GetResponseStream
86 internal Stream ResponseStream {
88 return m_ConnectStream;
91 m_ConnectStream = value;
95 internal CoreResponseData CoreResponseData
97 get { return m_CoreResponseData; }
101 public override bool IsMutuallyAuthenticated {
104 return m_IsMutuallyAuthenticated;
107 internal bool InternalSetIsMutuallyAuthenticated {
109 m_IsMutuallyAuthenticated = value;
114 /// <para>[To be supplied.]</para>
116 public virtual CookieCollection Cookies {
119 if (m_cookies == null) {
120 m_cookies = new CookieCollection();
130 // retreives response header object
134 /// the headers associated with this response from the server.
137 public override WebHeaderCollection Headers {
140 return m_HttpResponseHeaders;
144 // For portability only
145 public override bool SupportsHeaders {
151 // ContentLength, -1 indicates unknown value
154 /// Gets the lenght of the content returned by the request.
157 public override long ContentLength {
160 return m_ContentLength;
167 /// method used to encode the body of the response.
170 public String ContentEncoding {
173 string contentEncoding = m_HttpResponseHeaders[HttpKnownHeaderNames.ContentEncoding];
174 return contentEncoding == null ? string.Empty : contentEncoding;
178 // Returns the Content-Type of the response.
182 /// Gets the content type of the
186 public override string ContentType {
189 string contentType = m_HttpResponseHeaders.ContentType;
190 return contentType == null ? string.Empty : contentType;
196 /// <para>[To be supplied.]</para>
198 public string CharacterSet {
201 string contentType = m_HttpResponseHeaders.ContentType;
203 if (m_CharacterSet == null && !ValidationHelper.IsBlankString(contentType)) {
205 //sets characterset so the branch is never executed again.
206 m_CharacterSet = String.Empty;
208 //first string is the media type
209 string srchString = contentType.ToLower(CultureInfo.InvariantCulture);
211 //media subtypes of text type has a default as specified by rfc 2616
212 if (srchString.Trim().StartsWith("text/")) {
213 m_CharacterSet = "ISO-8859-1";
216 //one of the parameters may be the character set
217 //there must be at least a mediatype for this to be valid
218 int i = srchString.IndexOf(";");
221 //search the parameters
222 while ((i = srchString.IndexOf("charset",i)) >= 0){
226 //make sure the word starts with charset
227 if (srchString[i-8] == ';' || srchString[i-8] == ' '){
230 while (i < srchString.Length && srchString[i] == ' ')
233 //only process if next character is '='
234 //and there is a character after that
235 if (i < srchString.Length - 1 && srchString[i] == '=') {
238 //get and trim character set substring
239 int j = srchString.IndexOf(';',i);
240 //In the past we used
241 //Substring(i, j). J is the offset not the length
242 //the qfe is to fix the second parameter so that this it is the
243 //length. since j points to the next ; the operation j -i
244 //gives the length of the charset
246 m_CharacterSet = contentType.Substring(i,j - i).Trim();
248 m_CharacterSet = contentType.Substring(i).Trim();
257 return m_CharacterSet;
263 /// Gets the name of the server that sent the response.
266 public string Server {
269 string server = m_HttpResponseHeaders.Server;
270 return server == null ? string.Empty : server;
277 /// date and time that the content of the response was modified.
280 public DateTime LastModified {
284 string lastmodHeaderValue = m_HttpResponseHeaders.LastModified;
286 if (lastmodHeaderValue == null) {
289 return HttpProtocolUtils.string2date(lastmodHeaderValue);
293 // returns StatusCode
297 /// a number indicating the status of the response.
300 public virtual HttpStatusCode StatusCode {
307 // returns StatusDescription
310 /// Gets the status description returned with the response.
313 public virtual string StatusDescription {
316 return m_StatusDescription;
324 /// the version of the HTTP protocol used in the response.
327 public Version ProtocolVersion {
330 return m_IsVersionHttp11 ? HttpVersion.Version11 : HttpVersion.Version10;
335 internal bool KeepAlive {
338 // QFE - DevDiv bug: 37757
339 // If there is proxy involved, independen of the Http Version, we should honor the
340 // proxy indicated Proxy-Connection header value.
341 // This header value is not RFC mandated, but is a legacy from Netscape documentations.
342 // It indicates that the proxy wants to keep the connection.
343 // Functionally it is equivalent of a Keep-Alive AND/OR Connection header.
345 // The absence of this check will result in HTTP/1.0 responsen be considered to be not
346 // Keeping the connection alive.
348 // This will result in a state mismatch between the connection pool and HttpWebRequest object
349 // when the decision to drain the connection and putting it back to the idle pool is made.
351 if (m_UsesProxySemantics)
353 string proxyConnectionHeader = Headers[HttpKnownHeaderNames.ProxyConnection];
354 if (proxyConnectionHeader != null)
356 return proxyConnectionHeader.ToLower(CultureInfo.InvariantCulture).IndexOf("close") < 0 ||
357 proxyConnectionHeader.ToLower(CultureInfo.InvariantCulture).IndexOf("keep-alive") >= 0;
361 string connectionHeader = Headers[HttpKnownHeaderNames.Connection];
362 if (connectionHeader != null)
364 connectionHeader = connectionHeader.ToLower(CultureInfo.InvariantCulture);
367 if (ProtocolVersion == HttpVersion.Version10)
369 // An HTTP/1.0 response is keep-alive if it has a connection: keep-alive header
370 return (connectionHeader != null &&
371 connectionHeader.IndexOf("keep-alive") >= 0);
373 else if (ProtocolVersion >= HttpVersion.Version11)
375 // An HTTP/1.1 response is keep-alive if it DOESN'T have a connection: close header, or if it
376 // also has a connection: keep-alive header
377 return (connectionHeader == null ||
378 connectionHeader.IndexOf("close") < 0 ||
379 connectionHeader.IndexOf("keep-alive") >= 0);
389 ResponseStream - Return the response stream.
391 This property returns the response stream for this response. The
392 response stream will do de-chunking, etc. as needed.
394 Input: Nothing. Property is readonly.
396 Returns: Response stream for response.
402 /// <para>Gets the stream used for reading the body of the response from the
405 public override Stream GetResponseStream() {
406 if(Logging.On)Logging.Enter(Logging.Web, this, "GetResponseStream", "");
408 if(Logging.On)Logging.PrintInfo(Logging.Web, "ContentLength="+m_ContentLength);
411 if (m_IsWebSocketResponse && m_StatusCode == HttpStatusCode.SwitchingProtocols) // HTTP 101
413 if (this.m_WebSocketConnectionStream == null)
415 ConnectStream connectStream = m_ConnectStream as ConnectStream;
416 GlobalLog.Assert(connectStream != null, "HttpWebResponse.m_ConnectStream should always be a ConnectStream in WebSocket cases.");
417 GlobalLog.Assert(connectStream.Connection != null, "HttpWebResponse.m_ConnectStream.Connection should never be null in WebSocket cases.");
418 this.m_WebSocketConnectionStream = new WebSocketConnectionStream(connectStream, this.ConnectionGroupName);
420 result = this.m_WebSocketConnectionStream;
424 result = m_ConnectStream;
427 if(Logging.On)Logging.Exit(Logging.Web, this, "GetResponseStream", result);
433 Close - Closes the Response after the use.
435 This causes the read stream to be closed.
439 public override void Close() {
440 if(Logging.On)Logging.Enter(Logging.Web, this, "Close", "");
446 Stream stream = m_ConnectStream;
447 ICloseEx icloseEx = stream as ICloseEx;
448 if (icloseEx != null)
450 icloseEx.CloseEx(CloseExState.Normal /* | CloseExState.Silent */);
452 else if (stream != null)
459 if (this.IsWebSocketResponse)
461 ConnectStream connectStream = m_ConnectStream as ConnectStream;
462 if (connectStream != null && connectStream.Connection != null)
464 connectStream.Connection.ServicePoint.CloseConnectionGroup(ConnectionGroupName);
469 if(Logging.On)Logging.Exit(Logging.Web, this, "Close", "");
472 // This method is only to be called by HttpWebRequest.Abort().
473 internal void Abort() {
474 Stream stream = m_ConnectStream;
475 ICloseEx icloseEx = stream as ICloseEx;
478 if (icloseEx != null) {
479 icloseEx.CloseEx(CloseExState.Abort);
481 else if (stream != null) {
488 [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed",
489 Justification = "The base class calls Close() to clean up m_ConnectStream")]
490 protected override void Dispose(bool disposing) {
494 base.Dispose(true); // Calls Close()
495 m_propertiesDisposed = true;
498 //introduced for supporting design-time loading of System.Windows.dll
499 [Obsolete("This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.", true)]
500 [EditorBrowsable(EditorBrowsableState.Never)]
501 public HttpWebResponse(){}
503 internal HttpWebResponse(Uri responseUri, KnownHttpVerb verb, CoreResponseData coreData, string mediaType,
504 bool usesProxySemantics, DecompressionMethods decompressionMethod,
505 bool isWebSocketResponse, string connectionGroupName) {
508 m_MediaType = mediaType;
509 m_UsesProxySemantics = usesProxySemantics;
511 m_CoreResponseData = coreData;
512 m_ConnectStream = coreData.m_ConnectStream;
513 m_HttpResponseHeaders = coreData.m_ResponseHeaders;
514 m_ContentLength = coreData.m_ContentLength;
515 m_StatusCode = coreData.m_StatusCode;
516 m_StatusDescription = coreData.m_StatusDescription;
517 m_IsVersionHttp11 = coreData.m_IsVersionHttp11;
519 m_IsWebSocketResponse = isWebSocketResponse;
520 m_ConnectionGroupName = connectionGroupName;
522 //if the returned contentlength is zero, preemptively invoke calldone on the stream.
523 //this will wake up any pending reads.
524 if (m_ContentLength == 0 && m_ConnectStream is ConnectStream) {
525 ((ConnectStream)m_ConnectStream).CallDone();
528 // handle Content-Location header, by combining it with the orginal request.
529 string contentLocation = m_HttpResponseHeaders[HttpKnownHeaderNames.ContentLocation];
531 if (contentLocation != null) {
533 m_Uri = new Uri(m_Uri, contentLocation);
534 } catch (UriFormatException e) {
535 GlobalLog.Assert("Exception on response Uri parsing.", e.ToString());
538 // decompress responses by hooking up a final response Stream - only if user required it
539 if(decompressionMethod != DecompressionMethods.None) {
540 string contentEncoding = m_HttpResponseHeaders[HttpKnownHeaderNames.ContentEncoding];
541 if (contentEncoding != null){
542 if(((decompressionMethod & DecompressionMethods.GZip) != 0) &&
543 contentEncoding.IndexOf(HttpWebRequest.GZipHeader, StringComparison.CurrentCulture) != -1) {
544 m_ConnectStream = new GZipWrapperStream(m_ConnectStream, CompressionMode.Decompress);
545 m_ContentLength = -1; // unknown on compressed streams
547 // Setting a response header after parsing will ruin the Common Header optimization.
548 // This seems like a corner case. ContentEncoding could be added as a common header, with a special
549 // property allowing it to be nulled.
550 m_HttpResponseHeaders[HttpKnownHeaderNames.ContentEncoding] = null;
552 else if (((decompressionMethod & DecompressionMethods.Deflate) != 0) &&
553 contentEncoding.IndexOf(HttpWebRequest.DeflateHeader, StringComparison.CurrentCulture) != -1) {
554 m_ConnectStream = new DeflateWrapperStream(m_ConnectStream, CompressionMode.Decompress);
555 m_ContentLength = -1; // unknown on compressed streams
557 // Setting a response header after parsing will ruin the Common Header optimization.
558 // This seems like a corner case. ContentEncoding could be added as a common header, with a special
559 // property allowing it to be nulled.
560 m_HttpResponseHeaders[HttpKnownHeaderNames.ContentEncoding] = null;
567 // ISerializable constructor
570 /// <para>[To be supplied.]</para>
573 [Obsolete("Serialization is obsoleted for this type. http://go.microsoft.com/fwlink/?linkid=14202")]
574 protected HttpWebResponse(SerializationInfo serializationInfo, StreamingContext streamingContext):base(serializationInfo, streamingContext) {
575 m_HttpResponseHeaders = (WebHeaderCollection)serializationInfo.GetValue("m_HttpResponseHeaders", typeof(WebHeaderCollection));
576 m_Uri = (Uri)serializationInfo.GetValue("m_Uri", typeof(Uri));
578 m_Certificate = (X509Certificate)serializationInfo.GetValue("m_Certificate", typeof(X509Certificate));
579 #endif // !FEATURE_PAL
580 Version version = (Version)serializationInfo.GetValue("m_Version", typeof(Version));
581 m_IsVersionHttp11 = version.Equals(HttpVersion.Version11);
582 m_StatusCode = (HttpStatusCode)serializationInfo.GetInt32("m_StatusCode");
583 m_ContentLength = serializationInfo.GetInt64("m_ContentLength");
584 m_Verb = KnownHttpVerb.Parse(serializationInfo.GetString("m_Verb"));
585 m_StatusDescription = serializationInfo.GetString("m_StatusDescription");
586 m_MediaType = serializationInfo.GetString("m_MediaType");
590 // ISerializable method
593 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase", Justification = "System.dll is still using pre-v4 security model and needs this demand")]
594 [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter, SerializationFormatter=true)]
595 void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
597 GetObjectData(serializationInfo, streamingContext);
601 // FxCop: provide a way for derived classes to access this method even if they reimplement ISerializable.
604 [SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)]
605 protected override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
608 // for now disregard streamingContext.
609 // just Add all the members we need to deserialize to construct
610 // the object at deserialization time
612 // the following runtime types already support serialization:
613 // Boolean, Char, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, DateTime
614 // for the others we need to provide our own serialization
616 serializationInfo.AddValue("m_HttpResponseHeaders", m_HttpResponseHeaders, typeof(WebHeaderCollection));
617 serializationInfo.AddValue("m_Uri", m_Uri, typeof(Uri));
619 serializationInfo.AddValue("m_Certificate", m_Certificate, typeof(X509Certificate));
620 #endif // !FEATURE_PAL
621 serializationInfo.AddValue("m_Version", ProtocolVersion, typeof(Version));
622 serializationInfo.AddValue("m_StatusCode", m_StatusCode);
623 serializationInfo.AddValue("m_ContentLength", m_ContentLength);
624 serializationInfo.AddValue("m_Verb", m_Verb.Name);
625 serializationInfo.AddValue("m_StatusDescription", m_StatusDescription);
626 serializationInfo.AddValue("m_MediaType", m_MediaType);
627 base.GetObjectData(serializationInfo, streamingContext);
634 Gets response headers from parsed server response
638 headerName - HTTP header to search for matching header on.
642 string - contains the matched entry, if found
647 /// Gets a specified header value returned with the response.
650 public string GetResponseHeader( string headerName ) {
653 string headerValue = m_HttpResponseHeaders[headerName];
655 return ( (headerValue==null) ? String.Empty : headerValue );
658 #if HTTP_HEADER_EXTENSIONS_SUPPORTED
659 // searches for extension header in response
661 /// <para>[To be supplied.]</para>
663 public string GetExtension(HttpExtension extension, string header) {
665 return GetResponseHeader(header);
671 ResponseUri - Gets the final Response Uri, that includes any
672 changes that may have transpired from the orginal Request
674 This property returns Uri for this WebResponse.
678 Returns: Response Uri for response.
686 /// Gets the Uniform Resource Indentifier (Uri) of the resource that returned the
690 public override Uri ResponseUri { // read-only
700 Gets/Sets the http method of this request.
701 This method represents the Verb,
704 Returns: Method currently being used.
710 /// Gets the value of the method used to return the response.
713 public virtual string Method {
720 private void CheckDisposed() {
721 if (m_propertiesDisposed) {
722 throw new ObjectDisposedException(this.GetType().FullName);
725 } // class HttpWebResponse
727 // transfer ICloseEx behavior to nested compound stream.
728 internal class GZipWrapperStream : GZipStream, ICloseEx, IRequestLifetimeTracker {
730 public GZipWrapperStream(Stream stream, CompressionMode mode) : base( stream, mode, false) {
733 void ICloseEx.CloseEx(CloseExState closeState) {
734 ICloseEx icloseEx = BaseStream as ICloseEx;
735 if (icloseEx != null) {
736 // since the internal Close() of GZipStream just does Close on our base stream
737 // we don't have to call it anymore at this point
738 icloseEx.CloseEx(closeState);
745 public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, Object state) {
748 throw new ArgumentNullException("buffer");
750 if (offset<0 || offset>buffer.Length) {
751 throw new ArgumentOutOfRangeException("offset");
753 if (size<0 || size>buffer.Length-offset) {
754 throw new ArgumentOutOfRangeException("size");
758 return base.BeginRead(buffer, offset, size, callback, state);
762 if (NclUtilities.IsFatal(e)) throw;
763 if(e is System.IO.InvalidDataException || e is InvalidOperationException || e is IndexOutOfRangeException){
773 public override int EndRead(IAsyncResult asyncResult) {
775 if (asyncResult==null) {
776 throw new ArgumentNullException("asyncResult");
780 return base.EndRead(asyncResult);
784 if (NclUtilities.IsFatal(e)) throw;
785 if(e is System.IO.InvalidDataException || e is InvalidOperationException || e is IndexOutOfRangeException){
795 public override int Read(byte[] buffer, int offset, int size) {
798 throw new ArgumentNullException("buffer");
800 if (offset<0 || offset>buffer.Length) {
801 throw new ArgumentOutOfRangeException("offset");
803 if (size<0 || size>buffer.Length-offset) {
804 throw new ArgumentOutOfRangeException("size");
808 return base.Read(buffer, offset, size);
812 if (NclUtilities.IsFatal(e)) throw;
814 if(e is System.IO.InvalidDataException || e is InvalidOperationException || e is IndexOutOfRangeException){
824 void IRequestLifetimeTracker.TrackRequestLifetime(long requestStartTimestamp)
826 IRequestLifetimeTracker stream = BaseStream as IRequestLifetimeTracker;
827 Debug.Assert(stream != null, "Wrapped stream must implement IRequestLifetimeTracker interface");
828 stream.TrackRequestLifetime(requestStartTimestamp);
833 internal class DeflateWrapperStream : DeflateStream, ICloseEx, IRequestLifetimeTracker {
835 public DeflateWrapperStream(Stream stream, CompressionMode mode) : base( stream, mode, false) {
838 void ICloseEx.CloseEx(CloseExState closeState) {
839 ICloseEx icloseEx = BaseStream as ICloseEx;
840 if (icloseEx != null) {
841 // since the internal Close() of GZipStream just does Close on our base stream
842 // we don't have to call it anymore at this point
843 icloseEx.CloseEx(closeState);
851 public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, Object state) {
854 throw new ArgumentNullException("buffer");
856 if (offset<0 || offset>buffer.Length) {
857 throw new ArgumentOutOfRangeException("offset");
859 if (size<0 || size>buffer.Length-offset) {
860 throw new ArgumentOutOfRangeException("size");
864 return base.BeginRead(buffer, offset, size, callback, state);
868 if (NclUtilities.IsFatal(e)) throw;
869 if(e is System.IO.InvalidDataException || e is InvalidOperationException || e is IndexOutOfRangeException){
879 public override int EndRead(IAsyncResult asyncResult) {
881 if (asyncResult==null) {
882 throw new ArgumentNullException("asyncResult");
886 return base.EndRead(asyncResult);
890 if (NclUtilities.IsFatal(e)) throw;
891 if(e is System.IO.InvalidDataException || e is InvalidOperationException || e is IndexOutOfRangeException){
901 public override int Read(byte[] buffer, int offset, int size) {
904 throw new ArgumentNullException("buffer");
906 if (offset<0 || offset>buffer.Length) {
907 throw new ArgumentOutOfRangeException("offset");
909 if (size<0 || size>buffer.Length-offset) {
910 throw new ArgumentOutOfRangeException("size");
914 return base.Read(buffer, offset, size);
918 if (NclUtilities.IsFatal(e)) throw;
919 if(e is System.IO.InvalidDataException || e is InvalidOperationException || e is IndexOutOfRangeException){
929 void IRequestLifetimeTracker.TrackRequestLifetime(long requestStartTimestamp)
931 IRequestLifetimeTracker stream = BaseStream as IRequestLifetimeTracker;
932 Debug.Assert(stream != null, "Wrapped stream must implement IRequestLifetimeTracker interface");
933 stream.TrackRequestLifetime(requestStartTimestamp);
939 } // namespace System.Net