d1dea6d8ffced249024046f3ad89bada7079d534
[mono.git] / mcs / class / System / System.Net / WebClient.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 //
10 // Copyright 2003 Ximian, Inc. (http://www.ximian.com)
11 // Copyright 2006, 2010 Novell, Inc. (http://www.novell.com)
12 //
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33 //
34 // Notes on CancelAsync and Async methods:
35 //
36 //    WebClient.CancelAsync is implemented by calling Thread.Interrupt
37 //    in our helper thread.   The various async methods have to cancel
38 //    any ongoing requests by calling request.Abort () at that point.
39 //    In a few places (UploadDataCore, UploadValuesCore,
40 //    UploadFileCore) we catch the ThreadInterruptedException and
41 //    abort the request there.
42 //
43 //    Higher level routines (the async callbacks) also need to catch
44 //    the exception and raise the OnXXXXCompleted events there with
45 //    the "canceled" flag set to true. 
46 //
47 //    In a few other places where these helper routines are not used
48 //    (OpenReadAsync for example) catching the ThreadAbortException
49 //    also must abort the request.
50 //
51 //    The Async methods currently differ in their implementation from
52 //    the .NET implementation in that we manually catch any other
53 //    exceptions and correctly raise the OnXXXXCompleted passing the
54 //    Exception that caused the problem.   The .NET implementation
55 //    does not seem to have a mechanism to flag errors that happen
56 //    during downloads though.    We do this because we still need to
57 //    catch the exception on these helper threads, or we would
58 //    otherwise kill the application (on the 2.x profile, uncaught
59 //    exceptions in threads terminate the application).
60 //
61 using System;
62 using System.Collections.Specialized;
63 using System.ComponentModel;
64 using System.IO;
65 using System.Runtime.InteropServices;
66 using System.Runtime.Serialization;
67 using System.Text;
68 using System.Threading;
69 using System.Net.Cache;
70
71 namespace System.Net 
72 {
73         [ComVisible(true)]
74         public class WebClient : Component
75         {
76                 static readonly string urlEncodedCType = "application/x-www-form-urlencoded";
77                 static byte [] hexBytes;
78                 ICredentials credentials;
79                 WebHeaderCollection headers;
80                 WebHeaderCollection responseHeaders;
81                 Uri baseAddress;
82                 string baseString;
83                 NameValueCollection queryString;
84                 bool is_busy;
85                 bool async;
86                 Thread async_thread;
87                 Encoding encoding = Encoding.Default;
88                 IWebProxy proxy;
89
90                 // Constructors
91                 static WebClient ()
92                 {
93                         hexBytes = new byte [16];
94                         int index = 0;
95                         for (int i = '0'; i <= '9'; i++, index++)
96                                 hexBytes [index] = (byte) i;
97
98                         for (int i = 'a'; i <= 'f'; i++, index++)
99                                 hexBytes [index] = (byte) i;
100                 }
101                 
102                 public WebClient ()
103                 {
104                 }
105                 
106                 // Properties
107                 
108                 public string BaseAddress {
109                         get {
110                                 if (baseString == null) {
111                                         if (baseAddress == null)
112                                                 return string.Empty;
113                                 }
114
115                                 baseString = baseAddress.ToString ();
116                                 return baseString;
117                         }
118                         
119                         set {
120                                 if (value == null || value.Length == 0) {
121                                         baseAddress = null;
122                                 } else {
123                                         baseAddress = new Uri (value);
124                                 }
125                         }
126                 }
127
128                 static Exception GetMustImplement ()
129                 {
130                         return new NotImplementedException ();
131                 }
132                 
133                 [MonoTODO]
134                 public RequestCachePolicy CachePolicy
135                 {
136                         get {
137                                 throw GetMustImplement ();
138                         }
139                         set {
140                                 throw GetMustImplement ();
141                         }
142                 }
143
144                 [MonoTODO]
145                 public bool UseDefaultCredentials
146                 {
147                         get {
148                                 throw GetMustImplement ();
149                         }
150                         set {
151                                 throw GetMustImplement ();
152                         }
153                 }
154                 
155                 public ICredentials Credentials {
156                         get { return credentials; }
157                         set { credentials = value; }
158                 }
159
160                 public WebHeaderCollection Headers {
161                         get {
162                                 if (headers == null)
163                                         headers = new WebHeaderCollection ();
164
165                                 return headers;
166                         }
167                         set { headers = value; }
168                 }
169                 
170                 public NameValueCollection QueryString {
171                         get {
172                                 if (queryString == null)
173                                         queryString = new NameValueCollection ();
174
175                                 return queryString;
176                         }
177                         set { queryString = value; }
178                 }
179                 
180                 public WebHeaderCollection ResponseHeaders {
181                         get { return responseHeaders; }
182                 }
183
184                 public Encoding Encoding {
185                         get { return encoding; }
186                         set {
187                                 if (value == null)
188                                         throw new ArgumentNullException ("Encoding");
189                                 encoding = value;
190                         }
191                 }
192
193                 public IWebProxy Proxy {
194                         get { return proxy; }
195                         set { proxy = value; }
196                 }
197
198                 public bool IsBusy {
199                         get { return is_busy; } 
200                 }
201                 // Methods
202
203                 void CheckBusy ()
204                 {
205                         if (IsBusy)
206                                 throw new NotSupportedException ("WebClient does not support conccurent I/O operations.");
207                 }
208
209                 void SetBusy ()
210                 {
211                         lock (this) {
212                                 CheckBusy ();
213                                 is_busy = true;
214                         }
215                 }
216
217                 //   DownloadData
218
219                 public byte [] DownloadData (string address)
220                 {
221                         if (address == null)
222                                 throw new ArgumentNullException ("address");
223
224                         return DownloadData (CreateUri (address));
225                 }
226
227                 public byte [] DownloadData (Uri address)
228                 {
229                         if (address == null)
230                                 throw new ArgumentNullException ("address");
231
232                         try {
233                                 SetBusy ();
234                                 async = false;
235                                 return DownloadDataCore (address, null);
236                         } finally {
237                                 is_busy = false;
238                         }
239                 }
240
241                 byte [] DownloadDataCore (Uri address, object userToken)
242                 {
243                         WebRequest request = null;
244                         
245                         try {
246                                 request = SetupRequest (address);
247                                 return ReadAll (request, userToken);
248                         } catch (ThreadInterruptedException){
249                                 if (request != null)
250                                         request.Abort ();
251                                 throw;
252                         } catch (WebException) {
253                                 throw;
254                         } catch (Exception ex) {
255                                 throw new WebException ("An error occurred " +
256                                         "performing a WebClient request.", ex);
257                         }
258                 }
259
260                 //   DownloadFile
261
262                 public void DownloadFile (string address, string fileName)
263                 {
264                         if (address == null)
265                                 throw new ArgumentNullException ("address");
266
267                         DownloadFile (CreateUri (address), fileName);
268                 }
269
270                 public void DownloadFile (Uri address, string fileName)
271                 {
272                         if (address == null)
273                                 throw new ArgumentNullException ("address");
274                         if (fileName == null)
275                                 throw new ArgumentNullException ("fileName");
276
277                         try {
278                                 SetBusy ();
279                                 async = false;
280                                 DownloadFileCore (address, fileName, null);
281                         } catch (WebException) {
282                                 throw;
283                         } catch (Exception ex) {
284                                 throw new WebException ("An error occurred " +
285                                         "performing a WebClient request.", ex);
286                         } finally {
287                                 is_busy = false;
288                         }
289                 }
290
291                 void DownloadFileCore (Uri address, string fileName, object userToken)
292                 {
293                         WebRequest request = null;
294                         
295                         using (FileStream f = new FileStream (fileName, FileMode.Create)) {
296                                 try {
297                                         request = SetupRequest (address);
298                                         WebResponse response = GetWebResponse (request);
299                                         Stream st = response.GetResponseStream ();
300                                         
301                                         int cLength = (int) response.ContentLength;
302                                         int length = (cLength <= -1 || cLength > 32*1024) ? 32*1024 : cLength;
303                                         byte [] buffer = new byte [length];
304                                         
305                                         int nread = 0;
306                                         long notify_total = 0;
307                                         while ((nread = st.Read (buffer, 0, length)) != 0){
308                                                 if (async){
309                                                         notify_total += nread;
310                                                         OnDownloadProgressChanged (
311                                                                 new DownloadProgressChangedEventArgs (notify_total, response.ContentLength, userToken));
312                                                                                                       
313                                                 }
314                                                 f.Write (buffer, 0, nread);
315                                         }
316                                 } catch (ThreadInterruptedException){
317                                         if (request != null)
318                                                 request.Abort ();
319                                         throw;
320                                 }
321                         }
322                 }
323
324                 //   OpenRead
325
326                 public Stream OpenRead (string address)
327                 {
328                         if (address == null)
329                                 throw new ArgumentNullException ("address");
330                         return OpenRead (CreateUri (address));
331                 }
332
333                 public Stream OpenRead (Uri address)
334                 {
335                         if (address == null)
336                                 throw new ArgumentNullException ("address");
337
338                         WebRequest request = null;
339                         try {
340                                 SetBusy ();
341                                 async = false;
342                                 request = SetupRequest (address);
343                                 WebResponse response = GetWebResponse (request);
344                                 return response.GetResponseStream ();
345                         } catch (WebException) {
346                                 throw;
347                         } catch (Exception ex) {
348                                 throw new WebException ("An error occurred " +
349                                         "performing a WebClient request.", ex);
350                         } finally {
351                                 is_busy = false;
352                         }
353                 }
354
355                 //   OpenWrite
356
357                 public Stream OpenWrite (string address)
358                 {
359                         if (address == null)
360                                 throw new ArgumentNullException ("address");
361
362                         return OpenWrite (CreateUri (address));
363                 }
364                 
365                 public Stream OpenWrite (string address, string method)
366                 {
367                         if (address == null)
368                                 throw new ArgumentNullException ("address");
369
370                         return OpenWrite (CreateUri (address), method);
371                 }
372
373                 public Stream OpenWrite (Uri address)
374                 {
375                         return OpenWrite (address, (string) null);
376                 }
377
378                 public Stream OpenWrite (Uri address, string method)
379                 {
380                         if (address == null)
381                                 throw new ArgumentNullException ("address");
382
383                         try {
384                                 SetBusy ();
385                                 async = false;
386                                 WebRequest request = SetupRequest (address, method, true);
387                                 return request.GetRequestStream ();
388                         } catch (WebException) {
389                                 throw;
390                         } catch (Exception ex) {
391                                 throw new WebException ("An error occurred " +
392                                         "performing a WebClient request.", ex);
393                         } finally {
394                                 is_busy = false;
395                         }
396                 }
397
398                 private string DetermineMethod (Uri address, string method, bool is_upload)
399                 {
400                         if (method != null)
401                                 return method;
402
403                         if (address.Scheme == Uri.UriSchemeFtp)
404                                 return (is_upload) ? "STOR" : "RETR";
405
406                         return (is_upload) ? "POST" : "GET";
407                 }
408
409                 //   UploadData
410
411                 public byte [] UploadData (string address, byte [] data)
412                 {
413                         if (address == null)
414                                 throw new ArgumentNullException ("address");
415
416                         return UploadData (CreateUri (address), data);
417                 }
418                 
419                 public byte [] UploadData (string address, string method, byte [] data)
420                 {
421                         if (address == null)
422                                 throw new ArgumentNullException ("address");
423
424                         return UploadData (CreateUri (address), method, data);
425                 }
426
427                 public byte [] UploadData (Uri address, byte [] data)
428                 {
429                         return UploadData (address, (string) null, data);
430                 }
431
432                 public byte [] UploadData (Uri address, string method, byte [] data)
433                 {
434                         if (address == null)
435                                 throw new ArgumentNullException ("address");
436                         if (data == null)
437                                 throw new ArgumentNullException ("data");
438
439                         try {
440                                 SetBusy ();
441                                 async = false;
442                                 return UploadDataCore (address, method, data, null);
443                         } catch (WebException) {
444                                 throw;
445                         } catch (Exception ex) {
446                                 throw new WebException ("An error occurred " +
447                                         "performing a WebClient request.", ex);
448                         } finally {
449                                 is_busy = false;
450                         }
451                 }
452
453                 byte [] UploadDataCore (Uri address, string method, byte [] data, object userToken)
454                 {
455                         WebRequest request = SetupRequest (address, method, true);
456                         try {
457                                 int contentLength = data.Length;
458                                 request.ContentLength = contentLength;
459                                 using (Stream stream = request.GetRequestStream ()) {
460                                         stream.Write (data, 0, contentLength);
461                                 }
462                                 
463                                 return ReadAll (request, userToken);
464                         } catch (ThreadInterruptedException){
465                                 if (request != null)
466                                         request.Abort ();
467                                 throw;
468                         }
469                 }
470
471                 //   UploadFile
472
473                 public byte [] UploadFile (string address, string fileName)
474                 {
475                         if (address == null)
476                                 throw new ArgumentNullException ("address");
477
478                         return UploadFile (CreateUri (address), fileName);
479                 }
480
481                 public byte [] UploadFile (Uri address, string fileName)
482                 {
483                         return UploadFile (address, (string) null, fileName);
484                 }
485                 
486                 public byte [] UploadFile (string address, string method, string fileName)
487                 {
488                         return UploadFile (CreateUri (address), method, fileName);
489                 }
490
491                 public byte [] UploadFile (Uri address, string method, string fileName)
492                 {
493                         if (address == null)
494                                 throw new ArgumentNullException ("address");
495                         if (fileName == null)
496                                 throw new ArgumentNullException ("fileName");
497
498                         try {
499                                 SetBusy ();
500                                 async = false;
501                                 return UploadFileCore (address, method, fileName, null);
502                         } catch (WebException) {
503                                 throw;
504                         } catch (Exception ex) {
505                                 throw new WebException ("An error occurred " +
506                                         "performing a WebClient request.", ex);
507                         } finally {
508                                 is_busy = false;
509                         }
510                 }
511
512                 byte [] UploadFileCore (Uri address, string method, string fileName, object userToken)
513                 {
514                         string fileCType = Headers ["Content-Type"];
515                         if (fileCType != null) {
516                                 string lower = fileCType.ToLower ();
517                                 if (lower.StartsWith ("multipart/"))
518                                         throw new WebException ("Content-Type cannot be set to a multipart" +
519                                                                 " type for this request.");
520                         } else {
521                                 fileCType = "application/octet-stream";
522                         }
523
524                         string boundary = "------------" + DateTime.Now.Ticks.ToString ("x");
525                         Headers ["Content-Type"] = String.Format ("multipart/form-data; boundary={0}", boundary);
526                         Stream reqStream = null;
527                         Stream fStream = null;
528                         byte [] resultBytes = null;
529
530                         fileName = Path.GetFullPath (fileName);
531
532                         WebRequest request = null;
533                         try {
534                                 fStream = File.OpenRead (fileName);
535                                 request = SetupRequest (address, method, true);
536                                 reqStream = request.GetRequestStream ();
537                                 byte [] realBoundary = Encoding.ASCII.GetBytes ("--" + boundary + "\r\n");
538                                 reqStream.Write (realBoundary, 0, realBoundary.Length);
539                                 string partHeaders = String.Format ("Content-Disposition: form-data; " +
540                                                                     "name=\"file\"; filename=\"{0}\"\r\n" +
541                                                                     "Content-Type: {1}\r\n\r\n",
542                                                                     Path.GetFileName (fileName), fileCType);
543
544                                 byte [] partHeadersBytes = Encoding.UTF8.GetBytes (partHeaders);
545                                 reqStream.Write (partHeadersBytes, 0, partHeadersBytes.Length);
546                                 int nread;
547                                 byte [] buffer = new byte [4096];
548                                 while ((nread = fStream.Read (buffer, 0, 4096)) != 0)
549                                         reqStream.Write (buffer, 0, nread);
550
551                                 reqStream.WriteByte ((byte) '\r');
552                                 reqStream.WriteByte ((byte) '\n');
553                                 reqStream.Write (realBoundary, 0, realBoundary.Length);
554                                 reqStream.Close ();
555                                 reqStream = null;
556                                 resultBytes = ReadAll (request, userToken);
557                         } catch (ThreadInterruptedException){
558                                 if (request != null)
559                                         request.Abort ();
560                                 throw;
561                         } finally {
562                                 if (fStream != null)
563                                         fStream.Close ();
564
565                                 if (reqStream != null)
566                                         reqStream.Close ();
567                         }
568                         
569                         return resultBytes;
570                 }
571                 
572                 public byte[] UploadValues (string address, NameValueCollection data)
573                 {
574                         if (address == null)
575                                 throw new ArgumentNullException ("address");
576
577                         return UploadValues (CreateUri (address), data);
578                 }
579                 
580                 public byte[] UploadValues (string address, string method, NameValueCollection data)
581                 {
582                         if (address == null)
583                                 throw new ArgumentNullException ("address");
584                         return UploadValues (CreateUri (address), method, data);
585                 }
586
587                 public byte[] UploadValues (Uri address, NameValueCollection data)
588                 {
589                         return UploadValues (address, (string) null, data);
590                 }
591
592                 public byte[] UploadValues (Uri address, string method, NameValueCollection data)
593                 {
594                         if (address == null)
595                                 throw new ArgumentNullException ("address");
596                         if (data == null)
597                                 throw new ArgumentNullException ("data");
598
599                         try {
600                                 SetBusy ();
601                                 async = false;
602                                 return UploadValuesCore (address, method, data, null);
603                         } catch (WebException) {
604                                 throw;
605                         } catch (Exception ex) {
606                                 throw new WebException ("An error occurred " +
607                                         "performing a WebClient request.", ex);
608                         } finally {
609                                 is_busy = false;
610                         }
611                 }
612
613                 byte[] UploadValuesCore (Uri uri, string method, NameValueCollection data, object userToken)
614                 {
615                         string cType = Headers ["Content-Type"];
616                         if (cType != null && String.Compare (cType, urlEncodedCType, true) != 0)
617                                 throw new WebException ("Content-Type header cannot be changed from its default " +
618                                                         "value for this request.");
619
620                         Headers ["Content-Type"] = urlEncodedCType;
621                         WebRequest request = SetupRequest (uri, method, true);
622                         try {
623                                 MemoryStream tmpStream = new MemoryStream ();
624                                 foreach (string key in data) {
625                                         byte [] bytes = Encoding.UTF8.GetBytes (key);
626                                         UrlEncodeAndWrite (tmpStream, bytes);
627                                         tmpStream.WriteByte ((byte) '=');
628                                         bytes = Encoding.UTF8.GetBytes (data [key]);
629                                         UrlEncodeAndWrite (tmpStream, bytes);
630                                         tmpStream.WriteByte ((byte) '&');
631                                 }
632                                 
633                                 int length = (int) tmpStream.Length;
634                                 if (length > 0)
635                                         tmpStream.SetLength (--length); // remove trailing '&'
636                                 
637                                 byte [] buf = tmpStream.GetBuffer ();
638                                 request.ContentLength = length;
639                                 using (Stream rqStream = request.GetRequestStream ()) {
640                                         rqStream.Write (buf, 0, length);
641                                 }
642                                 tmpStream.Close ();
643                                 
644                                 return ReadAll (request, userToken);
645                         } catch (ThreadInterruptedException) {
646                                 request.Abort ();
647                                 throw;
648                         }
649                 }
650
651                 public string DownloadString (string address)
652                 {
653                         if (address == null)
654                                 throw new ArgumentNullException ("address");
655
656                         return encoding.GetString (DownloadData (CreateUri (address)));
657                 }
658
659                 public string DownloadString (Uri address)
660                 {
661                         if (address == null)
662                                 throw new ArgumentNullException ("address");
663
664                         return encoding.GetString (DownloadData (CreateUri (address)));
665                 }
666
667                 public string UploadString (string address, string data)
668                 {
669                         if (address == null)
670                                 throw new ArgumentNullException ("address");
671                         if (data == null)
672                                 throw new ArgumentNullException ("data");
673
674                         byte [] resp = UploadData (address, encoding.GetBytes (data));
675                         return encoding.GetString (resp);
676                 }
677
678                 public string UploadString (string address, string method, string data)
679                 {
680                         if (address == null)
681                                 throw new ArgumentNullException ("address");
682                         if (data == null)
683                                 throw new ArgumentNullException ("data");
684
685                         byte [] resp = UploadData (address, method, encoding.GetBytes (data));
686                         return encoding.GetString (resp);
687                 }
688
689                 public string UploadString (Uri address, string data)
690                 {
691                         if (address == null)
692                                 throw new ArgumentNullException ("address");
693                         if (data == null)
694                                 throw new ArgumentNullException ("data");
695
696                         byte [] resp = UploadData (address, encoding.GetBytes (data));
697                         return encoding.GetString (resp);
698                 }
699
700                 public string UploadString (Uri address, string method, string data)
701                 {
702                         if (address == null)
703                                 throw new ArgumentNullException ("address");
704                         if (data == null)
705                                 throw new ArgumentNullException ("data");
706
707                         byte [] resp = UploadData (address, method, encoding.GetBytes (data));
708                         return encoding.GetString (resp);
709                 }
710
711                 public event DownloadDataCompletedEventHandler DownloadDataCompleted;
712                 public event AsyncCompletedEventHandler DownloadFileCompleted;
713                 public event DownloadProgressChangedEventHandler DownloadProgressChanged;
714                 public event DownloadStringCompletedEventHandler DownloadStringCompleted;
715                 public event OpenReadCompletedEventHandler OpenReadCompleted;
716                 public event OpenWriteCompletedEventHandler OpenWriteCompleted;
717                 public event UploadDataCompletedEventHandler UploadDataCompleted;
718                 public event UploadFileCompletedEventHandler UploadFileCompleted;
719                 public event UploadProgressChangedEventHandler UploadProgressChanged;
720                 public event UploadStringCompletedEventHandler UploadStringCompleted;
721                 public event UploadValuesCompletedEventHandler UploadValuesCompleted;
722
723                 Uri CreateUri (string address)
724                 {
725                         Uri uri;
726                         try {
727                                 if (baseAddress == null)
728                                         uri = new Uri (address);
729                                 else
730                                         uri = new Uri (baseAddress, address);
731                                 return CreateUri (uri);
732                         } catch {
733                         }
734                         return new Uri (Path.GetFullPath (address));
735                 }
736
737                 Uri CreateUri (Uri address)
738                 {
739                         Uri result = address;
740                         if (baseAddress != null && !result.IsAbsoluteUri) {
741                                 try {
742                                         result = new Uri (baseAddress, result.OriginalString);
743                                 } catch {
744                                         return result; // Not much we can do here.
745                                 }
746                         }
747
748                         string query = result.Query;
749                         if (String.IsNullOrEmpty (query))
750                                 query = GetQueryString (true);
751                         UriBuilder builder = new UriBuilder (address);
752                         if (!String.IsNullOrEmpty (query))
753                                 builder.Query = query.Substring (1);
754                         return builder.Uri;
755                 }
756
757                 string GetQueryString (bool add_qmark)
758                 {
759                         if (queryString == null || queryString.Count == 0)
760                                 return null;
761
762                         StringBuilder sb = new StringBuilder ();
763                         if (add_qmark)
764                                 sb.Append ('?');
765
766                         foreach (string key in queryString)
767                                 sb.AppendFormat ("{0}={1}&", key, UrlEncode (queryString [key]));
768
769                         if (sb.Length != 0)
770                                 sb.Length--; // removes last '&' or the '?' if empty.
771
772                         if (sb.Length == 0)
773                                 return null;
774
775                         return sb.ToString ();
776                 }
777
778                 WebRequest SetupRequest (Uri uri)
779                 {
780                         WebRequest request = GetWebRequest (uri);
781                         if (Proxy != null)
782                                 request.Proxy = Proxy;
783                         request.Credentials = credentials;
784
785                         // Special headers. These are properties of HttpWebRequest.
786                         // What do we do with other requests differnt from HttpWebRequest?
787                         if (headers != null && headers.Count != 0 && (request is HttpWebRequest)) {
788                                 HttpWebRequest req = (HttpWebRequest) request;
789                                 string expect = headers ["Expect"];
790                                 string contentType = headers ["Content-Type"];
791                                 string accept = headers ["Accept"];
792                                 string connection = headers ["Connection"];
793                                 string userAgent = headers ["User-Agent"];
794                                 string referer = headers ["Referer"];
795                                 headers.RemoveInternal ("Expect");
796                                 headers.RemoveInternal ("Content-Type");
797                                 headers.RemoveInternal ("Accept");
798                                 headers.RemoveInternal ("Connection");
799                                 headers.RemoveInternal ("Referer");
800                                 headers.RemoveInternal ("User-Agent");
801                                 request.Headers = headers;
802
803                                 if (expect != null && expect.Length > 0)
804                                         req.Expect = expect;
805
806                                 if (accept != null && accept.Length > 0)
807                                         req.Accept = accept;
808
809                                 if (contentType != null && contentType.Length > 0)
810                                         req.ContentType = contentType;
811
812                                 if (connection != null && connection.Length > 0)
813                                         req.Connection = connection;
814
815                                 if (userAgent != null && userAgent.Length > 0)
816                                         req.UserAgent = userAgent;
817
818                                 if (referer != null && referer.Length > 0)
819                                         req.Referer = referer;
820                         }
821
822                         responseHeaders = null;
823                         return request;
824                 }
825
826                 WebRequest SetupRequest (Uri uri, string method, bool is_upload)
827                 {
828                         WebRequest request = SetupRequest (uri);
829                         request.Method = DetermineMethod (uri, method, is_upload);
830                         return request;
831                 }
832
833                 byte [] ReadAll (WebRequest request, object userToken)
834                 {
835                         WebResponse response = GetWebResponse (request);
836                         Stream stream = response.GetResponseStream ();
837                         int length = (int) response.ContentLength;
838                         HttpWebRequest wreq = request as HttpWebRequest;
839
840                         if (length > -1 && wreq != null && (int) wreq.AutomaticDecompression != 0) {
841                                 string content_encoding = ((HttpWebResponse) response).ContentEncoding;
842                                 if (((content_encoding == "gzip" && (wreq.AutomaticDecompression & DecompressionMethods.GZip) != 0)) ||
843                                         ((content_encoding == "deflate" && (wreq.AutomaticDecompression & DecompressionMethods.Deflate) != 0)))
844                                         length = -1;
845                         }
846
847                         MemoryStream ms = null;
848                         bool nolength = (length == -1);
849                         int size = ((nolength) ? 8192 : length);
850                         if (nolength)
851                                 ms = new MemoryStream ();
852
853 //                      long total = 0;
854                         int nread = 0;
855                         int offset = 0;
856                         byte [] buffer = new byte [size];
857                         while ((nread = stream.Read (buffer, offset, size)) != 0) {
858                                 if (nolength) {
859                                         ms.Write (buffer, 0, nread);
860                                 } else {
861                                         offset += nread;
862                                         size -= nread;
863                                 }
864                                 if (async){
865 //                                      total += nread;
866                                         OnDownloadProgressChanged (new DownloadProgressChangedEventArgs (nread, length, userToken));
867                                 }
868                         }
869
870                         if (nolength)
871                                 return ms.ToArray ();
872
873                         return buffer;
874                 }
875
876                 string UrlEncode (string str)
877                 {
878                         StringBuilder result = new StringBuilder ();
879
880                         int len = str.Length;
881                         for (int i = 0; i < len; i++) {
882                                 char c = str [i];
883                                 if (c == ' ')
884                                         result.Append ('+');
885                                 else if ((c < '0' && c != '-' && c != '.') ||
886                                          (c < 'A' && c > '9') ||
887                                          (c > 'Z' && c < 'a' && c != '_') ||
888                                          (c > 'z')) {
889                                         result.Append ('%');
890                                         int idx = ((int) c) >> 4;
891                                         result.Append ((char) hexBytes [idx]);
892                                         idx = ((int) c) & 0x0F;
893                                         result.Append ((char) hexBytes [idx]);
894                                 } else {
895                                         result.Append (c);
896                                 }
897                         }
898
899                         return result.ToString ();
900                 }
901
902                 static void UrlEncodeAndWrite (Stream stream, byte [] bytes)
903                 {
904                         if (bytes == null)
905                                 return;
906
907                         int len = bytes.Length;
908                         if (len == 0)
909                                 return;
910
911                         for (int i = 0; i < len; i++) {
912                                 char c = (char) bytes [i];
913                                 if (c == ' ')
914                                         stream.WriteByte ((byte) '+');
915                                 else if ((c < '0' && c != '-' && c != '.') ||
916                                          (c < 'A' && c > '9') ||
917                                          (c > 'Z' && c < 'a' && c != '_') ||
918                                          (c > 'z')) {
919                                         stream.WriteByte ((byte) '%');
920                                         int idx = ((int) c) >> 4;
921                                         stream.WriteByte (hexBytes [idx]);
922                                         idx = ((int) c) & 0x0F;
923                                         stream.WriteByte (hexBytes [idx]);
924                                 } else {
925                                         stream.WriteByte ((byte) c);
926                                 }
927                         }
928                 }
929
930                 public void CancelAsync ()
931                 {
932                         lock (this){
933                                 if (async_thread == null)
934                                         return;
935
936                                 //
937                                 // We first flag things as done, in case the Interrupt hangs
938                                 // or the thread decides to hang in some other way inside the
939                                 // event handlers, or if we are stuck somewhere else.  This
940                                 // ensures that the WebClient object is reusable immediately
941                                 //
942                                 Thread t = async_thread;
943                                 CompleteAsync ();
944                                 t.Interrupt ();
945                         }
946                 }
947
948                 void CompleteAsync ()
949                 {
950                         lock (this){
951                                 is_busy = false;
952                                 async_thread = null;
953                         }
954                 }
955
956                 //    DownloadDataAsync
957
958                 public void DownloadDataAsync (Uri address)
959                 {
960                         DownloadDataAsync (address, null);
961                 }
962
963                 public void DownloadDataAsync (Uri address, object userToken)
964                 {
965                         if (address == null)
966                                 throw new ArgumentNullException ("address");
967                         
968                         lock (this) {
969                                 SetBusy ();
970                                 async = true;
971                                 
972                                 async_thread = new Thread (delegate (object state) {
973                                         object [] args = (object []) state;
974                                         try {
975                                                 byte [] data = DownloadDataCore ((Uri) args [0], args [1]);
976                                                 OnDownloadDataCompleted (
977                                                         new DownloadDataCompletedEventArgs (data, null, false, args [1]));
978                                         } catch (ThreadInterruptedException){
979                                                 OnDownloadDataCompleted (
980                                                         new DownloadDataCompletedEventArgs (null, null, true, args [1]));
981                                                 throw;
982                                         } catch (Exception e){
983                                                 OnDownloadDataCompleted (
984                                                         new DownloadDataCompletedEventArgs (null, e, false, args [1]));
985                                         }
986                                 });
987                                 object [] cb_args = new object [] {address, userToken};
988                                 async_thread.Start (cb_args);
989                         }
990                 }
991
992                 //    DownloadFileAsync
993
994                 public void DownloadFileAsync (Uri address, string fileName)
995                 {
996                         DownloadFileAsync (address, fileName, null);
997                 }
998
999                 public void DownloadFileAsync (Uri address, string fileName, object userToken)
1000                 {
1001                         if (address == null)
1002                                 throw new ArgumentNullException ("address");
1003                         if (fileName == null)
1004                                 throw new ArgumentNullException ("fileName");
1005                         
1006                         lock (this) {
1007                                 SetBusy ();
1008                                 async = true;
1009
1010                                 async_thread = new Thread (delegate (object state) {
1011                                         object [] args = (object []) state;
1012                                         try {
1013                                                 DownloadFileCore ((Uri) args [0], (string) args [1], args [2]);
1014                                                 OnDownloadFileCompleted (
1015                                                         new AsyncCompletedEventArgs (null, false, args [2]));
1016                                         } catch (ThreadInterruptedException){
1017                                                 OnDownloadFileCompleted (
1018                                                         new AsyncCompletedEventArgs (null, true, args [2]));
1019                                         } catch (Exception e){
1020                                                 OnDownloadFileCompleted (
1021                                                         new AsyncCompletedEventArgs (e, false, args [2]));
1022                                         }});
1023                                 object [] cb_args = new object [] {address, fileName, userToken};
1024                                 async_thread.Start (cb_args);
1025                         }
1026                 }
1027
1028                 //    DownloadStringAsync
1029
1030                 public void DownloadStringAsync (Uri address)
1031                 {
1032                         DownloadStringAsync (address, null);
1033                 }
1034
1035                 public void DownloadStringAsync (Uri address, object userToken)
1036                 {
1037                         if (address == null)
1038                                 throw new ArgumentNullException ("address");
1039                         
1040                         lock (this) {
1041                                 SetBusy ();
1042                                 async = true;
1043
1044                                 async_thread = new Thread (delegate (object state) {
1045                                         object [] args = (object []) state;
1046                                         try {
1047                                                 string data = encoding.GetString (DownloadDataCore ((Uri) args [0], args [1]));
1048                                                 OnDownloadStringCompleted (
1049                                                         new DownloadStringCompletedEventArgs (data, null, false, args [1]));
1050                                         } catch (ThreadInterruptedException){
1051                                                 OnDownloadStringCompleted (
1052                                                         new DownloadStringCompletedEventArgs (null, null, true, args [1]));
1053                                         } catch (Exception e){
1054                                                 OnDownloadStringCompleted (
1055                                                         new DownloadStringCompletedEventArgs (null, e, false, args [1]));
1056                                         }});
1057                                 object [] cb_args = new object [] {address, userToken};
1058                                 async_thread.Start (cb_args);
1059                         }
1060                 }
1061
1062                 //    OpenReadAsync
1063
1064                 public void OpenReadAsync (Uri address)
1065                 {
1066                         OpenReadAsync (address, null);
1067                 }
1068
1069                 public void OpenReadAsync (Uri address, object userToken)
1070                 {
1071                         if (address == null)
1072                                 throw new ArgumentNullException ("address");
1073                         
1074                         lock (this) {
1075                                 SetBusy ();
1076                                 async = true;
1077
1078                                 async_thread = new Thread (delegate (object state) {
1079                                         object [] args = (object []) state;
1080                                         WebRequest request = null;
1081                                         try {
1082                                                 request = SetupRequest ((Uri) args [0]);
1083                                                 WebResponse response = GetWebResponse (request);
1084                                                 Stream stream = response.GetResponseStream ();
1085                                                 OnOpenReadCompleted (
1086                                                         new OpenReadCompletedEventArgs (stream, null, false, args [1]));
1087                                         } catch (ThreadInterruptedException){
1088                                                 if (request != null)
1089                                                         request.Abort ();
1090                                                 
1091                                                 OnOpenReadCompleted (new OpenReadCompletedEventArgs (null, null, true, args [1]));
1092                                         } catch (Exception e){
1093                                                 OnOpenReadCompleted (new OpenReadCompletedEventArgs (null, e, false, args [1]));
1094                                         } });
1095                                 object [] cb_args = new object [] {address, userToken};
1096                                 async_thread.Start (cb_args);
1097                         }
1098                 }
1099
1100                 //    OpenWriteAsync
1101
1102                 public void OpenWriteAsync (Uri address)
1103                 {
1104                         OpenWriteAsync (address, null);
1105                 }
1106
1107                 public void OpenWriteAsync (Uri address, string method)
1108                 {
1109                         OpenWriteAsync (address, method, null);
1110                 }
1111
1112                 public void OpenWriteAsync (Uri address, string method, object userToken)
1113                 {
1114                         if (address == null)
1115                                 throw new ArgumentNullException ("address");
1116
1117                         lock (this) {
1118                                 SetBusy ();
1119                                 async = true;
1120
1121                                 async_thread = new Thread (delegate (object state) {
1122                                         object [] args = (object []) state;
1123                                         WebRequest request = null;
1124                                         try {
1125                                                 request = SetupRequest ((Uri) args [0], (string) args [1], true);
1126                                                 Stream stream = request.GetRequestStream ();
1127                                                 OnOpenWriteCompleted (
1128                                                         new OpenWriteCompletedEventArgs (stream, null, false, args [2]));
1129                                         } catch (ThreadInterruptedException){
1130                                                 if (request != null)
1131                                                         request.Abort ();
1132                                                 OnOpenWriteCompleted (
1133                                                         new OpenWriteCompletedEventArgs (null, null, true, args [2]));
1134                                         } catch (Exception e){
1135                                                 OnOpenWriteCompleted (
1136                                                         new OpenWriteCompletedEventArgs (null, e, false, args [2]));
1137                                         }});
1138                                 object [] cb_args = new object [] {address, method, userToken};
1139                                 async_thread.Start (cb_args);
1140                         }
1141                 }
1142
1143                 //    UploadDataAsync
1144
1145                 public void UploadDataAsync (Uri address, byte [] data)
1146                 {
1147                         UploadDataAsync (address, null, data);
1148                 }
1149
1150                 public void UploadDataAsync (Uri address, string method, byte [] data)
1151                 {
1152                         UploadDataAsync (address, method, data, null);
1153                 }
1154
1155                 public void UploadDataAsync (Uri address, string method, byte [] data, object userToken)
1156                 {
1157                         if (address == null)
1158                                 throw new ArgumentNullException ("address");
1159                         if (data == null)
1160                                 throw new ArgumentNullException ("data");
1161                         
1162                         lock (this) {
1163                                 SetBusy ();
1164                                 async = true;
1165
1166                                 async_thread = new Thread (delegate (object state) {
1167                                         object [] args = (object []) state;
1168                                         byte [] data2;
1169
1170                                         try {
1171                                                 data2 = UploadDataCore ((Uri) args [0], (string) args [1], (byte []) args [2], args [3]);
1172                                         
1173                                                 OnUploadDataCompleted (
1174                                                         new UploadDataCompletedEventArgs (data2, null, false, args [3]));
1175                                         } catch (ThreadInterruptedException){
1176                                                 OnUploadDataCompleted (
1177                                                         new UploadDataCompletedEventArgs (null, null, true, args [3]));
1178                                         } catch (Exception e){
1179                                                 OnUploadDataCompleted (
1180                                                         new UploadDataCompletedEventArgs (null, e, false, args [3]));
1181                                         }});
1182                                 object [] cb_args = new object [] {address, method, data,  userToken};
1183                                 async_thread.Start (cb_args);
1184                         }
1185                 }
1186
1187                 //    UploadFileAsync
1188
1189                 public void UploadFileAsync (Uri address, string fileName)
1190                 {
1191                         UploadFileAsync (address, null, fileName);
1192                 }
1193
1194                 public void UploadFileAsync (Uri address, string method, string fileName)
1195                 {
1196                         UploadFileAsync (address, method, fileName, null);
1197                 }
1198
1199                 public void UploadFileAsync (Uri address, string method, string fileName, object userToken)
1200                 {
1201                         if (address == null)
1202                                 throw new ArgumentNullException ("address");
1203                         if (fileName == null)
1204                                 throw new ArgumentNullException ("fileName");
1205
1206                         lock (this) {
1207                                 SetBusy ();
1208                                 async = true;
1209
1210                                 async_thread = new Thread (delegate (object state) {
1211                                         object [] args = (object []) state;
1212                                         byte [] data;
1213
1214                                         try {
1215                                                 data = UploadFileCore ((Uri) args [0], (string) args [1], (string) args [2], args [3]);
1216                                                 OnUploadFileCompleted (
1217                                                         new UploadFileCompletedEventArgs (data, null, false, args [3]));
1218                                         } catch (ThreadInterruptedException){
1219                                                 OnUploadFileCompleted (
1220                                                         new UploadFileCompletedEventArgs (null, null, true, args [3]));
1221                                         } catch (Exception e){
1222                                                 OnUploadFileCompleted (
1223                                                         new UploadFileCompletedEventArgs (null, e, false, args [3]));
1224                                         }});
1225                                 object [] cb_args = new object [] {address, method, fileName,  userToken};
1226                                 async_thread.Start (cb_args);
1227                         }
1228                 }
1229
1230                 //    UploadStringAsync
1231
1232                 public void UploadStringAsync (Uri address, string data)
1233                 {
1234                         UploadStringAsync (address, null, data);
1235                 }
1236
1237                 public void UploadStringAsync (Uri address, string method, string data)
1238                 {
1239                         UploadStringAsync (address, method, data, null);
1240                 }
1241
1242                 public void UploadStringAsync (Uri address, string method, string data, object userToken)
1243                 {
1244                         if (address == null)
1245                                 throw new ArgumentNullException ("address");
1246                         if (data == null)
1247                                 throw new ArgumentNullException ("data");
1248                         
1249                         lock (this) {
1250                                 CheckBusy ();
1251                                 async = true;
1252                                 
1253                                 async_thread = new Thread (delegate (object state) {
1254                                         object [] args = (object []) state;
1255
1256                                         try {
1257                                                 string data2 = UploadString ((Uri) args [0], (string) args [1], (string) args [2]);
1258                                                 OnUploadStringCompleted (
1259                                                         new UploadStringCompletedEventArgs (data2, null, false, args [3]));
1260                                         } catch (ThreadInterruptedException){
1261                                                 OnUploadStringCompleted (
1262                                                         new UploadStringCompletedEventArgs (null, null, true, args [3]));
1263                                         } catch (Exception e){
1264                                                 OnUploadStringCompleted (
1265                                                         new UploadStringCompletedEventArgs (null, e, false, args [3]));
1266                                         }});
1267                                 object [] cb_args = new object [] {address, method, data, userToken};
1268                                 async_thread.Start (cb_args);
1269                         }
1270                 }
1271
1272                 //    UploadValuesAsync
1273
1274                 public void UploadValuesAsync (Uri address, NameValueCollection values)
1275                 {
1276                         UploadValuesAsync (address, null, values);
1277                 }
1278
1279                 public void UploadValuesAsync (Uri address, string method, NameValueCollection values)
1280                 {
1281                         UploadValuesAsync (address, method, values, null);
1282                 }
1283
1284                 public void UploadValuesAsync (Uri address, string method, NameValueCollection values, object userToken)
1285                 {
1286                         if (address == null)
1287                                 throw new ArgumentNullException ("address");
1288                         if (values == null)
1289                                 throw new ArgumentNullException ("values");
1290
1291                         lock (this) {
1292                                 CheckBusy ();
1293                                 async = true;
1294
1295                                 async_thread = new Thread (delegate (object state) {
1296                                         object [] args = (object []) state;
1297                                         try {
1298                                                 byte [] data = UploadValuesCore ((Uri) args [0], (string) args [1], (NameValueCollection) args [2], args [3]);
1299                                                 OnUploadValuesCompleted (
1300                                                         new UploadValuesCompletedEventArgs (data, null, false, args [3]));
1301                                         } catch (ThreadInterruptedException){
1302                                                 OnUploadValuesCompleted (
1303                                                         new UploadValuesCompletedEventArgs (null, null, true, args [3]));
1304                                         } catch (Exception e){
1305                                                 OnUploadValuesCompleted (
1306                                                         new UploadValuesCompletedEventArgs (null, e, false, args [3]));
1307                                         }});
1308                                 object [] cb_args = new object [] {address, method, values,  userToken};
1309                                 async_thread.Start (cb_args);
1310                         }
1311                 }
1312
1313                 protected virtual void OnDownloadDataCompleted (DownloadDataCompletedEventArgs args)
1314                 {
1315                         CompleteAsync ();
1316                         if (DownloadDataCompleted != null)
1317                                 DownloadDataCompleted (this, args);
1318                 }
1319
1320                 protected virtual void OnDownloadFileCompleted (AsyncCompletedEventArgs args)
1321                 {
1322                         CompleteAsync ();
1323                         if (DownloadFileCompleted != null)
1324                                 DownloadFileCompleted (this, args);
1325                 }
1326
1327                 protected virtual void OnDownloadProgressChanged (DownloadProgressChangedEventArgs e)
1328                 {
1329                         if (DownloadProgressChanged != null)
1330                                 DownloadProgressChanged (this, e);
1331                 }
1332
1333                 protected virtual void OnDownloadStringCompleted (DownloadStringCompletedEventArgs args)
1334                 {
1335                         CompleteAsync ();
1336                         if (DownloadStringCompleted != null)
1337                                 DownloadStringCompleted (this, args);
1338                 }
1339
1340                 protected virtual void OnOpenReadCompleted (OpenReadCompletedEventArgs args)
1341                 {
1342                         CompleteAsync ();
1343                         if (OpenReadCompleted != null)
1344                                 OpenReadCompleted (this, args);
1345                 }
1346
1347                 protected virtual void OnOpenWriteCompleted (OpenWriteCompletedEventArgs args)
1348                 {
1349                         CompleteAsync ();
1350                         if (OpenWriteCompleted != null)
1351                                 OpenWriteCompleted (this, args);
1352                 }
1353
1354                 protected virtual void OnUploadDataCompleted (UploadDataCompletedEventArgs args)
1355                 {
1356                         CompleteAsync ();
1357                         if (UploadDataCompleted != null)
1358                                 UploadDataCompleted (this, args);
1359                 }
1360
1361                 protected virtual void OnUploadFileCompleted (UploadFileCompletedEventArgs args)
1362                 {
1363                         CompleteAsync ();
1364                         if (UploadFileCompleted != null)
1365                                 UploadFileCompleted (this, args);
1366                 }
1367
1368                 protected virtual void OnUploadProgressChanged (UploadProgressChangedEventArgs e)
1369                 {
1370                         if (UploadProgressChanged != null)
1371                                 UploadProgressChanged (this, e);
1372                 }
1373
1374                 protected virtual void OnUploadStringCompleted (UploadStringCompletedEventArgs args)
1375                 {
1376                         CompleteAsync ();
1377                         if (UploadStringCompleted != null)
1378                                 UploadStringCompleted (this, args);
1379                 }
1380
1381                 protected virtual void OnUploadValuesCompleted (UploadValuesCompletedEventArgs args)
1382                 {
1383                         CompleteAsync ();
1384                         if (UploadValuesCompleted != null)
1385                                 UploadValuesCompleted (this, args);
1386                 }
1387
1388                 protected virtual WebResponse GetWebResponse (WebRequest request, IAsyncResult result)
1389                 {
1390                         WebResponse response = request.EndGetResponse (result);
1391                         responseHeaders = response.Headers;
1392                         return response;
1393                 }
1394
1395                 protected virtual WebRequest GetWebRequest (Uri address)
1396                 {
1397                         return WebRequest.Create (address);
1398                 }
1399
1400                 protected virtual WebResponse GetWebResponse (WebRequest request)
1401                 {
1402                         WebResponse response = request.GetResponse ();
1403                         responseHeaders = response.Headers;
1404                         return response;
1405                 }
1406
1407         }
1408 }
1409