2010-03-18 Sebastien Pouliot <sebastien@ximian.com>
[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.Text;
37 using System.Threading;
38
39 namespace System.Net {
40
41         // note: this type is effectively sealed to transparent code since it's default .ctor is marked with [SecuritySafeCritical]
42         public class WebClient {
43
44                 WebHeaderCollection headers;
45                 WebHeaderCollection responseHeaders;
46                 string baseAddress;
47                 bool is_busy;
48                 Encoding encoding = Encoding.UTF8;
49                 bool allow_read_buffering = true;
50                 bool allow_write_buffering = true;
51                 WebRequest request;
52                 object locker;
53                 CallbackData callback_data;
54
55                 public WebClient ()
56                 {
57                         // kind of calling NativeMethods.plugin_instance_get_source_location (PluginHost.Handle)
58                         // but without adding dependency on System.Windows.dll. GetData is [SecurityCritical]
59                         // this makes the default .ctor [SecuritySafeCritical] which would be a problem (inheritance)
60                         // but it happens that MS SL2 also has this default .ctor as SSC :-)
61                         baseAddress = (AppDomain.CurrentDomain.GetData ("xap_uri") as string);
62                         locker = new object ();
63                 }
64                 
65                 // Properties
66                 
67                 public string BaseAddress {
68                         get { return baseAddress; }
69                         set {
70                                 if (String.IsNullOrEmpty (value)) {
71                                         baseAddress = String.Empty;
72                                 } else {
73                                         Uri uri = null;
74                                         if (!Uri.TryCreate (value, UriKind.Absolute, out uri))
75                                                 throw new ArgumentException ("Invalid URI");
76
77                                         baseAddress = Uri.UnescapeDataString (uri.AbsoluteUri);
78                                 }
79                         }
80                 }
81
82                 [MonoTODO ("provide credentials to the client stack")]
83                 public ICredentials Credentials { get; set; }
84
85                 // this is an unvalidated collection, HttpWebRequest is responsable to validate it
86                 public WebHeaderCollection Headers {
87                         get {
88                                 if (headers == null)
89                                         headers = new WebHeaderCollection ();
90
91                                 return headers;
92                         }
93                         set { headers = value; }
94                 }
95
96                 public WebHeaderCollection ResponseHeaders {
97                         get { return responseHeaders; }
98                 }
99
100                 public Encoding Encoding {
101                         get { return encoding; }
102                         set {
103                                 if (value == null)
104                                         throw new ArgumentNullException ("value");
105                                 encoding = value;
106                         }
107                 }
108
109                 public bool IsBusy {
110                         get { return is_busy; }
111                 }
112
113                 [MonoTODO ("value is unused, current implementation always works like it's true (default)")]
114                 public bool AllowReadStreamBuffering {
115                         get { return allow_read_buffering; }
116                         set { allow_read_buffering = value; }
117                 }
118
119                 // new in SL4 RC
120                 [MonoTODO ("value is unused, current implementation always works like it's true (default)")]
121                 public bool AllowWriteStreamBuffering {
122                         get { return allow_write_buffering; }
123                         set { allow_write_buffering = value; }
124                 }
125
126                 public bool UseDefaultCredentials {
127                         get; set;
128                 }
129
130                 // Methods
131
132                 void CheckBusy ()
133                 {
134                         if (IsBusy)
135                                 throw new NotSupportedException ("WebClient does not support conccurent I/O operations.");
136                 }
137
138                 void SetBusy ()
139                 {
140                         lock (locker) {
141                                 CheckBusy ();
142                                 is_busy = true;
143                         }
144                 }
145
146                 private string DetermineMethod (Uri address, string method)
147                 {
148                         if (method != null)
149                                 return method;
150
151                         if (address.Scheme == Uri.UriSchemeFtp)
152                                 return "RETR";
153                         return "POST";
154                 }
155
156                 public event DownloadProgressChangedEventHandler DownloadProgressChanged;
157                 public event DownloadStringCompletedEventHandler DownloadStringCompleted;
158                 public event OpenReadCompletedEventHandler OpenReadCompleted;
159                 public event OpenWriteCompletedEventHandler OpenWriteCompleted;
160                 public event UploadProgressChangedEventHandler UploadProgressChanged;
161                 public event UploadStringCompletedEventHandler UploadStringCompleted;
162                 public event WriteStreamClosedEventHandler WriteStreamClosed;
163
164                 WebRequest SetupRequest (Uri uri, string method, CallbackData callbackData)
165                 {
166                         callback_data = callbackData;
167                         WebRequest request = GetWebRequest (uri);
168                         request.Method = DetermineMethod (uri, method);
169                         foreach (string header in Headers.AllKeys)
170                                 request.Headers.SetHeader (header, Headers [header]);
171                         return request;
172                 }
173
174                 Stream ProcessResponse (WebResponse response)
175                 {
176                         responseHeaders = response.Headers;
177                         HttpWebResponse hwr = (response as HttpWebResponse);
178                         if (hwr == null)
179                                 throw new NotSupportedException ();
180
181                         HttpStatusCode status_code = HttpStatusCode.NotFound;
182                         Stream s = null;
183                         try {
184                                 status_code = hwr.StatusCode;
185                                 if (status_code == HttpStatusCode.OK)
186                                         s = response.GetResponseStream ();
187                         }
188                         catch (Exception e) {
189                                 throw new WebException ("NotFound", e, WebExceptionStatus.UnknownError, response);
190                         }
191                         finally {
192                                 if (status_code != HttpStatusCode.OK)
193                                         throw new WebException ("NotFound", null, WebExceptionStatus.UnknownError, response);
194                         }
195                         return s;
196                 }
197
198                 public void CancelAsync ()
199                 {
200                         if (request != null)
201                                 request.Abort ();
202                 }
203
204                 void CompleteAsync ()
205                 {
206                         is_busy = false;
207                 }
208
209                 class CallbackData {
210                         public object user_token;
211                         public SynchronizationContext sync_context;
212                         public byte [] data;
213                         public CallbackData (object user_token, byte [] data)
214                         {
215                                 this.user_token = user_token;
216                                 this.data = data;
217                                 this.sync_context = SynchronizationContext.Current ?? new SynchronizationContext ();
218                         }
219                         public CallbackData (object user_token) : this (user_token, null)
220                         {
221                         }
222                 }
223
224                 //    DownloadStringAsync
225
226                 public void DownloadStringAsync (Uri address)
227                 {
228                         DownloadStringAsync (address, null);
229                 }
230
231                 public void DownloadStringAsync (Uri address, object userToken)
232                 {
233                         if (address == null)
234                                 throw new ArgumentNullException ("address");
235
236                         lock (locker) {
237                                 SetBusy ();
238
239                                 try {
240                                         request = SetupRequest (address, "GET", new CallbackData (userToken));
241                                         request.BeginGetResponse (new AsyncCallback (DownloadStringAsyncCallback), null);
242                                 }
243                                 catch (Exception e) {
244                                         WebException wex = new WebException ("Could not start operation.", e);
245                                         OnDownloadStringCompleted (
246                                                 new DownloadStringCompletedEventArgs (null, wex, false, userToken));
247                                 }
248                         }
249                 }
250
251                 private void DownloadStringAsyncCallback (IAsyncResult result)
252                 {
253                         string data = null;
254                         Exception ex = null;
255                         bool cancel = false;
256                         try {
257                                 WebResponse response = request.EndGetResponse (result);
258                                 Stream stream = ProcessResponse (response);
259
260                                 using (StreamReader sr = new StreamReader (stream, encoding, true)) {
261                                         data = sr.ReadToEnd ();
262                                 }
263                         }
264                         catch (WebException web) {
265                                 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
266                                 ex = web;
267                         }
268                         catch (Exception e) {
269                                 ex = e;
270                         }
271                         finally {
272                                 callback_data.sync_context.Post (delegate (object sender) {
273                                         OnDownloadStringCompleted (new DownloadStringCompletedEventArgs (data, ex, cancel, callback_data.user_token));
274                                 }, null);
275                         }
276                 }
277
278                 //    OpenReadAsync
279
280                 public void OpenReadAsync (Uri address)
281                 {
282                         OpenReadAsync (address, null);
283                 }
284
285                 public void OpenReadAsync (Uri address, object userToken)
286                 {
287                         if (address == null)
288                                 throw new ArgumentNullException ("address");
289
290                         lock (locker) {
291                                 SetBusy ();
292
293                                 try {
294                                         request = SetupRequest (address, "GET", new CallbackData (userToken));
295                                         request.BeginGetResponse (new AsyncCallback (OpenReadAsyncCallback), null);
296                                 }
297                                 catch (Exception e) {
298                                         WebException wex = new WebException ("Could not start operation.", e);
299                                         OnOpenReadCompleted (
300                                                 new OpenReadCompletedEventArgs (null, wex, false, userToken));
301                                 }
302                         }
303                 }
304
305                 private void OpenReadAsyncCallback (IAsyncResult result)
306                 {
307                         Stream stream = null;
308                         Exception ex = null;
309                         bool cancel = false;
310                         try {
311                                 WebResponse response = request.EndGetResponse (result);
312                                 stream = ProcessResponse (response);
313                         }
314                         catch (WebException web) {
315                                 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
316                                 ex = web;
317                         }
318                         catch (Exception e) {
319                                 ex = e;
320                         }
321                         finally {
322                                 callback_data.sync_context.Post (delegate (object sender) {
323                                         OnOpenReadCompleted (new OpenReadCompletedEventArgs (stream, ex, cancel, callback_data.user_token));
324                                 }, null);
325                         }
326                 }
327
328                 //    OpenWriteAsync
329
330                 public void OpenWriteAsync (Uri address)
331                 {
332                         OpenWriteAsync (address, null);
333                 }
334
335                 public void OpenWriteAsync (Uri address, string method)
336                 {
337                         OpenWriteAsync (address, method, null);
338                 }
339
340                 public void OpenWriteAsync (Uri address, string method, object userToken)
341                 {
342                         if (address == null)
343                                 throw new ArgumentNullException ("address");
344
345                         lock (locker) {
346                                 SetBusy ();
347
348                                 try {
349                                         request = SetupRequest (address, method, new CallbackData (userToken));
350                                         request.BeginGetRequestStream (new AsyncCallback (OpenWriteAsyncCallback), null);
351                                 }
352                                 catch (Exception e) {
353                                         WebException wex = new WebException ("Could not start operation.", e);
354                                         OnOpenWriteCompleted (
355                                                 new OpenWriteCompletedEventArgs (null, wex, false, userToken));
356                                 }
357                         }
358                 }
359
360                 private void OpenWriteAsyncCallback (IAsyncResult result)
361                 {
362                         Stream stream = null;
363                         Exception ex = null;
364                         bool cancel = false;
365                         InternalWebRequestStreamWrapper internal_stream;
366
367                         try {
368                                 stream = request.EndGetRequestStream (result);
369                                 internal_stream = (InternalWebRequestStreamWrapper) stream;
370                                 internal_stream.WebClient = this;
371                                 internal_stream.WebClientData = callback_data;
372                         }
373                         catch (WebException web) {
374                                 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
375                                 ex = web;
376                         }
377                         catch (Exception e) {
378                                 ex = e;
379                         }
380                         finally {
381                                 callback_data.sync_context.Post (delegate (object sender) {
382                                         OnOpenWriteCompleted (new OpenWriteCompletedEventArgs (stream, ex, cancel, callback_data.user_token));
383                                 }, null);
384                         }
385                 }
386
387                 internal void WriteStreamClosedCallback (object WebClientData)
388                 {
389                         try {
390                                 request.BeginGetResponse (OpenWriteAsyncResponseCallback, WebClientData);
391                         }
392                         catch (Exception e) {
393                                 callback_data.sync_context.Post (delegate (object sender) {
394                                         OnWriteStreamClosed (new WriteStreamClosedEventArgs (e));
395                                 }, null);
396                         }
397                 }
398
399                 private void OpenWriteAsyncResponseCallback (IAsyncResult result)
400                 {
401                         try {
402                                 WebResponse response = request.EndGetResponse (result);
403                                 ProcessResponse (response);
404                         }
405                         catch (Exception e) {
406                                 callback_data.sync_context.Post (delegate (object sender) {
407                                         OnWriteStreamClosed (new WriteStreamClosedEventArgs (e));
408                                 }, null);
409                         }
410                 }
411
412                 //    UploadStringAsync
413
414                 public void UploadStringAsync (Uri address, string data)
415                 {
416                         UploadStringAsync (address, null, data);
417                 }
418
419                 public void UploadStringAsync (Uri address, string method, string data)
420                 {
421                         UploadStringAsync (address, method, data, null);
422                 }
423
424                 public void UploadStringAsync (Uri address, string method, string data, object userToken)
425                 {
426                         if (address == null)
427                                 throw new ArgumentNullException ("address");
428                         if (data == null)
429                                 throw new ArgumentNullException ("data");
430
431                         lock (locker) {
432                                 SetBusy ();
433
434                                 try {
435                                         request = SetupRequest (address, method, new CallbackData (userToken, encoding.GetBytes (data)));
436                                         request.BeginGetRequestStream (new AsyncCallback (UploadStringRequestAsyncCallback), null);
437                                 }
438                                 catch (Exception e) {
439                                         WebException wex = new WebException ("Could not start operation.", e);
440                                         OnUploadStringCompleted (
441                                                 new UploadStringCompletedEventArgs (null, wex, false, userToken));
442                                 }
443                         }
444                 }
445
446                 private void UploadStringRequestAsyncCallback (IAsyncResult result)
447                 {
448                         try {
449                                 Stream stream = request.EndGetRequestStream (result);
450                                 stream.Write (callback_data.data, 0, callback_data.data.Length);
451                                 request.BeginGetResponse (new AsyncCallback (UploadStringResponseAsyncCallback), null);
452                         }
453                         catch {
454                                 request.Abort ();
455                                 throw;
456                         }
457                 }
458
459                 private void UploadStringResponseAsyncCallback (IAsyncResult result)
460                 {
461                         string data = null;
462                         Exception ex = null;
463                         bool cancel = false;
464                         try {
465                                 WebResponse response = request.EndGetResponse (result);
466                                 Stream stream = ProcessResponse (response);
467
468                                 using (StreamReader sr = new StreamReader (stream, encoding, true)) {
469                                         data = sr.ReadToEnd ();
470                                 }
471                         }
472                         catch (WebException web) {
473                                 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
474                                 ex = web;
475                         }
476                         catch (InvalidOperationException ioe) {
477                                 ex = new WebException ("An exception occurred during a WebClient request", ioe);
478                         }
479                         catch (Exception e) {
480                                 ex = e;
481                         }
482                         finally {
483                                 callback_data.sync_context.Post (delegate (object sender) {
484                                         OnUploadStringCompleted (new UploadStringCompletedEventArgs (data, ex, cancel, callback_data.user_token));
485                                 }, null);
486                         }
487                 }
488
489                 protected virtual void OnDownloadProgressChanged (DownloadProgressChangedEventArgs e)
490                 {
491                         DownloadProgressChangedEventHandler handler = DownloadProgressChanged;
492                         if (handler != null)
493                                 handler (this, e);
494                 }
495                 
496                 protected virtual void OnOpenReadCompleted (OpenReadCompletedEventArgs args)
497                 {
498                         CompleteAsync ();
499                         OpenReadCompletedEventHandler handler = OpenReadCompleted;
500                         if (handler != null)
501                                 handler (this, args);
502                 }
503
504                 protected virtual void OnDownloadStringCompleted (DownloadStringCompletedEventArgs args)
505                 {
506                         CompleteAsync ();
507                         DownloadStringCompletedEventHandler handler = DownloadStringCompleted;
508                         if (handler != null)
509                                 handler (this, args);
510                 }
511
512                 protected virtual void OnOpenWriteCompleted (OpenWriteCompletedEventArgs args)
513                 {
514                         CompleteAsync ();
515                         OpenWriteCompletedEventHandler handler = OpenWriteCompleted;
516                         if (handler != null)
517                                 handler (this, args);
518                 }
519
520                 protected virtual void OnUploadProgressChanged (UploadProgressChangedEventArgs e)
521                 {
522                         UploadProgressChangedEventHandler handler = UploadProgressChanged;
523                         if (handler != null)
524                                 handler (this, e);
525                 }
526
527                 protected virtual void OnUploadStringCompleted (UploadStringCompletedEventArgs args)
528                 {
529                         CompleteAsync ();
530                         UploadStringCompletedEventHandler handler = UploadStringCompleted;
531                         if (handler != null)
532                                 handler (this, args);
533                 }
534
535                 protected virtual void OnWriteStreamClosed (WriteStreamClosedEventArgs e)
536                 {
537                         CompleteAsync ();
538                         WriteStreamClosedEventHandler handler = WriteStreamClosed;
539                         if (handler != null)
540                                 handler (this, e);
541                 }
542
543                 protected virtual WebRequest GetWebRequest (Uri address)
544                 {
545                         if (address == null)
546                                 throw new ArgumentNullException ("address");
547
548                         // if the URI is relative then we use our base address URI to make an absolute one
549                         Uri uri = address.IsAbsoluteUri ? address : new Uri (new Uri (baseAddress), address);
550
551                         WebRequest request = WebRequest.Create (uri);
552
553                         request.progress = delegate (long read, long length) {
554                                 callback_data.sync_context.Post (delegate (object sender) {
555                                         OnDownloadProgressChanged (new DownloadProgressChangedEventArgs (read, length, callback_data.user_token));
556                                 }, null);
557
558                         };
559                         return request;
560                 }
561
562                 protected virtual WebResponse GetWebResponse (WebRequest request, IAsyncResult result)
563                 {
564                         return request.EndGetResponse (result);
565                 }
566         }
567 }
568