[threads] Don't ignore abort requests in abort protected blocks
[mono.git] / mcs / class / System.Net.Http / System.Net.Http / HttpClientHandler.cs
1 //
2 // HttpClientHandler.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System.Collections.Generic;
30 using System.Security.Authentication;
31 using System.Security.Cryptography.X509Certificates;
32 using System.Threading;
33 using System.Threading.Tasks;
34 using System.Collections.Specialized;
35 using System.Net.Http.Headers;
36 using System.Net.Security;
37 using System.Linq;
38
39 namespace System.Net.Http
40 {
41         public class HttpClientHandler : HttpMessageHandler
42         {
43                 static long groupCounter;
44
45                 bool allowAutoRedirect;
46                 DecompressionMethods automaticDecompression;
47                 CookieContainer cookieContainer;
48                 ICredentials credentials;
49                 int maxAutomaticRedirections;
50                 long maxRequestContentBufferSize;
51                 bool preAuthenticate;
52                 IWebProxy proxy;
53                 bool useCookies;
54                 bool useDefaultCredentials;
55                 bool useProxy;
56                 ClientCertificateOption certificate;
57                 bool sentRequest;
58                 string connectionGroupName;
59                 bool disposed;
60
61                 public HttpClientHandler ()
62                 {
63                         allowAutoRedirect = true;
64                         maxAutomaticRedirections = 50;
65                         maxRequestContentBufferSize = int.MaxValue;
66                         useCookies = true;
67                         useProxy = true;
68                         connectionGroupName = "HttpClientHandler" + Interlocked.Increment (ref groupCounter);
69                 }
70
71                 internal void EnsureModifiability ()
72                 {
73                         if (sentRequest)
74                                 throw new InvalidOperationException (
75                                         "This instance has already started one or more requests. " +
76                                         "Properties can only be modified before sending the first request.");
77                 }
78
79                 public bool AllowAutoRedirect {
80                         get {
81                                 return allowAutoRedirect;
82                         }
83                         set {
84                                 EnsureModifiability ();
85                                 allowAutoRedirect = value;
86                         }
87                 }
88
89                 public DecompressionMethods AutomaticDecompression {
90                         get {
91                                 return automaticDecompression;
92                         }
93                         set {
94                                 EnsureModifiability ();
95                                 automaticDecompression = value;
96                         }
97                 }
98
99                 public ClientCertificateOption ClientCertificateOptions {
100                         get {
101                                 return certificate;
102                         }
103                         set {
104                                 EnsureModifiability ();
105                                 certificate = value;
106                         }
107                 }
108
109                 public CookieContainer CookieContainer {
110                         get {
111                                 return cookieContainer ?? (cookieContainer = new CookieContainer ());
112                         }
113                         set {
114                                 EnsureModifiability ();
115                                 cookieContainer = value;
116                         }
117                 }
118
119                 public ICredentials Credentials {
120                         get {
121                                 return credentials;
122                         }
123                         set {
124                                 EnsureModifiability ();
125                                 credentials = value;
126                         }
127                 }
128
129                 public int MaxAutomaticRedirections {
130                         get {
131                                 return maxAutomaticRedirections;
132                         }
133                         set {
134                                 EnsureModifiability ();
135                                 if (value <= 0)
136                                         throw new ArgumentOutOfRangeException ();
137
138                                 maxAutomaticRedirections = value;
139                         }
140                 }
141
142                 public long MaxRequestContentBufferSize {
143                         get {
144                                 return maxRequestContentBufferSize;
145                         }
146                         set {
147                                 EnsureModifiability ();
148                                 if (value < 0)
149                                         throw new ArgumentOutOfRangeException ();
150
151                                 maxRequestContentBufferSize = value;
152                         }
153                 }
154
155                 public bool PreAuthenticate {
156                         get {
157                                 return preAuthenticate;
158                         }
159                         set {
160                                 EnsureModifiability ();
161                                 preAuthenticate = value;
162                         }
163                 }
164
165                 public IWebProxy Proxy {
166                         get {
167                                 return proxy;
168                         }
169                         set {
170                                 EnsureModifiability ();
171                                 if (!UseProxy)
172                                         throw new InvalidOperationException ();
173
174                                 proxy = value;
175                         }
176                 }
177
178                 public virtual bool SupportsAutomaticDecompression {
179                         get {
180                                 return true;
181                         }
182                 }
183
184                 public virtual bool SupportsProxy {
185                         get {
186                                 return true;
187                         }
188                 }
189
190                 public virtual bool SupportsRedirectConfiguration {
191                         get {
192                                 return true;
193                         }
194                 }
195
196                 public bool UseCookies {
197                         get {
198                                 return useCookies;
199                         }
200                         set {
201                                 EnsureModifiability ();
202                                 useCookies = value;
203                         }
204                 }
205
206                 public bool UseDefaultCredentials {
207                         get {
208                                 return useDefaultCredentials;
209                         }
210                         set {
211                                 EnsureModifiability ();
212                                 useDefaultCredentials = value;
213                         }
214                 }
215
216                 public bool UseProxy {
217                         get {
218                                 return useProxy;
219                         }
220                         set {
221                                 EnsureModifiability ();
222                                 useProxy = value;
223                         }
224                 }
225
226                 protected override void Dispose (bool disposing)
227                 {
228                         if (disposing && !disposed) {
229                                 Volatile.Write (ref disposed, true);
230                                 ServicePointManager.CloseConnectionGroup (connectionGroupName);
231                         }
232
233                         base.Dispose (disposing);
234                 }
235
236                 internal virtual HttpWebRequest CreateWebRequest (HttpRequestMessage request)
237                 {
238                         var wr = new HttpWebRequest (request.RequestUri);
239                         wr.ThrowOnError = false;
240                         wr.AllowWriteStreamBuffering = false;
241
242                         wr.ConnectionGroupName = connectionGroupName;
243                         wr.Method = request.Method.Method;
244                         wr.ProtocolVersion = request.Version;
245
246                         if (wr.ProtocolVersion == HttpVersion.Version10) {
247                                 wr.KeepAlive = request.Headers.ConnectionKeepAlive;
248                         } else {
249                                 wr.KeepAlive = request.Headers.ConnectionClose != true;
250                         }
251
252                         if (allowAutoRedirect) {
253                                 wr.AllowAutoRedirect = true;
254                                 wr.MaximumAutomaticRedirections = maxAutomaticRedirections;
255                         } else {
256                                 wr.AllowAutoRedirect = false;
257                         }
258
259                         wr.AutomaticDecompression = automaticDecompression;
260                         wr.PreAuthenticate = preAuthenticate;
261
262                         if (useCookies) {
263                                 // It cannot be null or allowAutoRedirect won't work
264                                 wr.CookieContainer = CookieContainer;
265                         }
266
267                         if (useDefaultCredentials) {
268                                 wr.UseDefaultCredentials = true;
269                         } else {
270                                 wr.Credentials = credentials;
271                         }
272
273                         if (useProxy) {
274                                 wr.Proxy = proxy;
275                         } else {
276                                 // Disables default WebRequest.DefaultWebProxy value
277                                 wr.Proxy = null;
278                         }
279
280                         wr.ServicePoint.Expect100Continue = request.Headers.ExpectContinue == true;
281
282                         // Add request headers
283                         var headers = wr.Headers;
284                         foreach (var header in request.Headers) {
285                                 var values = header.Value;
286                                 if (header.Key == "Host") {
287                                         //
288                                         // Host must be explicitly set for HttpWebRequest
289                                         //
290                                         wr.Host = request.Headers.Host;
291                                         continue;
292                                 }
293
294                                 if (header.Key == "Transfer-Encoding") {
295                                         // Chunked Transfer-Encoding is never set for HttpWebRequest. It's detected
296                                         // from ContentLength by HttpWebRequest
297                                         values = values.Where (l => l != "chunked");
298                                 }
299
300                                 var values_formated = HttpRequestHeaders.GetSingleHeaderString (header.Key, values);
301                                 if (values_formated == null)
302                                         continue;
303
304                                 headers.AddInternal (header.Key, values_formated);
305                         }
306                         
307                         return wr;
308                 }
309
310                 HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
311                 {
312                         var response = new HttpResponseMessage (wr.StatusCode);
313                         response.RequestMessage = requestMessage;
314                         response.ReasonPhrase = wr.StatusDescription;
315                         response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
316
317                         var headers = wr.Headers;
318                         for (int i = 0; i < headers.Count; ++i) {
319                                 var key = headers.GetKey(i);
320                                 var value = headers.GetValues (i);
321
322                                 HttpHeaders item_headers;
323                                 if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content)
324                                         item_headers = response.Content.Headers;
325                                 else
326                                         item_headers = response.Headers;
327                                         
328                                 item_headers.TryAddWithoutValidation (key, value);
329                         }
330
331                         requestMessage.RequestUri = wr.ResponseUri;
332
333                         return response;
334                 }
335
336                 protected async internal override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
337                 {
338                         if (disposed)
339                                 throw new ObjectDisposedException (GetType ().ToString ());
340
341                         Volatile.Write (ref sentRequest, true);
342                         var wrequest = CreateWebRequest (request);
343                         HttpWebResponse wresponse = null;
344
345                         try {
346                                 using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
347                                         var content = request.Content;
348                                         if (content != null) {
349                                                 var headers = wrequest.Headers;
350
351                                                 foreach (var header in content.Headers) {
352                                                         foreach (var value in header.Value) {
353                                                                 headers.AddInternal (header.Key, value);
354                                                         }
355                                                 }
356
357                                                 //
358                                                 // Content length has to be set because HttpWebRequest is running without buffering
359                                                 //
360                                                 var contentLength = content.Headers.ContentLength;
361                                                 if (contentLength != null) {
362                                                         wrequest.ContentLength = contentLength.Value;
363                                                 } else {
364                                                         await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
365                                                         wrequest.ContentLength = content.Headers.ContentLength.Value;
366                                                 }
367
368                                                 wrequest.ResendContentFactory = content.CopyTo;
369
370                                                 var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false);
371                                                 await request.Content.CopyToAsync (stream).ConfigureAwait (false);
372                                         } else if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) {
373                                                 // Explicitly set this to make sure we're sending a "Content-Length: 0" header.
374                                                 // This fixes the issue that's been reported on the forums:
375                                                 // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
376                                                 wrequest.ContentLength = 0;
377                                         }
378
379                                         wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
380                                 }
381                         } catch (WebException we) {
382                                 if (we.Status != WebExceptionStatus.RequestCanceled)
383                                         throw new HttpRequestException ("An error occurred while sending the request", we);
384                         } catch (System.IO.IOException ex) {
385                                 throw new HttpRequestException ("An error occurred while sending the request", ex);
386                         }
387
388                         if (cancellationToken.IsCancellationRequested) {
389                                 var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
390                                 cancelled.SetCanceled ();
391                                 return await cancelled.Task;
392                         }
393                         
394                         return CreateResponseMessage (wresponse, request, cancellationToken);
395                 }
396
397 #if NETSTANDARD
398                 public bool CheckCertificateRevocationList {
399                         get {
400                                 throw new NotImplementedException ();
401                         }
402                         set {
403                                 throw new NotImplementedException ();
404                         }
405                 }
406
407                 public X509CertificateCollection ClientCertificates {
408                         get {
409                                 throw new NotImplementedException ();
410                         }
411                 }
412
413                 public ICredentials DefaultProxyCredentials {
414                         get {
415                                 throw new NotImplementedException ();
416                         }
417                         set {
418                                 throw new NotImplementedException ();
419                         }
420                 }
421
422                 public int MaxConnectionsPerServer {
423                         get {
424                                 throw new NotImplementedException ();
425                         }
426                         set {
427                                 throw new NotImplementedException ();
428                         }
429                 }
430
431                 public int MaxResponseHeadersLength {
432                         get {
433                                 throw new NotImplementedException ();
434                         }
435                         set {
436                                 throw new NotImplementedException ();
437                         }
438                 }
439
440                 public IDictionary<string,object> Properties {
441                         get {
442                                 throw new NotImplementedException ();
443                         }
444                 }
445
446                 public Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,bool> ServerCertificateCustomValidationCallback {
447                         get {
448                                 throw new NotImplementedException ();
449                         }
450                         set {
451                                 throw new NotImplementedException ();
452                         }
453                 }
454
455                 public SslProtocols SslProtocols {
456                         get {
457                                 throw new NotImplementedException ();
458                         }
459                         set {
460                                 throw new NotImplementedException ();
461                         }
462                 }
463
464 #endif
465         }
466 }