2014-08-13 10:39:27 +01:00
//
2019-04-12 14:10:50 +00:00
// MonoWebRequestHandler.cs
2014-08-13 10:39:27 +01:00
//
// Authors:
// Marek Safar <marek.safar@gmail.com>
2019-04-12 14:10:50 +00:00
// Martin Baulig <mabaul@microsoft.com>
2014-08-13 10:39:27 +01:00
//
2019-04-12 14:10:50 +00:00
// Copyright (C) 2011-2018 Xamarin Inc (http://www.xamarin.com)
2014-08-13 10:39:27 +01:00
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
2016-08-23 13:20:38 +00:00
using System.Collections.Generic ;
using System.Security.Authentication ;
using System.Security.Cryptography.X509Certificates ;
2019-04-12 14:10:50 +00:00
using System.Security.Principal ;
2014-08-13 10:39:27 +01:00
using System.Threading ;
using System.Threading.Tasks ;
using System.Collections.Specialized ;
using System.Net.Http.Headers ;
2019-04-12 14:10:50 +00:00
using System.Net.Cache ;
2016-08-23 13:20:38 +00:00
using System.Net.Security ;
2015-08-26 07:17:56 -04:00
using System.Linq ;
2014-08-13 10:39:27 +01:00
namespace System.Net.Http
{
2019-04-12 14:10:50 +00:00
class MonoWebRequestHandler : IMonoHttpClientHandler
2014-08-13 10:39:27 +01:00
{
static long groupCounter ;
bool allowAutoRedirect ;
DecompressionMethods automaticDecompression ;
CookieContainer cookieContainer ;
ICredentials credentials ;
int maxAutomaticRedirections ;
long maxRequestContentBufferSize ;
bool preAuthenticate ;
IWebProxy proxy ;
bool useCookies ;
bool useProxy ;
2019-04-12 14:10:50 +00:00
SslClientAuthenticationOptions sslOptions ;
bool allowPipelining ;
RequestCachePolicy cachePolicy ;
AuthenticationLevel authenticationLevel ;
TimeSpan continueTimeout ;
TokenImpersonationLevel impersonationLevel ;
int maxResponseHeadersLength ;
int readWriteTimeout ;
RemoteCertificateValidationCallback serverCertificateValidationCallback ;
bool unsafeAuthenticatedConnectionSharing ;
2014-08-13 10:39:27 +01:00
bool sentRequest ;
string connectionGroupName ;
bool disposed ;
2019-04-12 14:10:50 +00:00
internal MonoWebRequestHandler ( )
2014-08-13 10:39:27 +01:00
{
allowAutoRedirect = true ;
maxAutomaticRedirections = 50 ;
maxRequestContentBufferSize = int . MaxValue ;
useCookies = true ;
useProxy = true ;
2019-04-12 14:10:50 +00:00
allowPipelining = true ;
authenticationLevel = AuthenticationLevel . MutualAuthRequested ;
cachePolicy = System . Net . WebRequest . DefaultCachePolicy ;
continueTimeout = TimeSpan . FromMilliseconds ( 350 ) ;
impersonationLevel = TokenImpersonationLevel . Delegation ;
maxResponseHeadersLength = HttpWebRequest . DefaultMaximumResponseHeadersLength ;
readWriteTimeout = 300000 ;
serverCertificateValidationCallback = null ;
unsafeAuthenticatedConnectionSharing = false ;
2014-08-13 10:39:27 +01:00
connectionGroupName = "HttpClientHandler" + Interlocked . Increment ( ref groupCounter ) ;
}
internal void EnsureModifiability ( )
{
if ( sentRequest )
throw new InvalidOperationException (
"This instance has already started one or more requests. " +
"Properties can only be modified before sending the first request." ) ;
}
public bool AllowAutoRedirect {
get {
return allowAutoRedirect ;
}
set {
EnsureModifiability ( ) ;
allowAutoRedirect = value ;
}
}
public DecompressionMethods AutomaticDecompression {
get {
return automaticDecompression ;
}
set {
EnsureModifiability ( ) ;
automaticDecompression = value ;
}
}
public CookieContainer CookieContainer {
get {
return cookieContainer ? ? ( cookieContainer = new CookieContainer ( ) ) ;
}
set {
EnsureModifiability ( ) ;
cookieContainer = value ;
}
}
public ICredentials Credentials {
get {
return credentials ;
}
set {
EnsureModifiability ( ) ;
credentials = value ;
}
}
public int MaxAutomaticRedirections {
get {
return maxAutomaticRedirections ;
}
set {
EnsureModifiability ( ) ;
if ( value < = 0 )
throw new ArgumentOutOfRangeException ( ) ;
maxAutomaticRedirections = value ;
}
}
public long MaxRequestContentBufferSize {
get {
return maxRequestContentBufferSize ;
}
set {
EnsureModifiability ( ) ;
if ( value < 0 )
throw new ArgumentOutOfRangeException ( ) ;
maxRequestContentBufferSize = value ;
}
}
public bool PreAuthenticate {
get {
return preAuthenticate ;
}
set {
EnsureModifiability ( ) ;
preAuthenticate = value ;
}
}
public IWebProxy Proxy {
get {
return proxy ;
}
set {
EnsureModifiability ( ) ;
if ( ! UseProxy )
throw new InvalidOperationException ( ) ;
proxy = value ;
}
}
public virtual bool SupportsAutomaticDecompression {
get {
return true ;
}
}
public virtual bool SupportsProxy {
get {
return true ;
}
}
public virtual bool SupportsRedirectConfiguration {
get {
return true ;
}
}
public bool UseCookies {
get {
return useCookies ;
}
set {
EnsureModifiability ( ) ;
useCookies = value ;
}
}
2019-04-12 14:10:50 +00:00
public bool UseProxy {
2014-08-13 10:39:27 +01:00
get {
2019-04-12 14:10:50 +00:00
return useProxy ;
2014-08-13 10:39:27 +01:00
}
set {
EnsureModifiability ( ) ;
2019-04-12 14:10:50 +00:00
useProxy = value ;
2014-08-13 10:39:27 +01:00
}
}
2019-04-12 14:10:50 +00:00
public bool AllowPipelining {
get { return allowPipelining ; }
set {
EnsureModifiability ( ) ;
allowPipelining = value ;
2014-08-13 10:39:27 +01:00
}
2019-04-12 14:10:50 +00:00
}
public RequestCachePolicy CachePolicy {
get { return cachePolicy ; }
2014-08-13 10:39:27 +01:00
set {
EnsureModifiability ( ) ;
2019-04-12 14:10:50 +00:00
cachePolicy = value ;
}
}
public AuthenticationLevel AuthenticationLevel {
get { return authenticationLevel ; }
set {
EnsureModifiability ( ) ;
authenticationLevel = value ;
}
}
[MonoTODO]
public TimeSpan ContinueTimeout {
get { return continueTimeout ; }
set {
EnsureModifiability ( ) ;
continueTimeout = value ;
}
}
public TokenImpersonationLevel ImpersonationLevel {
get { return impersonationLevel ; }
set {
EnsureModifiability ( ) ;
impersonationLevel = value ;
}
}
public int MaxResponseHeadersLength {
get { return maxResponseHeadersLength ; }
set {
EnsureModifiability ( ) ;
maxResponseHeadersLength = value ;
}
}
public int ReadWriteTimeout {
get { return readWriteTimeout ; }
set {
EnsureModifiability ( ) ;
readWriteTimeout = value ;
}
}
public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
get { return serverCertificateValidationCallback ; }
set {
EnsureModifiability ( ) ;
serverCertificateValidationCallback = value ;
}
}
public bool UnsafeAuthenticatedConnectionSharing {
get { return unsafeAuthenticatedConnectionSharing ; }
set {
EnsureModifiability ( ) ;
unsafeAuthenticatedConnectionSharing = value ;
2014-08-13 10:39:27 +01:00
}
}
2019-04-12 14:10:50 +00:00
public SslClientAuthenticationOptions SslOptions {
get = > sslOptions ? ? ( sslOptions = new SslClientAuthenticationOptions ( ) ) ;
set {
EnsureModifiability ( ) ;
sslOptions = value ;
}
}
public void Dispose ( )
{
Dispose ( true ) ;
}
protected virtual void Dispose ( bool disposing )
2014-08-13 10:39:27 +01:00
{
2015-01-13 10:44:36 +00:00
if ( disposing & & ! disposed ) {
2014-08-13 10:39:27 +01:00
Volatile . Write ( ref disposed , true ) ;
2015-01-13 10:44:36 +00:00
ServicePointManager . CloseConnectionGroup ( connectionGroupName ) ;
2014-08-13 10:39:27 +01:00
}
2019-04-12 14:10:50 +00:00
}
2014-08-13 10:39:27 +01:00
2019-04-12 14:10:50 +00:00
bool GetConnectionKeepAlive ( HttpRequestHeaders headers )
{
return headers . Connection . Any ( l = > string . Equals ( l , "Keep-Alive" , StringComparison . OrdinalIgnoreCase ) ) ;
2014-08-13 10:39:27 +01:00
}
internal virtual HttpWebRequest CreateWebRequest ( HttpRequestMessage request )
{
var wr = new HttpWebRequest ( request . RequestUri ) ;
wr . ThrowOnError = false ;
2015-04-07 09:35:12 +01:00
wr . AllowWriteStreamBuffering = false ;
2014-08-13 10:39:27 +01:00
2019-04-12 14:10:50 +00:00
if ( request . Version = = HttpVersion . Version20 )
wr . ProtocolVersion = HttpVersion . Version11 ;
else
wr . ProtocolVersion = request . Version ;
2014-08-13 10:39:27 +01:00
wr . ConnectionGroupName = connectionGroupName ;
wr . Method = request . Method . Method ;
if ( wr . ProtocolVersion = = HttpVersion . Version10 ) {
2019-04-12 14:10:50 +00:00
wr . KeepAlive = GetConnectionKeepAlive ( request . Headers ) ;
2014-08-13 10:39:27 +01:00
} else {
wr . KeepAlive = request . Headers . ConnectionClose ! = true ;
}
if ( allowAutoRedirect ) {
wr . AllowAutoRedirect = true ;
wr . MaximumAutomaticRedirections = maxAutomaticRedirections ;
} else {
wr . AllowAutoRedirect = false ;
}
wr . AutomaticDecompression = automaticDecompression ;
wr . PreAuthenticate = preAuthenticate ;
if ( useCookies ) {
// It cannot be null or allowAutoRedirect won't work
wr . CookieContainer = CookieContainer ;
}
2019-04-12 14:10:50 +00:00
wr . Credentials = credentials ;
2014-08-13 10:39:27 +01:00
if ( useProxy ) {
wr . Proxy = proxy ;
2016-08-03 10:59:49 +00:00
} else {
// Disables default WebRequest.DefaultWebProxy value
wr . Proxy = null ;
2014-08-13 10:39:27 +01:00
}
2016-08-03 10:59:49 +00:00
wr . ServicePoint . Expect100Continue = request . Headers . ExpectContinue = = true ;
2014-08-13 10:39:27 +01:00
// Add request headers
var headers = wr . Headers ;
foreach ( var header in request . Headers ) {
2015-08-26 07:17:56 -04:00
var values = header . Value ;
2015-10-06 08:40:39 -04:00
if ( header . Key = = "Host" ) {
//
// Host must be explicitly set for HttpWebRequest
//
wr . Host = request . Headers . Host ;
continue ;
}
2015-08-26 07:17:56 -04:00
if ( header . Key = = "Transfer-Encoding" ) {
2017-06-07 13:16:24 +00:00
//
// Chunked Transfer-Encoding is set for HttpWebRequest later when Content length is checked
//
2015-08-26 07:17:56 -04:00
values = values . Where ( l = > l ! = "chunked" ) ;
2014-08-13 10:39:27 +01:00
}
2015-08-26 07:17:56 -04:00
2019-04-12 14:10:50 +00:00
var values_formated = HeaderUtils . GetSingleHeaderString ( header . Key , values ) ;
2015-08-26 07:17:56 -04:00
if ( values_formated = = null )
continue ;
2016-08-03 10:59:49 +00:00
headers . AddInternal ( header . Key , values_formated ) ;
2014-08-13 10:39:27 +01:00
}
2019-04-12 14:10:50 +00:00
2014-08-13 10:39:27 +01:00
return wr ;
}
HttpResponseMessage CreateResponseMessage ( HttpWebResponse wr , HttpRequestMessage requestMessage , CancellationToken cancellationToken )
{
var response = new HttpResponseMessage ( wr . StatusCode ) ;
response . RequestMessage = requestMessage ;
response . ReasonPhrase = wr . StatusDescription ;
2019-04-12 14:10:50 +00:00
#if LEGACY_HTTPCLIENT
2014-08-13 10:39:27 +01:00
response . Content = new StreamContent ( wr . GetResponseStream ( ) , cancellationToken ) ;
2019-04-12 14:10:50 +00:00
#else
response . Content = new StreamContent ( wr . GetResponseStream ( ) ) ;
#endif
2014-08-13 10:39:27 +01:00
var headers = wr . Headers ;
for ( int i = 0 ; i < headers . Count ; + + i ) {
2019-04-12 14:10:50 +00:00
var key = headers . GetKey ( i ) ;
2014-08-13 10:39:27 +01:00
var value = headers . GetValues ( i ) ;
HttpHeaders item_headers ;
2019-04-12 14:10:50 +00:00
if ( HeaderUtils . IsContentHeader ( key ) )
2014-08-13 10:39:27 +01:00
item_headers = response . Content . Headers ;
else
item_headers = response . Headers ;
2019-04-12 14:10:50 +00:00
2014-08-13 10:39:27 +01:00
item_headers . TryAddWithoutValidation ( key , value ) ;
}
2014-10-04 11:27:48 +01:00
requestMessage . RequestUri = wr . ResponseUri ;
2014-08-13 10:39:27 +01:00
return response ;
}
2017-11-28 19:36:51 +00:00
static bool MethodHasBody ( HttpMethod method )
{
switch ( method . Method ) {
case "HEAD" :
case "GET" :
case "MKCOL" :
case "CONNECT" :
case "TRACE" :
return false ;
default :
return true ;
}
}
2019-04-12 14:10:50 +00:00
public async Task < HttpResponseMessage > SendAsync ( HttpRequestMessage request , CancellationToken cancellationToken )
2014-08-13 10:39:27 +01:00
{
if ( disposed )
throw new ObjectDisposedException ( GetType ( ) . ToString ( ) ) ;
Volatile . Write ( ref sentRequest , true ) ;
2015-01-13 10:44:36 +00:00
var wrequest = CreateWebRequest ( request ) ;
2015-04-07 09:35:12 +01:00
HttpWebResponse wresponse = null ;
2014-08-13 10:39:27 +01:00
2015-04-07 09:35:12 +01:00
try {
using ( cancellationToken . Register ( l = > ( ( HttpWebRequest ) l ) . Abort ( ) , wrequest ) ) {
var content = request . Content ;
if ( content ! = null ) {
var headers = wrequest . Headers ;
foreach ( var header in content . Headers ) {
foreach ( var value in header . Value ) {
2016-08-03 10:59:49 +00:00
headers . AddInternal ( header . Key , value ) ;
2015-04-07 09:35:12 +01:00
}
}
2017-06-07 13:16:24 +00:00
if ( request . Headers . TransferEncodingChunked = = true ) {
wrequest . SendChunked = true ;
2015-04-07 09:35:12 +01:00
} else {
2017-06-07 13:16:24 +00:00
//
// Content length has to be set because HttpWebRequest is running without buffering
//
var contentLength = content . Headers . ContentLength ;
if ( contentLength ! = null ) {
wrequest . ContentLength = contentLength . Value ;
} else {
if ( MaxRequestContentBufferSize = = 0 )
throw new InvalidOperationException ( "The content length of the request content can't be determined. Either set TransferEncodingChunked to true, load content into buffer, or set MaxRequestContentBufferSize." ) ;
await content . LoadIntoBufferAsync ( MaxRequestContentBufferSize ) . ConfigureAwait ( false ) ;
wrequest . ContentLength = content . Headers . ContentLength . Value ;
}
2015-04-07 09:35:12 +01:00
}
2018-04-24 09:31:23 +00:00
wrequest . ResendContentFactory = content . CopyToAsync ;
2015-08-25 18:44:33 -04:00
2017-08-21 15:34:15 +00:00
using ( var stream = await wrequest . GetRequestStreamAsync ( ) . ConfigureAwait ( false ) ) {
await request . Content . CopyToAsync ( stream ) . ConfigureAwait ( false ) ;
}
2017-11-28 19:36:51 +00:00
} else if ( MethodHasBody ( request . Method ) ) {
2015-04-07 09:35:12 +01:00
// Explicitly set this to make sure we're sending a "Content-Length: 0" header.
// This fixes the issue that's been reported on the forums:
// http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
wrequest . ContentLength = 0 ;
2014-08-13 10:39:27 +01:00
}
2015-04-07 09:35:12 +01:00
wresponse = ( HttpWebResponse ) await wrequest . GetResponseAsync ( ) . ConfigureAwait ( false ) ;
2014-08-13 10:39:27 +01:00
}
2015-04-07 09:35:12 +01:00
} catch ( WebException we ) {
if ( we . Status ! = WebExceptionStatus . RequestCanceled )
2016-11-10 13:04:39 +00:00
throw new HttpRequestException ( "An error occurred while sending the request" , we ) ;
} catch ( System . IO . IOException ex ) {
throw new HttpRequestException ( "An error occurred while sending the request" , ex ) ;
2015-04-07 09:35:12 +01:00
}
2014-08-13 10:39:27 +01:00
2015-04-07 09:35:12 +01:00
if ( cancellationToken . IsCancellationRequested ) {
var cancelled = new TaskCompletionSource < HttpResponseMessage > ( ) ;
cancelled . SetCanceled ( ) ;
return await cancelled . Task ;
2014-08-13 10:39:27 +01:00
}
2016-08-23 13:20:38 +00:00
2019-04-12 14:10:50 +00:00
return CreateResponseMessage ( wresponse , request , cancellationToken ) ;
2016-08-23 13:20:38 +00:00
}
public ICredentials DefaultProxyCredentials {
get {
throw new NotImplementedException ( ) ;
}
set {
throw new NotImplementedException ( ) ;
}
}
public int MaxConnectionsPerServer {
get {
throw new NotImplementedException ( ) ;
}
set {
throw new NotImplementedException ( ) ;
}
}
2019-04-12 14:10:50 +00:00
public IDictionary < string , object > Properties {
2016-08-23 13:20:38 +00:00
get {
throw new NotImplementedException ( ) ;
}
}
2014-08-13 10:39:27 +01:00
}
}