| File: System\Net\HttpWebResponse.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Requests\src\System.Net.Requests.csproj (System.Net.Requests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; namespace System.Net { /// <devdoc> /// <para> /// An HTTP-specific implementation of the /// <see cref='System.Net.WebResponse'/> class. /// </para> /// </devdoc> public class HttpWebResponse : WebResponse, ISerializable { private HttpResponseMessage _httpResponseMessage = null!; private readonly Uri _requestUri; private CookieCollection _cookies; private WebHeaderCollection? _webHeaderCollection; private string? _characterSet; private readonly bool _isVersionHttp11 = true; [Obsolete("This API supports the .NET infrastructure and is not intended to be used directly from your code.", true)] [EditorBrowsable(EditorBrowsableState.Never)] public HttpWebResponse() { _requestUri = null!; _cookies = null!; } [Obsolete("Serialization has been deprecated for HttpWebResponse.")] [EditorBrowsable(EditorBrowsableState.Never)] protected HttpWebResponse(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { throw new PlatformNotSupportedException(); } [Obsolete("Serialization has been deprecated for HttpWebResponse.")] void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) { throw new PlatformNotSupportedException(); } [Obsolete("Serialization has been deprecated for HttpWebResponse.")] protected override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) { throw new PlatformNotSupportedException(); } internal HttpWebResponse(HttpResponseMessage _message, Uri requestUri, CookieContainer? cookieContainer) { _httpResponseMessage = _message; _requestUri = requestUri; // Match Desktop behavior. If the request didn't set a CookieContainer, we don't populate the response's CookieCollection. if (cookieContainer != null) { _cookies = cookieContainer.GetCookies(requestUri); } else { _cookies = new CookieCollection(); } } public override bool IsMutuallyAuthenticated { get { return base.IsMutuallyAuthenticated; } } public override long ContentLength { get { CheckDisposed(); return _httpResponseMessage.Content?.Headers.ContentLength ?? -1; } } public override string ContentType { get { CheckDisposed(); // We use TryGetValues() instead of the strongly type Headers.ContentType property so that // we return a string regardless of it being fully RFC conformant. This matches current // .NET Framework behavior. if (_httpResponseMessage.Content != null && _httpResponseMessage.Content.Headers.TryGetValues("Content-Type", out IEnumerable<string>? values)) { // In most cases, there is only one media type value as per RFC. But for completeness, we // return all values in cases of overly malformed strings. return string.Join(',', values); } else { return string.Empty; } } } public string ContentEncoding { get { CheckDisposed(); if (_httpResponseMessage.Content != null) { return GetHeaderValueAsString(_httpResponseMessage.Content.Headers.ContentEncoding); } return string.Empty; } } public virtual CookieCollection Cookies { get { CheckDisposed(); return _cookies; } set { CheckDisposed(); _cookies = value; } } public DateTime LastModified { get { CheckDisposed(); string? lastmodHeaderValue = Headers["Last-Modified"]; if (string.IsNullOrEmpty(lastmodHeaderValue)) { return DateTime.Now; } if (HttpDateParser.TryParse(lastmodHeaderValue, out DateTimeOffset dateTimeOffset)) { return dateTimeOffset.LocalDateTime; } else { throw new ProtocolViolationException(SR.net_baddate); } } } /// <devdoc> /// <para> /// Gets the name of the server that sent the response. /// </para> /// </devdoc> public string Server { get { CheckDisposed(); string? server = Headers["Server"]; return string.IsNullOrEmpty(server) ? string.Empty : server; } } // HTTP Version /// <devdoc> /// <para> /// Gets /// the version of the HTTP protocol used in the response. /// </para> /// </devdoc> public Version ProtocolVersion { get { CheckDisposed(); return _isVersionHttp11 ? HttpVersion.Version11 : HttpVersion.Version10; } } public override WebHeaderCollection Headers { get { CheckDisposed(); if (_webHeaderCollection == null) { _webHeaderCollection = new WebHeaderCollection(); foreach (var header in _httpResponseMessage.Headers) { _webHeaderCollection[header.Key] = GetHeaderValueAsString(header.Value); } if (_httpResponseMessage.Content != null) { foreach (var header in _httpResponseMessage.Content.Headers) { _webHeaderCollection[header.Key] = GetHeaderValueAsString(header.Value); } } } return _webHeaderCollection; } } public virtual string Method { get { CheckDisposed(); return _httpResponseMessage.RequestMessage!.Method.Method; } } public override Uri ResponseUri { get { CheckDisposed(); // The underlying System.Net.Http API will automatically update // the .RequestUri property to be the final URI of the response. return _httpResponseMessage.RequestMessage!.RequestUri!; } } public virtual HttpStatusCode StatusCode { get { CheckDisposed(); return _httpResponseMessage.StatusCode; } } public virtual string StatusDescription { get { CheckDisposed(); return _httpResponseMessage.ReasonPhrase ?? string.Empty; } } /// <devdoc> /// <para>[To be supplied.]</para> /// </devdoc> public string? CharacterSet { get { CheckDisposed(); string? contentType = Headers["Content-Type"]; if (_characterSet == null && !string.IsNullOrWhiteSpace(contentType)) { //sets characterset so the branch is never executed again. _characterSet = string.Empty; //first string is the media type string srchString = contentType.ToLowerInvariant(); //media subtypes of text type has a default as specified by rfc 2616 if (srchString.AsSpan().Trim().StartsWith("text/", StringComparison.Ordinal)) { _characterSet = "ISO-8859-1"; } //one of the parameters may be the character set //there must be at least a mediatype for this to be valid int i = srchString.IndexOf(';'); if (i > 0) { //search the parameters while ((i = srchString.IndexOf("charset", i, StringComparison.Ordinal)) >= 0) { i += 7; //make sure the word starts with charset if (srchString[i - 8] == ';' || srchString[i - 8] == ' ') { //skip whitespace while (i < srchString.Length && srchString[i] == ' ') i++; //only process if next character is '=' //and there is a character after that if (i < srchString.Length - 1 && srchString[i] == '=') { i++; //get and trim character set substring int j = srchString.IndexOf(';', i); //In the past we used //Substring(i, j). J is the offset not the length //the qfe is to fix the second parameter so that this it is the //length. since j points to the next ; the operation j -i //gives the length of the charset if (j > i) _characterSet = contentType.AsSpan(i, j - i).Trim().ToString(); else _characterSet = contentType.AsSpan(i).Trim().ToString(); //done break; } } } } } return _characterSet; } } public override bool SupportsHeaders { get { return true; } } public override Stream GetResponseStream() { CheckDisposed(); if (_httpResponseMessage.Content != null) { Stream contentStream = _httpResponseMessage.Content.ReadAsStream(); int maxErrorResponseLength = HttpWebRequest.DefaultMaximumErrorResponseLength; if (maxErrorResponseLength < 0 || StatusCode < HttpStatusCode.BadRequest) { return contentStream; } return new TruncatedReadStream(contentStream, (long)maxErrorResponseLength * 1024); } return Stream.Null; } public string GetResponseHeader(string headerName) { CheckDisposed(); string? headerValue = Headers[headerName]; return headerValue ?? string.Empty; } public override void Close() { Dispose(true); } protected override void Dispose(bool disposing) { var httpResponseMessage = _httpResponseMessage; if (httpResponseMessage != null) { httpResponseMessage.Dispose(); _httpResponseMessage = null!; } } private void CheckDisposed() { ObjectDisposedException.ThrowIf(_httpResponseMessage == null, this); } private static string GetHeaderValueAsString(IEnumerable<string> values) => string.Join(", ", values); internal sealed class TruncatedReadStream(Stream innerStream, long maxSize) : Stream { private long _maxRemainingLength = maxSize; public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => false; public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override void Flush() => throw new NotSupportedException(); public override int Read(byte[] buffer, int offset, int count) { return Read(new Span<byte>(buffer, offset, count)); } public override int Read(Span<byte> buffer) { int readBytes = innerStream.Read(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength))); _maxRemainingLength -= readBytes; return readBytes; } public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask(); } public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) { int readBytes = await innerStream.ReadAsync(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength)), cancellationToken) .ConfigureAwait(false); _maxRemainingLength -= readBytes; return readBytes; } public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); public override ValueTask DisposeAsync() => innerStream.DisposeAsync(); protected override void Dispose(bool disposing) { if (disposing) { innerStream.Dispose(); } } } } }