2006-01-26 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[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 //
8 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.Collections.Specialized;
34 using System.ComponentModel;
35 using System.IO;
36 using System.Runtime.InteropServices;
37 using System.Runtime.Serialization;
38 using System.Text;
39
40 namespace System.Net 
41 {
42         [ComVisible(true)]
43         public sealed class WebClient : Component
44         {
45                 static readonly string urlEncodedCType = "application/x-www-form-urlencoded";
46                 static byte [] hexBytes;
47                 ICredentials credentials;
48                 WebHeaderCollection headers;
49                 WebHeaderCollection responseHeaders;
50                 Uri baseAddress;
51                 string baseString;
52                 NameValueCollection queryString;
53 #if NET_2_0
54                 Encoding encoding = Encoding.Default;
55 #endif
56
57                 // Constructors
58                 static WebClient ()
59                 {
60                         hexBytes = new byte [16];
61                         int index = 0;
62                         for (int i = '0'; i <= '9'; i++, index++)
63                                 hexBytes [index] = (byte) i;
64
65                         for (int i = 'A'; i <= 'F'; i++, index++)
66                                 hexBytes [index] = (byte) i;
67                 }
68                 
69                 public WebClient ()
70                 {
71                 }
72                 
73                 // Properties
74                 
75                 public string BaseAddress {
76                         get {
77                                 if (baseString == null) {
78                                         if (baseAddress == null)
79                                                 return "";
80                                 }
81
82                                 baseString = baseAddress.ToString ();
83                                 return baseString;
84                         }
85                         
86                         set {
87                                 if (value == null || value == "") {
88                                         baseAddress = null;
89                                 } else {
90                                         baseAddress = new Uri (value);
91                                 }
92                         }
93                 }
94                 
95                 public ICredentials Credentials {
96                         get { return credentials; }
97                         set { credentials = value; }
98                 }
99
100                 public WebHeaderCollection Headers {
101                         get {
102                                 if (headers == null)
103                                         headers = new WebHeaderCollection ();
104
105                                 return headers;
106                         }
107                         set { headers = value; }
108                 }
109                 
110                 public NameValueCollection QueryString {
111                         get {
112                                 if (queryString == null)
113                                         queryString = new NameValueCollection ();
114
115                                 return queryString;
116                         }
117                         set { queryString = value; }
118                 }
119                 
120                 public WebHeaderCollection ResponseHeaders {
121                         get { return responseHeaders; }
122                 }
123
124 #if NET_2_0
125                 public Encoding Encoding {
126                         get { return encoding; }
127                         set {
128                                 if (value == null)
129                                         throw new ArgumentNullException ("value");
130                                 encoding = value;
131                         }
132                 }
133 #endif
134
135                 // Methods
136                 
137                 public byte [] DownloadData (string address)
138                 {
139                         return DownloadData (address, "GET");
140                 }
141
142 #if NET_2_0
143                 public
144 #endif
145                 byte [] DownloadData (Uri address)
146                 {
147                         return DownloadData (address, "GET");
148                 }
149                 
150 #if NET_2_0
151                 public
152 #endif
153                 byte [] DownloadData (string address, string method)
154                 {
155                         return DownloadData (MakeUri (address), method);
156                 }
157
158 #if NET_2_0
159                 public
160 #endif
161                 byte [] DownloadData (Uri address, string method)
162                 {
163                         WebRequest request = SetupRequest (address, method);
164                         request.Method = method;
165                         WebResponse response = request.GetResponse ();
166                         Stream st = ProcessResponse (response);
167                         return ReadAll (st, (int) response.ContentLength);
168                 }
169
170                 public void DownloadFile (string address, string fileName)
171                 {
172                         DownloadFile (MakeUri (address), fileName);
173                 }
174
175 #if NET_2_0
176                 public
177 #endif
178                 void DownloadFile (Uri address, string fileName)
179                 {
180                         WebRequest request = SetupRequest (address);
181                         WebResponse response = request.GetResponse ();
182                         Stream st = ProcessResponse (response);
183
184                         int cLength = (int) response.ContentLength;
185                         int length = (cLength <= -1 || cLength > 8192) ? 8192 : cLength;
186                         byte [] buffer = new byte [length];
187                         FileStream f = new FileStream (fileName, FileMode.CreateNew);
188
189                         int nread = 0;
190                         while ((nread = st.Read (buffer, 0, length)) != 0)
191                                 f.Write (buffer, 0, nread);
192
193                         f.Close ();
194                 }
195                 
196                 public Stream OpenRead (string address)
197                 {
198                         return OpenRead (MakeUri (address));
199                 }
200
201 #if NET_2_0
202                 public
203 #endif
204                 Stream OpenRead (Uri address)
205                 {
206                         WebRequest request = SetupRequest (address);
207                         WebResponse response = request.GetResponse ();
208                         return ProcessResponse (response);
209                 }
210                 
211                 public Stream OpenWrite (string address)
212                 {
213                         return OpenWrite (address, "POST");
214                 }
215                 
216                 public Stream OpenWrite (string address, string method)
217                 {
218                         return OpenWrite (MakeUri (address), method);
219                 }
220
221 #if NET_2_0
222                 public
223 #endif
224                 Stream OpenWrite (Uri address, string method)
225                 {
226                         WebRequest request = SetupRequest (address, method);
227                         return request.GetRequestStream ();
228                 }
229                                 
230                 public byte [] UploadData (string address, byte [] data)
231                 {
232                         return UploadData (address, "POST", data);
233                 }
234                 
235                 public byte [] UploadData (string address, string method, byte [] data)
236                 {
237                         return UploadData (MakeUri (address), method, data);
238                 }
239
240 #if NET_2_0
241                 public byte [] UploadData (Uri address, byte [] data)
242                 {
243                         return UploadData (address, "POST", data);
244                 }
245 #endif
246
247 #if NET_2_0
248                 public
249 #endif
250                 byte [] UploadData (Uri address, string method, byte [] data)
251                 {
252                         if (data == null)
253                                 throw new ArgumentNullException ("data");
254
255                         int contentLength = data.Length;
256                         WebRequest request = SetupRequest (address, method, contentLength);
257                         using (Stream stream = request.GetRequestStream ()) {
258                                 stream.Write (data, 0, contentLength);
259                         }
260
261                         WebResponse response = request.GetResponse ();
262                         Stream st = ProcessResponse (response);
263                         return ReadAll (st, (int) response.ContentLength);
264                 }
265                 
266                 public byte [] UploadFile (string address, string fileName)
267                 {
268                         return UploadFile (address, "POST", fileName);
269                 }
270                 
271                 public byte [] UploadFile (string address, string method, string fileName)
272                 {
273                         return UploadFile (MakeUri (address), method, fileName);
274                 }
275
276 #if NET_2_0
277                 public
278 #endif
279                 byte [] UploadFile (Uri address, string method, string fileName)
280                 {
281                         string fileCType = Headers ["Content-Type"];
282                         if (fileCType != null) {
283                                 string lower = fileCType.ToLower ();
284                                 if (lower.StartsWith ("multipart/"))
285                                         throw new WebException ("Content-Type cannot be set to a multipart" +
286                                                                 " type for this request.");
287                         } else {
288                                 fileCType = "application/octet-stream";
289                         }
290
291                         string boundary = "------------" + DateTime.Now.Ticks.ToString ("x");
292                         Headers ["Content-Type"] = String.Format ("multipart/form-data; boundary={0}", boundary);
293                         WebRequest request = SetupRequest (address, method);
294                         Stream reqStream = null;
295                         Stream fStream = null;
296                         byte [] resultBytes = null;
297
298                         try {
299                                 fStream = File.OpenRead (fileName);
300                                 reqStream = request.GetRequestStream ();
301                                 byte [] realBoundary = Encoding.ASCII.GetBytes ("--" + boundary + "\r\n");
302                                 reqStream.Write (realBoundary, 0, realBoundary.Length);
303                                 string partHeaders = String.Format ("Content-Disposition: form-data; " +
304                                                                     "name=\"file\"; filename=\"{0}\"\r\n" +
305                                                                     "Content-Type: {1}\r\n\r\n",
306                                                                     Path.GetFileName (fileName), fileCType);
307
308                                 byte [] partHeadersBytes = Encoding.UTF8.GetBytes (partHeaders);
309                                 reqStream.Write (partHeadersBytes, 0, partHeadersBytes.Length);
310                                 int nread;
311                                 byte [] buffer = new byte [4096];
312                                 while ((nread = fStream.Read (buffer, 0, 4096)) != 0)
313                                         reqStream.Write (buffer, 0, nread);
314
315                                 reqStream.WriteByte ((byte) '\r');
316                                 reqStream.WriteByte ((byte) '\n');
317                                 reqStream.Write (realBoundary, 0, realBoundary.Length);
318                                 reqStream.Close ();
319                                 reqStream = null;
320                                 WebResponse response = request.GetResponse ();
321                                 Stream st = ProcessResponse (response);
322                                 resultBytes = ReadAll (st, (int) response.ContentLength);
323                         } catch (WebException) {
324                                 throw;
325                         } catch (Exception e) {
326                                 throw new WebException ("Error uploading file.", e);
327                         } finally {
328                                 if (fStream != null)
329                                         fStream.Close ();
330
331                                 if (reqStream != null)
332                                         reqStream.Close ();
333                         }
334                         
335                         return resultBytes;     
336                 }
337                 
338                 public byte[] UploadValues (string address, NameValueCollection data)
339                 {
340                         return UploadValues (address, "POST", data);
341                 }
342                 
343                 public byte[] UploadValues (string address, string method, NameValueCollection data)
344                 {
345                         return UploadValues (MakeUri (address), method, data);
346                 }
347
348 #if NET_2_0
349                 public
350 #endif
351                 byte[] UploadValues (Uri uri, string method, NameValueCollection data)
352                 {
353                         if (data == null)
354                                 throw new ArgumentNullException ("data"); // MS throws a nullref
355
356                         string cType = Headers ["Content-Type"];
357                         if (cType != null && String.Compare (cType, urlEncodedCType, true) != 0)
358                                 throw new WebException ("Content-Type header cannot be changed from its default " +
359                                                         "value for this request.");
360
361                         Headers ["Content-Type"] = urlEncodedCType;
362                         WebRequest request = SetupRequest (uri, method);
363                         Stream rqStream = request.GetRequestStream ();
364                         MemoryStream tmpStream = new MemoryStream ();
365                         foreach (string key in data) {
366                                 byte [] bytes = Encoding.ASCII.GetBytes (key);
367                                 UrlEncodeAndWrite (tmpStream, bytes);
368                                 tmpStream.WriteByte ((byte) '=');
369                                 bytes = Encoding.ASCII.GetBytes (data [key]);
370                                 UrlEncodeAndWrite (tmpStream, bytes);
371                                 tmpStream.WriteByte ((byte) '&');
372                         }
373
374                         int length = (int) tmpStream.Length;
375                         if (length > 0)
376                                 tmpStream.SetLength (--length); // remove trailing '&'
377
378                         tmpStream.WriteByte ((byte) '\r');
379                         tmpStream.WriteByte ((byte) '\n');
380
381                         byte [] buf = tmpStream.GetBuffer ();
382                         rqStream.Write (buf, 0, length + 2);
383                         rqStream.Close ();
384                         tmpStream.Close ();
385
386                         WebResponse response = request.GetResponse ();
387                         Stream st = ProcessResponse (response);
388                         return ReadAll (st, (int) response.ContentLength);
389                 }
390
391 #if NET_2_0
392                 public string DownloadString (string address)
393                 {
394                         return encoding.GetString (DownloadData (address));
395                 }
396
397                 public string DownloadString (string address, string method)
398                 {
399                         return encoding.GetString (DownloadData (address, method));
400                 }
401
402                 public string DownloadString (Uri address)
403                 {
404                         return encoding.GetString (DownloadData (address));
405                 }
406
407                 public string DownloadString (Uri address, string method)
408                 {
409                         return encoding.GetString (DownloadData (address, method));
410                 }
411
412                 public string UploadString (string address, string data)
413                 {
414                         byte [] resp = UploadData (address, encoding.GetBytes (data));
415                         return encoding.GetString (resp);
416                 }
417
418                 public string UploadString (string address, string method, string data)
419                 {
420                         byte [] resp = UploadData (address, method, encoding.GetBytes (data));
421                         return encoding.GetString (resp);
422                 }
423
424                 public string UploadString (Uri address, string data)
425                 {
426                         byte [] resp = UploadData (address, encoding.GetBytes (data));
427                         return encoding.GetString (resp);
428                 }
429
430                 public string UploadString (Uri address, string method, string data)
431                 {
432                         byte [] resp = UploadData (address, method, encoding.GetBytes (data));
433                         return encoding.GetString (resp);
434                 }
435 #endif
436
437                 Uri MakeUri (string path)
438                 {
439                         string query = null;
440                         if (queryString != null && queryString.Count != 0) {
441                                 // This is not the same as UploadValues, because these 'keys' are not
442                                 // urlencoded here.
443                                 StringBuilder sb = new StringBuilder ();
444                                 sb.Append ('?');
445                                 foreach (string key in queryString)
446                                         sb.AppendFormat ("{0}={1}&", key, UrlEncode (queryString [key]));
447
448                                 if (sb.Length != 0) {
449                                         sb.Length--; // remove trailing '&'
450                                         query = sb.ToString ();
451                                 }
452                         }
453                         
454
455                         if (baseAddress == null && query == null) {
456                                 try {
457                                         return new Uri (path);
458                                 }
459                                 catch (System.UriFormatException) {
460                                         if ((path[0] == Path.DirectorySeparatorChar) || (path[1] == ':' && Char.ToLower(path[0]) > 'a' && Char.ToLower(path[0]) < 'z')) {
461                                                 return new Uri ("file://" + path);
462                                         }
463                                         else {
464                                                 return new Uri ("file://" + Environment.CurrentDirectory + Path.DirectorySeparatorChar + path);
465                                         }
466                                 }
467                         }
468
469                         if (baseAddress == null)
470                                 return new Uri (path + query, (query != null));
471
472                         if (query == null)
473                                 return new Uri (baseAddress, path);
474
475                         return new Uri (baseAddress, path + query, (query != null));
476                 }
477                 
478                 WebRequest SetupRequest (Uri uri)
479                 {
480                         WebRequest request = WebRequest.Create (uri);
481                         request.Credentials = credentials;
482
483                         // Special headers. These are properties of HttpWebRequest.
484                         // What do we do with other requests differnt from HttpWebRequest?
485                         if (headers != null && headers.Count != 0 && (request is HttpWebRequest)) {
486                                 HttpWebRequest req = (HttpWebRequest) request;
487                                 string expect = headers ["Expect"];
488                                 string contentType = headers ["Content-Type"];
489                                 string accept = headers ["Accept"];
490                                 string connection = headers ["Connection"];
491                                 string userAgent = headers ["User-Agent"];
492                                 string referer = headers ["Referer"];
493                                 headers.RemoveInternal ("Expect");
494                                 headers.RemoveInternal ("Content-Type");
495                                 headers.RemoveInternal ("Accept");
496                                 headers.RemoveInternal ("Connection");
497                                 headers.RemoveInternal ("Referer");
498                                 headers.RemoveInternal ("User-Agent");
499                                 request.Headers = headers;
500
501                                 if (expect != null && expect != "")
502                                         req.Expect = expect;
503
504                                 if (accept != null && accept != "")
505                                         req.Accept = accept;
506
507                                 if (contentType != null && contentType != "")
508                                         req.ContentType = contentType;
509
510                                 if (connection != null && connection != "")
511                                         req.Connection = connection;
512
513                                 if (userAgent != null && userAgent != "")
514                                         req.UserAgent = userAgent;
515
516                                 if (referer != null && referer != "")
517                                         req.Referer = referer;
518                         }
519
520                         responseHeaders = null;
521                         return request;
522                 }
523
524                 WebRequest SetupRequest (Uri uri, string method)
525                 {
526                         WebRequest request = SetupRequest (uri);
527                         request.Method = method;
528                         return request;
529                 }
530
531                 WebRequest SetupRequest (Uri uri, string method, int contentLength)
532                 {
533                         WebRequest request = SetupRequest (uri, method);
534                         request.ContentLength = contentLength;
535                         return request;
536                 }
537
538                 Stream ProcessResponse (WebResponse response)
539                 {
540                         responseHeaders = response.Headers;
541                         return response.GetResponseStream ();
542                 }
543
544                 static byte [] ReadAll (Stream stream, int length)
545                 {
546                         MemoryStream ms = null;
547                         
548                         bool nolength = (length == -1);
549                         int size = ((nolength) ? 8192 : length);
550                         if (nolength)
551                                 ms = new MemoryStream ();
552
553                         int nread = 0;
554                         int offset = 0;
555                         byte [] buffer = new byte [size];
556                         while ((nread = stream.Read (buffer, offset, size)) != 0) {
557                                 if (nolength) {
558                                         ms.Write (buffer, 0, nread);
559                                 } else {
560                                         offset += nread;
561                                         size -= nread;
562                                 }
563                         }
564
565                         if (nolength)
566                                 return ms.ToArray ();
567
568                         return buffer;
569                 }
570
571                 string UrlEncode (string str)
572                 {
573                         StringBuilder result = new StringBuilder ();
574
575                         int len = str.Length;
576                         for (int i = 0; i < len; i++) {
577                                 char c = str [i];
578                                 if (c == ' ')
579                                         result.Append ('+');
580                                 else if ((c < '0' && c != '-' && c != '.') ||
581                                          (c < 'A' && c > '9') ||
582                                          (c > 'Z' && c < 'a' && c != '_') ||
583                                          (c > 'z')) {
584                                         result.Append ('%');
585                                         int idx = ((int) c) >> 4;
586                                         result.Append ((char) hexBytes [idx]);
587                                         idx = ((int) c) & 0x0F;
588                                         result.Append ((char) hexBytes [idx]);
589                                 } else {
590                                         result.Append (c);
591                                 }
592                         }
593
594                         return result.ToString ();
595                 }
596
597                 static void UrlEncodeAndWrite (Stream stream, byte [] bytes)
598                 {
599                         if (bytes == null)
600                                 return;
601
602                         int len = bytes.Length;
603                         if (len == 0)
604                                 return;
605
606                         for (int i = 0; i < len; i++) {
607                                 char c = (char) bytes [i];
608                                 if (c == ' ')
609                                         stream.WriteByte ((byte) '+');
610                                 else if ((c < '0' && c != '-' && c != '.') ||
611                                          (c < 'A' && c > '9') ||
612                                          (c > 'Z' && c < 'a' && c != '_') ||
613                                          (c > 'z')) {
614                                         stream.WriteByte ((byte) '%');
615                                         int idx = ((int) c) >> 4;
616                                         stream.WriteByte (hexBytes [idx]);
617                                         idx = ((int) c) & 0x0F;
618                                         stream.WriteByte (hexBytes [idx]);
619                                 } else {
620                                         stream.WriteByte ((byte) c);
621                                 }
622                         }
623                 }
624         }
625 }
626