Making sure mono_marshal_free is used instead of g_free in mono_string_builder_to_utf8
[mono.git] / mcs / class / System.Net.Http / CFNetworkHandler.cs
1 //
2 // CFNetworkHandler.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2013 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.Net.Http.Headers;
31 using System.Threading.Tasks;
32 using System.IO;
33 using System.Collections.Generic;
34 using System.Net;
35
36 #if XAMCORE_4_0
37 using CFNetwork;
38 using CoreFoundation;
39 using CF=CoreFoundation;
40 #elif XAMCORE_2_0
41 using CoreServices;
42 using CoreFoundation;
43 using CF=CoreFoundation;
44 #else
45 using MonoTouch.CoreServices;
46 using MonoTouch.CoreFoundation;
47 using CF=MonoTouch.CoreFoundation;
48 #endif
49
50 namespace System.Net.Http
51 {
52         public class CFNetworkHandler : HttpMessageHandler
53         {
54                 class StreamBucket
55                 {
56                         public TaskCompletionSource<HttpResponseMessage> Response;
57                         public HttpRequestMessage Request;
58                         public CancellationTokenRegistration CancellationTokenRegistration;
59                         public CFContentStream ContentStream;
60
61                         public void Close ()
62                         {
63                                 CancellationTokenRegistration.Dispose ();
64                                 ContentStream.Close ();
65                         }
66                 }
67
68                 bool allowAutoRedirect;
69                 bool sentRequest;
70                 bool useSystemProxy;
71                 CookieContainer cookies;
72
73                 Dictionary<IntPtr, StreamBucket> streamBuckets;
74
75                 public CFNetworkHandler ()
76                 {
77                         allowAutoRedirect = true;
78                         streamBuckets = new Dictionary<IntPtr, StreamBucket> ();
79                 }
80
81                 void EnsureModifiability ()
82                 {
83                         if (sentRequest)
84                                 throw new InvalidOperationException (
85                                         "This instance has already started one or more requests. " +
86                                         "Properties can only be modified before sending the first request.");
87                 }
88
89                 public bool AllowAutoRedirect {
90                         get {
91                                 return allowAutoRedirect;
92                         }
93                         set {
94                                 EnsureModifiability ();
95                                 allowAutoRedirect = value;
96                         }
97                 }
98
99                 public CookieContainer CookieContainer {
100                         get {
101                                 return cookies;
102                         }
103                         set {
104                                 EnsureModifiability ();
105                                 cookies = value;
106                         }
107                 }
108
109                 public bool UseSystemProxy {
110                         get {
111                                 return useSystemProxy;
112                         }
113                         set {
114                                 EnsureModifiability ();
115                                 useSystemProxy = value;
116                         }
117                 }
118
119                 // TODO: Add more properties
120
121                 protected override void Dispose (bool disposing)
122                 {
123                         // TODO: CloseStream remaining stream buckets if there are any
124
125                         base.Dispose (disposing);
126                 }
127
128                 CFHTTPMessage CreateWebRequestAsync (HttpRequestMessage request)
129                 {
130                         var req = CFHTTPMessage.CreateRequest (request.RequestUri, request.Method.Method, request.Version);
131
132                         // TODO:
133 /*
134                         if (wr.ProtocolVersion == HttpVersion.Version10) {
135                                 wr.KeepAlive = request.Headers.ConnectionKeepAlive;
136                         } else {
137                                 wr.KeepAlive = request.Headers.ConnectionClose != true;
138                         }
139
140                         if (useDefaultCredentials) {
141                                 wr.UseDefaultCredentials = true;
142                         } else {
143                                 wr.Credentials = credentials;
144                         }
145
146                         if (useProxy) {
147                                 wr.Proxy = proxy;
148                         }
149 */
150                         if (cookies != null) {
151                                 string cookieHeader = cookies.GetCookieHeader (request.RequestUri);
152                                 if (cookieHeader != "")
153                                         req.SetHeaderFieldValue ("Cookie", cookieHeader);
154                         }
155
156                         foreach (var header in request.Headers) {
157                                 foreach (var value in header.Value) {
158                                         req.SetHeaderFieldValue (header.Key, value);
159                                 }
160                         }
161
162                         if (request.Content != null) {
163                                 foreach (var header in request.Content.Headers) {
164                                         foreach (var value in header.Value) {
165                                                 req.SetHeaderFieldValue (header.Key, value);
166                                         }
167                                 }
168                         }
169
170                         return req;
171                 }
172
173                 protected internal override async Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
174                 {
175                         sentRequest = true;
176
177                         CFHTTPStream stream;
178                         using (var message = CreateWebRequestAsync (request))
179                         {
180                                 if (request.Content != null) {
181                                         var data = await request.Content.ReadAsByteArrayAsync ().ConfigureAwait (false);
182                                         message.SetBody (data);
183                                 }
184
185                                 stream = CFHTTPStream.CreateForHTTPRequest (message);
186                         }
187
188                         if (useSystemProxy) {
189                                 var proxies = CF.CFNetwork.GetSystemProxySettings ();
190                                 if (proxies.HTTPEnable) {
191                                         stream.SetProxy (proxies);
192                                 }
193                         }
194
195                         stream.ShouldAutoredirect = allowAutoRedirect;
196                         stream.HasBytesAvailableEvent += HandleHasBytesAvailableEvent;
197                         stream.ErrorEvent += HandleErrorEvent;
198                         stream.ClosedEvent += HandleClosedEvent;
199
200                         var response = new TaskCompletionSource<HttpResponseMessage> ();
201
202                         if (cancellationToken.IsCancellationRequested) {
203                                 response.SetCanceled ();
204                                 return await response.Task;
205                         }
206
207                         var bucket = new StreamBucket () {
208                                 Request = request,
209                                 Response = response,
210                         };
211
212                         streamBuckets.Add (stream.Handle, bucket);
213
214                         //
215                         // Always schedule stream events handling on main-loop. Due to ConfigureAwait (false) we may end up
216                         // on any thread-pool thread which may not have run-loop running
217                         //
218 #if XAMCORE_2_0
219                         stream.EnableEvents (CF.CFRunLoop.Main, CF.CFRunLoop.ModeCommon);
220 #else
221                         stream.EnableEvents (CF.CFRunLoop.Main, CF.CFRunLoop.CFRunLoopCommonModes);
222 #endif
223
224                         stream.Open ();
225
226                         bucket.CancellationTokenRegistration = cancellationToken.Register (() => {
227                                 StreamBucket bucket2;
228                                 if (!streamBuckets.TryGetValue (stream.Handle, out bucket2))
229                                         return;
230
231                                 bucket2.Response.TrySetCanceled ();
232                                 CloseStream (stream);
233                         });
234
235                         return await response.Task;
236                 }
237
238                 void HandleErrorEvent (object sender, CFStream.StreamEventArgs e)
239                 {
240                         var stream = (CFHTTPStream)sender;
241
242                         StreamBucket bucket;
243                         if (!streamBuckets.TryGetValue (stream.Handle, out bucket))
244                                 return;
245
246                         bucket.Response.TrySetException (stream.GetError ());
247                         CloseStream (stream);
248                 }
249
250                 void HandleClosedEvent (object sender, CFStream.StreamEventArgs e)
251                 {
252                         var stream = (CFHTTPStream)sender;
253                         CloseStream (stream);
254                 }
255
256                 void CloseStream (CFHTTPStream stream)
257                 {
258                         StreamBucket bucket;
259                         if (streamBuckets.TryGetValue (stream.Handle, out bucket)) {
260                                 bucket.Close ();
261                                 streamBuckets.Remove (stream.Handle);
262                         }
263
264                         stream.Close ();
265                 }
266
267                 void HandleHasBytesAvailableEvent (object sender, CFStream.StreamEventArgs e)
268                 {
269                         var stream = (CFHTTPStream) sender;
270
271                         StreamBucket bucket;
272                         if (!streamBuckets.TryGetValue (stream.Handle, out bucket))
273                                 return;
274
275                         if (bucket.Response.Task.IsCompleted) {
276                                 bucket.ContentStream.ReadStreamData ();
277                                 return;
278                         }
279
280                         var header = stream.GetResponseHeader ();
281
282                         // Is this possible?
283                         if (!header.IsHeaderComplete)
284                                 throw new NotImplementedException ();
285
286                         bucket.ContentStream = new CFContentStream (stream);
287                                 
288                         var response_msg = new HttpResponseMessage (header.ResponseStatusCode);
289                         response_msg.RequestMessage = bucket.Request;
290                         response_msg.ReasonPhrase = header.ResponseStatusLine;
291                         response_msg.Content = bucket.ContentStream;
292
293                         var fields = header.GetAllHeaderFields ();
294                         if (fields != null) {
295                                 foreach (var entry in fields) {
296                                         if (entry.Key == null)
297                                                 continue;
298
299                                         var key = entry.Key.ToString ();
300                                         var value = entry.Value == null ? string.Empty : entry.Value.ToString ();
301                                         HttpHeaders item_headers;
302                                         if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content) {
303                                                 item_headers = response_msg.Content.Headers;
304                                         } else {
305                                                 item_headers = response_msg.Headers;
306
307                                                 if (cookies != null && (key == "Set-Cookie" || key == "Set-Cookie2"))
308                                                         AddCookie (value, bucket.Request.RequestUri, key);
309                                         }
310
311                                         item_headers.TryAddWithoutValidation (key, value);
312                                 }
313                         }
314
315                         bucket.Response.TrySetResult (response_msg);
316
317                         bucket.ContentStream.ReadStreamData ();
318                 }
319
320                 void AddCookie (string value, Uri uri, string header)
321                 {
322                         CookieCollection cookies1 = null;
323                         try {
324                                 cookies1 = cookies.CookieCutter (uri, header, value, false);
325                         } catch {
326                         }
327
328                         if (cookies1 != null && cookies1.Count != 0) 
329                                 cookies.Add (cookies1);
330                 }
331         }
332 }