[System.IO.Compression] Fixed date time handling of Zip entries.
[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.Threading;
30 using System.Threading.Tasks;
31 using System.Collections.Specialized;
32 using System.Net.Http.Headers;
33 using System.Linq;
34
35 namespace System.Net.Http
36 {
37         public class HttpClientHandler : HttpMessageHandler
38         {
39                 static long groupCounter;
40
41                 bool allowAutoRedirect;
42                 DecompressionMethods automaticDecompression;
43                 CookieContainer cookieContainer;
44                 ICredentials credentials;
45                 int maxAutomaticRedirections;
46                 long maxRequestContentBufferSize;
47                 bool preAuthenticate;
48                 IWebProxy proxy;
49                 bool useCookies;
50                 bool useDefaultCredentials;
51                 bool useProxy;
52                 ClientCertificateOption certificate;
53                 bool sentRequest;
54                 string connectionGroupName;
55                 bool disposed;
56
57                 public HttpClientHandler ()
58                 {
59                         allowAutoRedirect = true;
60                         maxAutomaticRedirections = 50;
61                         maxRequestContentBufferSize = int.MaxValue;
62                         useCookies = true;
63                         useProxy = true;
64                         connectionGroupName = "HttpClientHandler" + Interlocked.Increment (ref groupCounter);
65                 }
66
67                 internal void EnsureModifiability ()
68                 {
69                         if (sentRequest)
70                                 throw new InvalidOperationException (
71                                         "This instance has already started one or more requests. " +
72                                         "Properties can only be modified before sending the first request.");
73                 }
74
75                 public bool AllowAutoRedirect {
76                         get {
77                                 return allowAutoRedirect;
78                         }
79                         set {
80                                 EnsureModifiability ();
81                                 allowAutoRedirect = value;
82                         }
83                 }
84
85                 public DecompressionMethods AutomaticDecompression {
86                         get {
87                                 return automaticDecompression;
88                         }
89                         set {
90                                 EnsureModifiability ();
91                                 automaticDecompression = value;
92                         }
93                 }
94
95                 public ClientCertificateOption ClientCertificateOptions {
96                         get {
97                                 return certificate;
98                         }
99                         set {
100                                 EnsureModifiability ();
101                                 certificate = value;
102                         }
103                 }
104
105                 public CookieContainer CookieContainer {
106                         get {
107                                 return cookieContainer ?? (cookieContainer = new CookieContainer ());
108                         }
109                         set {
110                                 EnsureModifiability ();
111                                 cookieContainer = value;
112                         }
113                 }
114
115                 public ICredentials Credentials {
116                         get {
117                                 return credentials;
118                         }
119                         set {
120                                 EnsureModifiability ();
121                                 credentials = value;
122                         }
123                 }
124
125                 public int MaxAutomaticRedirections {
126                         get {
127                                 return maxAutomaticRedirections;
128                         }
129                         set {
130                                 EnsureModifiability ();
131                                 if (value <= 0)
132                                         throw new ArgumentOutOfRangeException ();
133
134                                 maxAutomaticRedirections = value;
135                         }
136                 }
137
138                 public long MaxRequestContentBufferSize {
139                         get {
140                                 return maxRequestContentBufferSize;
141                         }
142                         set {
143                                 EnsureModifiability ();
144                                 if (value < 0)
145                                         throw new ArgumentOutOfRangeException ();
146
147                                 maxRequestContentBufferSize = value;
148                         }
149                 }
150
151                 public bool PreAuthenticate {
152                         get {
153                                 return preAuthenticate;
154                         }
155                         set {
156                                 EnsureModifiability ();
157                                 preAuthenticate = value;
158                         }
159                 }
160
161                 public IWebProxy Proxy {
162                         get {
163                                 return proxy;
164                         }
165                         set {
166                                 EnsureModifiability ();
167                                 if (!UseProxy)
168                                         throw new InvalidOperationException ();
169
170                                 proxy = value;
171                         }
172                 }
173
174                 public virtual bool SupportsAutomaticDecompression {
175                         get {
176                                 return true;
177                         }
178                 }
179
180                 public virtual bool SupportsProxy {
181                         get {
182                                 return true;
183                         }
184                 }
185
186                 public virtual bool SupportsRedirectConfiguration {
187                         get {
188                                 return true;
189                         }
190                 }
191
192                 public bool UseCookies {
193                         get {
194                                 return useCookies;
195                         }
196                         set {
197                                 EnsureModifiability ();
198                                 useCookies = value;
199                         }
200                 }
201
202                 public bool UseDefaultCredentials {
203                         get {
204                                 return useDefaultCredentials;
205                         }
206                         set {
207                                 EnsureModifiability ();
208                                 useDefaultCredentials = value;
209                         }
210                 }
211
212                 public bool UseProxy {
213                         get {
214                                 return useProxy;
215                         }
216                         set {
217                                 EnsureModifiability ();
218                                 useProxy = value;
219                         }
220                 }
221
222                 protected override void Dispose (bool disposing)
223                 {
224                         if (disposing && !disposed) {
225                                 Volatile.Write (ref disposed, true);
226                                 ServicePointManager.CloseConnectionGroup (connectionGroupName);
227                         }
228
229                         base.Dispose (disposing);
230                 }
231
232                 internal virtual HttpWebRequest CreateWebRequest (HttpRequestMessage request)
233                 {
234                         var wr = new HttpWebRequest (request.RequestUri);
235                         wr.ThrowOnError = false;
236                         wr.AllowWriteStreamBuffering = false;
237
238                         wr.ConnectionGroupName = connectionGroupName;
239                         wr.Method = request.Method.Method;
240                         wr.ProtocolVersion = request.Version;
241
242                         if (wr.ProtocolVersion == HttpVersion.Version10) {
243                                 wr.KeepAlive = request.Headers.ConnectionKeepAlive;
244                         } else {
245                                 wr.KeepAlive = request.Headers.ConnectionClose != true;
246                         }
247
248                         wr.ServicePoint.Expect100Continue = request.Headers.ExpectContinue == true;
249
250                         if (allowAutoRedirect) {
251                                 wr.AllowAutoRedirect = true;
252                                 wr.MaximumAutomaticRedirections = maxAutomaticRedirections;
253                         } else {
254                                 wr.AllowAutoRedirect = false;
255                         }
256
257                         wr.AutomaticDecompression = automaticDecompression;
258                         wr.PreAuthenticate = preAuthenticate;
259
260                         if (useCookies) {
261                                 // It cannot be null or allowAutoRedirect won't work
262                                 wr.CookieContainer = CookieContainer;
263                         }
264
265                         if (useDefaultCredentials) {
266                                 wr.UseDefaultCredentials = true;
267                         } else {
268                                 wr.Credentials = credentials;
269                         }
270
271                         if (useProxy) {
272                                 wr.Proxy = proxy;
273                         }
274
275                         // Add request headers
276                         var headers = wr.Headers;
277                         foreach (var header in request.Headers) {
278                                 var values = header.Value;
279                                 if (header.Key == "Host") {
280                                         //
281                                         // Host must be explicitly set for HttpWebRequest
282                                         //
283                                         wr.Host = request.Headers.Host;
284                                         continue;
285                                 }
286
287                                 if (header.Key == "Transfer-Encoding") {
288                                         // Chunked Transfer-Encoding is never set for HttpWebRequest. It's detected
289                                         // from ContentLength by HttpWebRequest
290                                         values = values.Where (l => l != "chunked");
291                                 }
292
293                                 var values_formated = HttpRequestHeaders.GetSingleHeaderString (header.Key, values);
294                                 if (values_formated == null)
295                                         continue;
296
297                                 headers.AddInternal (header.Key, values_formated);
298                         }
299                         
300                         return wr;
301                 }
302
303                 HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
304                 {
305                         var response = new HttpResponseMessage (wr.StatusCode);
306                         response.RequestMessage = requestMessage;
307                         response.ReasonPhrase = wr.StatusDescription;
308                         response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
309
310                         var headers = wr.Headers;
311                         for (int i = 0; i < headers.Count; ++i) {
312                                 var key = headers.GetKey(i);
313                                 var value = headers.GetValues (i);
314
315                                 HttpHeaders item_headers;
316                                 if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content)
317                                         item_headers = response.Content.Headers;
318                                 else
319                                         item_headers = response.Headers;
320                                         
321                                 item_headers.TryAddWithoutValidation (key, value);
322                         }
323
324                         requestMessage.RequestUri = wr.ResponseUri;
325
326                         return response;
327                 }
328
329                 protected async internal override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
330                 {
331                         if (disposed)
332                                 throw new ObjectDisposedException (GetType ().ToString ());
333
334                         Volatile.Write (ref sentRequest, true);
335                         var wrequest = CreateWebRequest (request);
336                         HttpWebResponse wresponse = null;
337
338                         try {
339                                 using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
340                                         var content = request.Content;
341                                         if (content != null) {
342                                                 var headers = wrequest.Headers;
343
344                                                 foreach (var header in content.Headers) {
345                                                         foreach (var value in header.Value) {
346                                                                 headers.AddInternal (header.Key, value);
347                                                         }
348                                                 }
349
350                                                 //
351                                                 // Content length has to be set because HttpWebRequest is running without buffering
352                                                 //
353                                                 var contentLength = content.Headers.ContentLength;
354                                                 if (contentLength != null) {
355                                                         wrequest.ContentLength = contentLength.Value;
356                                                 } else {
357                                                         await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
358                                                         wrequest.ContentLength = content.Headers.ContentLength.Value;
359                                                 }
360
361                                                 wrequest.ResendContentFactory = content.CopyTo;
362
363                                                 var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false);
364                                                 await request.Content.CopyToAsync (stream).ConfigureAwait (false);
365                                         } else if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) {
366                                                 // Explicitly set this to make sure we're sending a "Content-Length: 0" header.
367                                                 // This fixes the issue that's been reported on the forums:
368                                                 // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
369                                                 wrequest.ContentLength = 0;
370                                         }
371
372                                         wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
373                                 }
374                         } catch (WebException we) {
375                                 if (we.Status != WebExceptionStatus.RequestCanceled)
376                                         throw;
377                         }
378
379                         if (cancellationToken.IsCancellationRequested) {
380                                 var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
381                                 cancelled.SetCanceled ();
382                                 return await cancelled.Task;
383                         }
384                         
385                         return CreateResponseMessage (wresponse, request, cancellationToken);
386                 }
387         }
388 }