2 // System.Net.WebClient
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)
11 // Copyright 2003 Ximian, Inc. (http://www.ximian.com)
12 // Copyright 2006, 2008, 2009-2010 Novell, Inc. (http://www.novell.com)
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:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
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.
36 using System.Security;
38 using System.Threading;
40 namespace System.Net {
42 // note: this type is effectively sealed to transparent code since it's default .ctor is marked with [SecuritySafeCritical]
43 public class WebClient {
45 WebHeaderCollection headers;
46 WebHeaderCollection responseHeaders;
47 bool response_supports_headers = true; // will be set to false, if needed, when we get a response
51 Encoding encoding = Encoding.UTF8;
52 bool allow_read_buffering = true;
53 bool allow_write_buffering = true;
56 CallbackData callback_data;
61 // kind of calling NativeMethods.plugin_instance_get_source_location (PluginHost.Handle)
62 // but without adding dependency on System.Windows.dll. GetData is [SecurityCritical]
63 // this makes the default .ctor [SecuritySafeCritical] which would be a problem (inheritance)
64 // but it happens that MS SL2 also has this default .ctor as SSC :-)
65 BaseAddress = (AppDomain.CurrentDomain.GetData ("xap_uri") as string);
66 locker = new object ();
67 UseDefaultCredentials = true;
72 public string BaseAddress {
73 get { return baseAddress; }
75 if (String.IsNullOrEmpty (value)) {
76 baseAddress = String.Empty;
77 base_address_uri = null;
79 if (!Uri.TryCreate (value, UriKind.Absolute, out base_address_uri))
80 throw new ArgumentException ("Invalid URI");
82 baseAddress = Uri.UnescapeDataString (base_address_uri.AbsoluteUri);
87 [MonoTODO ("provide credentials to the client stack")]
88 public ICredentials Credentials { get; set; }
90 // this is an unvalidated collection, HttpWebRequest is responsable to validate it
91 public WebHeaderCollection Headers {
94 headers = new WebHeaderCollection ();
98 set { headers = value; }
101 public WebHeaderCollection ResponseHeaders {
103 if (!response_supports_headers)
104 throw new NotImplementedException ();
105 return responseHeaders;
109 public Encoding Encoding {
110 get { return encoding; }
113 throw new ArgumentNullException ("value");
119 get { return is_busy; }
122 [MonoTODO ("value is unused, current implementation always works like it's true (default)")]
123 public bool AllowReadStreamBuffering {
124 get { return allow_read_buffering; }
125 set { allow_read_buffering = value; }
129 [MonoTODO ("value is unused, current implementation always works like it's true (default)")]
130 public bool AllowWriteStreamBuffering {
131 get { return allow_write_buffering; }
132 set { allow_write_buffering = value; }
135 public bool UseDefaultCredentials {
144 throw new NotSupportedException ("WebClient does not support concurrent I/O operations.");
155 private string DetermineMethod (Uri address, string method)
160 if (address.Scheme == Uri.UriSchemeFtp)
165 public event DownloadProgressChangedEventHandler DownloadProgressChanged;
166 public event DownloadStringCompletedEventHandler DownloadStringCompleted;
167 public event OpenReadCompletedEventHandler OpenReadCompleted;
168 public event OpenWriteCompletedEventHandler OpenWriteCompleted;
169 public event UploadProgressChangedEventHandler UploadProgressChanged;
170 public event UploadStringCompletedEventHandler UploadStringCompleted;
171 public event WriteStreamClosedEventHandler WriteStreamClosed;
173 WebRequest SetupRequest (Uri uri, string method, CallbackData callbackData)
175 callback_data = callbackData;
176 WebRequest request = GetWebRequest (uri);
177 // do not send a relative URI to Determine method
178 request.Method = DetermineMethod (request.RequestUri, method);
179 // copy headers to the request - some needs special treatments
180 foreach (string header in Headers) {
181 switch (header.ToLowerInvariant ()) {
182 case "content-length":
184 if (Int64.TryParse (Headers [header], out cl) && (cl >= 0))
185 request.ContentLength = cl;
189 // this skip the normal/user validation on headers
190 request.Headers.SetHeader (header, Headers [header]);
193 request.Headers [header] = Headers [header];
197 // original headers are removed after calls
202 Stream ProcessResponse (WebResponse response)
204 response_supports_headers = response.SupportsHeaders;
205 if (response_supports_headers)
206 responseHeaders = response.Headers;
208 HttpWebResponse hwr = (response as HttpWebResponse);
210 throw new NotSupportedException ();
212 HttpStatusCode status_code = HttpStatusCode.NotFound;
215 status_code = hwr.StatusCode;
216 if (status_code == HttpStatusCode.OK)
217 s = response.GetResponseStream ();
219 catch (Exception e) {
220 throw new WebException ("NotFound", status_code == HttpStatusCode.OK ? e : null,
221 WebExceptionStatus.UnknownError, response);
226 public void CancelAsync ()
233 void CompleteAsync ()
239 internal class CallbackData {
240 public object user_token;
241 public SynchronizationContext sync_context;
243 public CallbackData (object user_token, byte [] data)
245 this.user_token = user_token;
247 this.sync_context = SynchronizationContext.Current ?? new SynchronizationContext ();
249 public CallbackData (object user_token) : this (user_token, null)
254 // DownloadStringAsync
256 public void DownloadStringAsync (Uri address)
258 DownloadStringAsync (address, null);
261 public void DownloadStringAsync (Uri address, object userToken)
264 throw new ArgumentNullException ("address");
270 request = SetupRequest (address, "GET", new CallbackData (userToken));
271 request.BeginGetResponse (new AsyncCallback (DownloadStringAsyncCallback), null);
273 catch (Exception e) {
274 WebException wex = new WebException ("Could not start operation.", e);
275 OnDownloadStringCompleted (
276 new DownloadStringCompletedEventArgs (null, wex, false, userToken));
281 private void DownloadStringAsyncCallback (IAsyncResult result)
287 WebResponse response = request.EndGetResponse (result);
288 Stream stream = ProcessResponse (response);
290 using (StreamReader sr = new StreamReader (stream, encoding, true)) {
291 data = sr.ReadToEnd ();
294 catch (WebException web) {
295 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
298 catch (SecurityException se) {
299 // SecurityException inside a SecurityException (not a WebException) for SL compatibility
300 ex = new SecurityException (String.Empty, se);
302 catch (Exception e) {
303 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
306 callback_data.sync_context.Post (delegate (object sender) {
307 OnDownloadStringCompleted (new DownloadStringCompletedEventArgs (data, ex, cancel, callback_data.user_token));
314 public void OpenReadAsync (Uri address)
316 OpenReadAsync (address, null);
319 public void OpenReadAsync (Uri address, object userToken)
322 throw new ArgumentNullException ("address");
328 request = SetupRequest (address, "GET", new CallbackData (userToken));
329 request.BeginGetResponse (new AsyncCallback (OpenReadAsyncCallback), null);
331 catch (Exception e) {
332 WebException wex = new WebException ("Could not start operation.", e);
333 OnOpenReadCompleted (
334 new OpenReadCompletedEventArgs (null, wex, false, userToken));
339 private void OpenReadAsyncCallback (IAsyncResult result)
341 Stream stream = null;
345 WebResponse response = request.EndGetResponse (result);
346 stream = ProcessResponse (response);
348 catch (WebException web) {
349 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
352 catch (SecurityException se) {
353 // SecurityException inside a SecurityException (not a WebException) for SL compatibility
354 ex = new SecurityException (String.Empty, se);
356 catch (Exception e) {
357 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
360 callback_data.sync_context.Post (delegate (object sender) {
361 OnOpenReadCompleted (new OpenReadCompletedEventArgs (stream, ex, cancel, callback_data.user_token));
368 public void OpenWriteAsync (Uri address)
370 OpenWriteAsync (address, null);
373 public void OpenWriteAsync (Uri address, string method)
375 OpenWriteAsync (address, method, null);
378 public void OpenWriteAsync (Uri address, string method, object userToken)
381 throw new ArgumentNullException ("address");
387 request = SetupRequest (address, method, new CallbackData (userToken));
388 request.BeginGetRequestStream (new AsyncCallback (OpenWriteAsyncCallback), null);
390 catch (Exception e) {
391 WebException wex = new WebException ("Could not start operation.", e);
392 OnOpenWriteCompleted (
393 new OpenWriteCompletedEventArgs (null, wex, false, userToken));
398 private void OpenWriteAsyncCallback (IAsyncResult result)
400 Stream stream = null;
403 InternalWebRequestStreamWrapper internal_stream;
406 stream = request.EndGetRequestStream (result);
407 internal_stream = (InternalWebRequestStreamWrapper) stream;
408 internal_stream.WebClient = this;
409 internal_stream.WebClientData = callback_data;
411 catch (WebException web) {
412 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
415 catch (Exception e) {
416 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
419 callback_data.sync_context.Post (delegate (object sender) {
420 OnOpenWriteCompleted (new OpenWriteCompletedEventArgs (stream, ex, cancel, callback_data.user_token));
425 internal void WriteStreamClosedCallback (object WebClientData, long length)
428 request.BeginGetResponse (OpenWriteAsyncResponseCallback, WebClientData);
430 catch (Exception e) {
431 callback_data.sync_context.Post (delegate (object sender) {
432 OnWriteStreamClosed (new WriteStreamClosedEventArgs (e));
436 // kind of dummy, 0% progress, that is always emitted
437 upload_length = length;
438 callback_data.sync_context.Post (delegate (object sender) {
439 OnUploadProgressChanged (
440 new UploadProgressChangedEventArgs (0, -1, length, -1, 0, callback_data.user_token));
445 private void OpenWriteAsyncResponseCallback (IAsyncResult result)
449 WebResponse response = request.EndGetResponse (result);
450 ProcessResponse (response);
452 catch (SecurityException se) {
453 // SecurityException inside a SecurityException (not a WebException) for SL compatibility
454 ex = new SecurityException (String.Empty, se);
456 catch (Exception e) {
457 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
460 callback_data.sync_context.Post (delegate (object sender) {
461 OnWriteStreamClosed (new WriteStreamClosedEventArgs (ex));
468 public void UploadStringAsync (Uri address, string data)
470 UploadStringAsync (address, null, data);
473 public void UploadStringAsync (Uri address, string method, string data)
475 UploadStringAsync (address, method, data, null);
478 public void UploadStringAsync (Uri address, string method, string data, object userToken)
481 throw new ArgumentNullException ("address");
483 throw new ArgumentNullException ("data");
489 CallbackData cbd = new CallbackData (userToken, encoding.GetBytes (data));
490 request = SetupRequest (address, method, cbd);
491 request.BeginGetRequestStream (new AsyncCallback (UploadStringRequestAsyncCallback), cbd);
493 catch (Exception e) {
494 WebException wex = new WebException ("Could not start operation.", e);
495 OnUploadStringCompleted (
496 new UploadStringCompletedEventArgs (null, wex, false, userToken));
501 private void UploadStringRequestAsyncCallback (IAsyncResult result)
504 Stream stream = request.EndGetRequestStream (result);
505 stream.Write (callback_data.data, 0, callback_data.data.Length);
506 request.BeginGetResponse (new AsyncCallback (UploadStringResponseAsyncCallback), null);
513 // kind of dummy, 0% progress, that is always emitted
514 upload_length = callback_data.data.Length;
515 callback_data.sync_context.Post (delegate (object sender) {
516 OnUploadProgressChanged (
517 new UploadProgressChangedEventArgs (0, -1, upload_length, -1, 0, callback_data.user_token));
522 private void UploadStringResponseAsyncCallback (IAsyncResult result)
528 WebResponse response = request.EndGetResponse (result);
529 Stream stream = ProcessResponse (response);
531 using (StreamReader sr = new StreamReader (stream, encoding, true)) {
532 data = sr.ReadToEnd ();
535 catch (WebException web) {
536 cancel = (web.Status == WebExceptionStatus.RequestCanceled);
539 catch (SecurityException se) {
540 // SecurityException inside a SecurityException (not a WebException) for SL compatibility
541 ex = new SecurityException (String.Empty, se);
543 catch (Exception e) {
544 ex = new WebException ("Could not complete operation.", e, WebExceptionStatus.UnknownError, null);
547 callback_data.sync_context.Post (delegate (object sender) {
548 OnUploadStringCompleted (new UploadStringCompletedEventArgs (data, ex, cancel, callback_data.user_token));
553 protected virtual void OnDownloadProgressChanged (DownloadProgressChangedEventArgs e)
555 DownloadProgressChangedEventHandler handler = DownloadProgressChanged;
560 protected virtual void OnOpenReadCompleted (OpenReadCompletedEventArgs e)
563 OpenReadCompletedEventHandler handler = OpenReadCompleted;
568 protected virtual void OnDownloadStringCompleted (DownloadStringCompletedEventArgs e)
571 DownloadStringCompletedEventHandler handler = DownloadStringCompleted;
576 protected virtual void OnOpenWriteCompleted (OpenWriteCompletedEventArgs e)
579 OpenWriteCompletedEventHandler handler = OpenWriteCompleted;
584 protected virtual void OnUploadProgressChanged (UploadProgressChangedEventArgs e)
586 UploadProgressChangedEventHandler handler = UploadProgressChanged;
591 protected virtual void OnUploadStringCompleted (UploadStringCompletedEventArgs e)
594 UploadStringCompletedEventHandler handler = UploadStringCompleted;
599 protected virtual void OnWriteStreamClosed (WriteStreamClosedEventArgs e)
602 WriteStreamClosedEventHandler handler = WriteStreamClosed;
607 protected virtual WebRequest GetWebRequest (Uri address)
610 throw new ArgumentNullException ("address");
612 // if the URI is relative then we use our base address URI to make an absolute one
613 Uri uri = address.IsAbsoluteUri || base_address_uri == null ? address : new Uri (base_address_uri, address);
615 HttpWebRequest request = (HttpWebRequest) WebRequest.Create (uri);
616 request.AllowReadStreamBuffering = AllowReadStreamBuffering;
617 request.AllowWriteStreamBuffering = AllowWriteStreamBuffering;
618 request.UseDefaultCredentials = UseDefaultCredentials;
620 request.progress = delegate (long read, long length) {
621 callback_data.sync_context.Post (delegate (object sender) {
622 if (upload_length > 0) {
623 // always emitted as 50% with an unknown (-1) TotalBytesToSend
624 OnUploadProgressChanged (new UploadProgressChangedEventArgs (read, length,
625 upload_length, -1, 50, callback_data.user_token));
627 OnDownloadProgressChanged (new DownloadProgressChangedEventArgs (read, length,
628 callback_data.user_token));
635 protected virtual WebResponse GetWebResponse (WebRequest request, IAsyncResult result)
637 return request.EndGetResponse (result);