Revert "[runtime] Use pthread_cond_timedwait_relative_np () function on osx if availa...
[mono.git] / mcs / class / System.Net / System.Net / WebClient_2_1.cs
1 //
2 // System.Net.WebClient
3 //
4 // Authors:
5 //      Lawrence Pit (loz@cable.a2000.nl)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //      Atsushi Enomoto (atsushi@ximian.com)
8 //      Miguel de Icaza (miguel@ximian.com)
9 //      Stephane Delcroix (sdelcroix@novell.com)
10 //
11 // Copyright 2003 Ximian, Inc. (http://www.ximian.com)
12 // Copyright 2006, 2008, 2009-2010 Novell, Inc. (http://www.novell.com)
13 //
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System.IO;
36 using System.Security;
37 using System.Text;
38 using System.Threading;
39
40 namespace System.Net {
41
42         // note: this type is effectively sealed to transparent code since it's default .ctor is marked with [SecuritySafeCritical]
43         public class WebClient {
44
45                 WebHeaderCollection headers;
46                 WebHeaderCollection responseHeaders;
47                 bool response_supports_headers = true;  // will be set to false, if needed, when we get a response
48                 string baseAddress;
49                 Uri base_address_uri;
50                 bool is_busy;
51                 Encoding encoding = Encoding.UTF8;
52                 bool allow_read_buffering = true;
53                 bool allow_write_buffering = true;
54                 WebRequest request;
55                 object locker;
56                 CallbackData callback_data;
57                 long upload_length;
58
59                 public WebClient ()
60                 {
61                         // kind of calling NativeMethods.plugin_instance_get_source_location (PluginHost.Handle)
62                         // but without adding dependency on System.Windows.dll. GetData is [SecurityCritical]
63                         // this makes the default .ctor [SecuritySafeCritical] which would be a problem (inheritance)
64                         // but it happens that MS SL2 also has this default .ctor as SSC :-)
65                         BaseAddress = (AppDomain.CurrentDomain.GetData ("xap_uri") as string);
66                         locker = new object ();
67                         UseDefaultCredentials = true;
68                 }
69                 
70                 // Properties
71                 
72                 public string BaseAddress {
73                         get { return baseAddress; }
74                         set {
75                                 if (String.IsNullOrEmpty (value)) {
76                                         baseAddress = String.Empty;
77                                         base_address_uri = null;
78                                 } else {
79                                         if (!Uri.TryCreate (value, UriKind.Absolute, out base_address_uri))
80                                                 throw new ArgumentException ("Invalid URI");
81
82                                         baseAddress = Uri.UnescapeDataString (base_address_uri.AbsoluteUri);
83                                 }
84                         }
85                 }
86
87                 [MonoTODO ("provide credentials to the client stack")]
88                 public ICredentials Credentials { get; set; }
89
90                 // this is an unvalidated collection, HttpWebRequest is responsable to validate it
91                 public WebHeaderCollection Headers {
92                         get {
93                                 if (headers == null)
94                                         headers = new WebHeaderCollection ();
95
96                                 return headers;
97                         }
98                         set { headers = value; }
99                 }
100
101                 public WebHeaderCollection ResponseHeaders {
102                         get {
103                                 if (!response_supports_headers)
104                                         throw new NotImplementedException ();
105                                 return responseHeaders;
106                         }
107                 }
108
109                 public Encoding Encoding {
110                         get { return encoding; }
111                         set {
112                                 if (value == null)
113                                         throw new ArgumentNullException ("value");
114                                 encoding = value;
115                         }
116                 }
117
118                 public bool IsBusy {
119                         get { return is_busy; }
120                 }
121
122                 [MonoTODO ("value is unused, current implementation always works like it's true (default)")]
123                 public bool AllowReadStreamBuffering {
124                         get { return allow_read_buffering; }
125                         set { allow_read_buffering = value; }
126                 }
127
128                 // new in SL4 RC
129                 [MonoTODO ("value is unused, current implementation always works like it's true (default)")]
130                 public bool AllowWriteStreamBuffering {
131                         get { return allow_write_buffering; }
132                         set { allow_write_buffering = value; }
133                 }
134
135                 public bool UseDefaultCredentials {
136                         get; set;
137                 }
138
139                 // Methods
140
141                 void CheckBusy ()
142                 {
143                         if (IsBusy)
144                                 throw new NotSupportedException ("WebClient does not support concurrent I/O operations.");
145                 }
146
147                 void SetBusy ()
148                 {
149                         lock (locker) {
150                                 CheckBusy ();
151                                 is_busy = true;
152                         }
153                 }
154
155                 private string DetermineMethod (Uri address, string method)
156                 {
157                         if (method != null)
158                                 return method;
159
160                         if (address.Scheme == Uri.UriSchemeFtp)
161                                 return "RETR";
162                         return "POST";
163                 }
164
165                 public event DownloadProgressChangedEventHandler DownloadProgressChanged;
166                 public event DownloadStringCompletedEventHandler DownloadStringCompleted;
167                 public event OpenReadCompletedEventHandler OpenReadCompleted;
168                 public event OpenWriteCompletedEventHandler OpenWriteCompleted;
169                 public event UploadProgressChangedEventHandler UploadProgressChanged;
170                 public event UploadStringCompletedEventHandler UploadStringCompleted;
171                 public event WriteStreamClosedEventHandler WriteStreamClosed;
172
173                 WebRequest SetupRequest (Uri uri, string method, CallbackData callbackData)
174                 {
175                         callback_data = callbackData;
176                         WebRequest request = GetWebRequest (uri);
177                         // do not send a relative URI to Determine method
178                         request.Method = DetermineMethod (request.RequestUri, method);
179                         // copy headers to the request - some needs special treatments
180                         foreach (string header in Headers) {
181                                 switch (header.ToLowerInvariant ()) {
182                                 case "content-length":
183                                         long cl = 0;
184                                         if (Int64.TryParse (Headers [header], out cl) && (cl >= 0))
185                                                 request.ContentLength = cl;
186                                         break;
187                                 case "accept":
188                                 case "content-type":
189                                         // this skip the normal/user validation on headers
190                                         request.Headers.SetHeader (header, Headers [header]);
191                                         break;
192                                 default:
193                                         request.Headers [header] = Headers [header];
194                                         break;
195                                 }
196                         }
197                         // original headers are removed after calls
198                         Headers.Clear ();
199                         return request;
200                 }
201
202                 Stream ProcessResponse (WebResponse response)
203                 {
204                         response_supports_headers = response.SupportsHeaders;
205                         if (response_supports_headers)
206                                 responseHeaders =  response.Headers;
207
208                         HttpWebResponse hwr = (response as HttpWebResponse);
209                         if (hwr == null)
210                                 throw new NotSupportedException ();
211
212                         HttpStatusCode status_code = HttpStatusCode.NotFound;
213                         Stream s = null;
214                         try {
215                                 status_code = hwr.StatusCode;
216                                 if (status_code == HttpStatusCode.OK)
217                                         s = response.GetResponseStream ();
218                         }
219                         catch (Exception e) {
220                                 throw new WebException ("NotFound", status_code == HttpStatusCode.OK ? e : null, 
221                                         WebExceptionStatus.UnknownError, response);
222                         }
223                         return s;
224                 }
225
226                 public void CancelAsync ()
227                 {
228                         if (request != null)
229                                 request.Abort ();
230                         upload_length = 0;
231                 }
232
233                 void CompleteAsync ()
234                 {
235                         is_busy = false;
236                         upload_length = 0;
237                 }
238
239                 internal class CallbackData {
240                         public object user_token;
241                         public SynchronizationContext sync_context;
242                         public byte [] data;
243                         public CallbackData (object user_token, byte [] data)
244                         {
245                                 this.user_token = user_token;
246                                 this.data = data;
247                                 this.sync_context = SynchronizationContext.Current ?? new SynchronizationContext ();
248                         }
249                         public CallbackData (object user_token) : this (user_token, null)
250                         {
251                         }
252                 }
253
254                 //    DownloadStringAsync
255
256                 public void DownloadStringAsync (Uri address)
257                 {
258                         DownloadStringAsync (address, null);
259                 }
260
261                 public void DownloadStringAsync (Uri address, object userToken)
262                 {
263                         if (address == null)
264                                 throw new ArgumentNullException ("address");
265
266                         lock (locker) {
267                                 SetBusy ();
268
269                                 try {
270                                         request = SetupRequest (address, "GET", new CallbackData (userToken));
271                                         request.BeginGetResponse (new AsyncCallback (DownloadStringAsyncCallback), null);
272                                 }
273                                 catch (Exception e) {
274                                         WebException wex = new WebException ("Could not start operation.", e);
275                                         OnDownloadStringCompleted (
276                                                 new DownloadStringCompletedEventArgs (null, wex, false, userToken));
277                                 }
278                         }
279                 }
280
281                 private void DownloadStringAsyncCallback (IAsyncResult result)
282                 {
283                         string data = null;
284                         Exception ex = null;
285                         bool cancel = false;
286                         try {
287                                 WebResponse response = request.EndGetResponse (result);
288                                 Stream stream = ProcessResponse (response);
289
290                                 using (StreamReader sr = new StreamReader (stream, encoding, true)) {
291                                         data = sr.ReadToEnd ();
292                                 }
293                         }
294                         catch (WebException web) {
295                                 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
296                                 ex = web;
297                         }
298                         catch (SecurityException se) {
299                                 // SecurityException inside a SecurityException (not a WebException) for SL compatibility
300                                 ex = new SecurityException (String.Empty, se);
301                         }
302                         catch (Exception e) {
303                                 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
304                         }
305                         finally {
306                                 callback_data.sync_context.Post (delegate (object sender) {
307                                         OnDownloadStringCompleted (new DownloadStringCompletedEventArgs (data, ex, cancel, callback_data.user_token));
308                                 }, null);
309                         }
310                 }
311
312                 //    OpenReadAsync
313
314                 public void OpenReadAsync (Uri address)
315                 {
316                         OpenReadAsync (address, null);
317                 }
318
319                 public void OpenReadAsync (Uri address, object userToken)
320                 {
321                         if (address == null)
322                                 throw new ArgumentNullException ("address");
323
324                         lock (locker) {
325                                 SetBusy ();
326
327                                 try {
328                                         request = SetupRequest (address, "GET", new CallbackData (userToken));
329                                         request.BeginGetResponse (new AsyncCallback (OpenReadAsyncCallback), null);
330                                 }
331                                 catch (Exception e) {
332                                         WebException wex = new WebException ("Could not start operation.", e);
333                                         OnOpenReadCompleted (
334                                                 new OpenReadCompletedEventArgs (null, wex, false, userToken));
335                                 }
336                         }
337                 }
338
339                 private void OpenReadAsyncCallback (IAsyncResult result)
340                 {
341                         Stream stream = null;
342                         Exception ex = null;
343                         bool cancel = false;
344                         try {
345                                 WebResponse response = request.EndGetResponse (result);
346                                 stream = ProcessResponse (response);
347                         }
348                         catch (WebException web) {
349                                 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
350                                 ex = web;
351                         }
352                         catch (SecurityException se) {
353                                 // SecurityException inside a SecurityException (not a WebException) for SL compatibility
354                                 ex = new SecurityException (String.Empty, se);
355                         }
356                         catch (Exception e) {
357                                 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
358                         }
359                         finally {
360                                 callback_data.sync_context.Post (delegate (object sender) {
361                                         OnOpenReadCompleted (new OpenReadCompletedEventArgs (stream, ex, cancel, callback_data.user_token));
362                                 }, null);
363                         }
364                 }
365
366                 //    OpenWriteAsync
367
368                 public void OpenWriteAsync (Uri address)
369                 {
370                         OpenWriteAsync (address, null);
371                 }
372
373                 public void OpenWriteAsync (Uri address, string method)
374                 {
375                         OpenWriteAsync (address, method, null);
376                 }
377
378                 public void OpenWriteAsync (Uri address, string method, object userToken)
379                 {
380                         if (address == null)
381                                 throw new ArgumentNullException ("address");
382
383                         lock (locker) {
384                                 SetBusy ();
385
386                                 try {
387                                         request = SetupRequest (address, method, new CallbackData (userToken));
388                                         request.BeginGetRequestStream (new AsyncCallback (OpenWriteAsyncCallback), null);
389                                 }
390                                 catch (Exception e) {
391                                         WebException wex = new WebException ("Could not start operation.", e);
392                                         OnOpenWriteCompleted (
393                                                 new OpenWriteCompletedEventArgs (null, wex, false, userToken));
394                                 }
395                         }
396                 }
397
398                 private void OpenWriteAsyncCallback (IAsyncResult result)
399                 {
400                         Stream stream = null;
401                         Exception ex = null;
402                         bool cancel = false;
403                         InternalWebRequestStreamWrapper internal_stream;
404
405                         try {
406                                 stream = request.EndGetRequestStream (result);
407                                 internal_stream = (InternalWebRequestStreamWrapper) stream;
408                                 internal_stream.WebClient = this;
409                                 internal_stream.WebClientData = callback_data;
410                         }
411                         catch (WebException web) {
412                                 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
413                                 ex = web;
414                         }
415                         catch (Exception e) {
416                                 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
417                         }
418                         finally {
419                                 callback_data.sync_context.Post (delegate (object sender) {
420                                         OnOpenWriteCompleted (new OpenWriteCompletedEventArgs (stream, ex, cancel, callback_data.user_token));
421                                 }, null);
422                         }
423                 }
424
425                 internal void WriteStreamClosedCallback (object WebClientData, long length)
426                 {
427                         try {
428                                 request.BeginGetResponse (OpenWriteAsyncResponseCallback, WebClientData);
429                         }
430                         catch (Exception e) {
431                                 callback_data.sync_context.Post (delegate (object sender) {
432                                         OnWriteStreamClosed (new WriteStreamClosedEventArgs (e));
433                                 }, null);
434                         }
435                         finally {
436                                 // kind of dummy, 0% progress, that is always emitted
437                                 upload_length = length;
438                                 callback_data.sync_context.Post (delegate (object sender) {
439                                         OnUploadProgressChanged (
440                                                 new UploadProgressChangedEventArgs (0, -1, length, -1, 0, callback_data.user_token));
441                                 }, null);
442                         }
443                 }
444
445                 private void OpenWriteAsyncResponseCallback (IAsyncResult result)
446                 {
447                         Exception ex = null;
448                         try {
449                                 WebResponse response = request.EndGetResponse (result);
450                                 ProcessResponse (response);
451                         }
452                         catch (SecurityException se) {
453                                 // SecurityException inside a SecurityException (not a WebException) for SL compatibility
454                                 ex = new SecurityException (String.Empty, se);
455                         }
456                         catch (Exception e) {
457                                 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
458                         }
459                         finally {
460                                 callback_data.sync_context.Post (delegate (object sender) {
461                                         OnWriteStreamClosed (new WriteStreamClosedEventArgs (ex));
462                                 }, null);
463                         }
464                 }
465
466                 //    UploadStringAsync
467
468                 public void UploadStringAsync (Uri address, string data)
469                 {
470                         UploadStringAsync (address, null, data);
471                 }
472
473                 public void UploadStringAsync (Uri address, string method, string data)
474                 {
475                         UploadStringAsync (address, method, data, null);
476                 }
477
478                 public void UploadStringAsync (Uri address, string method, string data, object userToken)
479                 {
480                         if (address == null)
481                                 throw new ArgumentNullException ("address");
482                         if (data == null)
483                                 throw new ArgumentNullException ("data");
484
485                         lock (locker) {
486                                 SetBusy ();
487
488                                 try {
489                                         CallbackData cbd = new CallbackData (userToken, encoding.GetBytes (data));
490                                         request = SetupRequest (address, method, cbd);
491                                         request.BeginGetRequestStream (new AsyncCallback (UploadStringRequestAsyncCallback), cbd);
492                                 }
493                                 catch (Exception e) {
494                                         WebException wex = new WebException ("Could not start operation.", e);
495                                         OnUploadStringCompleted (
496                                                 new UploadStringCompletedEventArgs (null, wex, false, userToken));
497                                 }
498                         }
499                 }
500
501                 private void UploadStringRequestAsyncCallback (IAsyncResult result)
502                 {
503                         try {
504                                 Stream stream = request.EndGetRequestStream (result);
505                                 stream.Write (callback_data.data, 0, callback_data.data.Length);
506                                 request.BeginGetResponse (new AsyncCallback (UploadStringResponseAsyncCallback), null);
507                         }
508                         catch {
509                                 request.Abort ();
510                                 throw;
511                         }
512                         finally {
513                                 // kind of dummy, 0% progress, that is always emitted
514                                 upload_length = callback_data.data.Length;
515                                 callback_data.sync_context.Post (delegate (object sender) {
516                                         OnUploadProgressChanged (
517                                                 new UploadProgressChangedEventArgs (0, -1, upload_length, -1, 0, callback_data.user_token));
518                                 }, "null");             
519                         }
520                 }
521
522                 private void UploadStringResponseAsyncCallback (IAsyncResult result)
523                 {
524                         string data = null;
525                         Exception ex = null;
526                         bool cancel = false;
527                         try {
528                                 WebResponse response = request.EndGetResponse (result);
529                                 Stream stream = ProcessResponse (response);
530
531                                 using (StreamReader sr = new StreamReader (stream, encoding, true)) {
532                                         data = sr.ReadToEnd ();
533                                 }
534                         }
535                         catch (WebException web) {
536                                 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
537                                 ex = web;
538                         }
539                         catch (SecurityException se) {
540                                 // SecurityException inside a SecurityException (not a WebException) for SL compatibility
541                                 ex = new SecurityException (String.Empty, se);
542                         }
543                         catch (Exception e) {
544                                 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
545                         }
546                         finally {
547                                 callback_data.sync_context.Post (delegate (object sender) {
548                                         OnUploadStringCompleted (new UploadStringCompletedEventArgs (data, ex, cancel, callback_data.user_token));
549                                 }, null);
550                         }
551                 }
552
553                 protected virtual void OnDownloadProgressChanged (DownloadProgressChangedEventArgs e)
554                 {
555                         DownloadProgressChangedEventHandler handler = DownloadProgressChanged;
556                         if (handler != null)
557                                 handler (this, e);
558                 }
559                 
560                 protected virtual void OnOpenReadCompleted (OpenReadCompletedEventArgs e)
561                 {
562                         CompleteAsync ();
563                         OpenReadCompletedEventHandler handler = OpenReadCompleted;
564                         if (handler != null)
565                                 handler (this, e);
566                 }
567
568                 protected virtual void OnDownloadStringCompleted (DownloadStringCompletedEventArgs e)
569                 {
570                         CompleteAsync ();
571                         DownloadStringCompletedEventHandler handler = DownloadStringCompleted;
572                         if (handler != null)
573                                 handler (this, e);
574                 }
575
576                 protected virtual void OnOpenWriteCompleted (OpenWriteCompletedEventArgs e)
577                 {
578                         CompleteAsync ();
579                         OpenWriteCompletedEventHandler handler = OpenWriteCompleted;
580                         if (handler != null)
581                                 handler (this, e);
582                 }
583
584                 protected virtual void OnUploadProgressChanged (UploadProgressChangedEventArgs e)
585                 {
586                         UploadProgressChangedEventHandler handler = UploadProgressChanged;
587                         if (handler != null)
588                                 handler (this, e);
589                 }
590
591                 protected virtual void OnUploadStringCompleted (UploadStringCompletedEventArgs e)
592                 {
593                         CompleteAsync ();
594                         UploadStringCompletedEventHandler handler = UploadStringCompleted;
595                         if (handler != null)
596                                 handler (this, e);
597                 }
598
599                 protected virtual void OnWriteStreamClosed (WriteStreamClosedEventArgs e)
600                 {
601                         CompleteAsync ();
602                         WriteStreamClosedEventHandler handler = WriteStreamClosed;
603                         if (handler != null)
604                                 handler (this, e);
605                 }
606
607                 protected virtual WebRequest GetWebRequest (Uri address)
608                 {
609                         if (address == null)
610                                 throw new ArgumentNullException ("address");
611
612                         // if the URI is relative then we use our base address URI to make an absolute one
613                         Uri uri = address.IsAbsoluteUri || base_address_uri == null ? address : new Uri (base_address_uri, address);
614
615                         HttpWebRequest request = (HttpWebRequest) WebRequest.Create (uri);
616                         request.AllowReadStreamBuffering = AllowReadStreamBuffering;
617                         request.AllowWriteStreamBuffering = AllowWriteStreamBuffering;
618                         request.UseDefaultCredentials = UseDefaultCredentials;
619
620                         request.progress = delegate (long read, long length) {
621                                 callback_data.sync_context.Post (delegate (object sender) {
622                                         if (upload_length > 0) {
623                                                 // always emitted as 50% with an unknown (-1) TotalBytesToSend
624                                                 OnUploadProgressChanged (new UploadProgressChangedEventArgs (read, length, 
625                                                         upload_length, -1, 50, callback_data.user_token));
626                                         } else {
627                                                 OnDownloadProgressChanged (new DownloadProgressChangedEventArgs (read, length, 
628                                                         callback_data.user_token));
629                                         }
630                                 }, null);
631                         };
632                         return request;
633                 }
634
635                 protected virtual WebResponse GetWebResponse (WebRequest request, IAsyncResult result)
636                 {
637                         return request.EndGetResponse (result);
638                 }
639         }
640 }
641