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