removed writeline
[mono.git] / mcs / class / System / System.Net / HttpWebRequest.cs
1 //
2 // System.Net.HttpWebRequest
3 //
4 // Authors:
5 //      Lawrence Pit (loz@cable.a2000.nl)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (c) 2002 Lawrence Pit
9 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
10 //
11
12 using System;
13 using System.Collections;
14 using System.IO;
15 using System.Net.Sockets;
16 using System.Runtime.Remoting.Messaging;
17 using System.Runtime.Serialization;
18 using System.Security.Cryptography.X509Certificates;
19 using System.Text;
20 using System.Threading;
21
22 namespace System.Net 
23 {
24         [Serializable]
25         public class HttpWebRequest : WebRequest, ISerializable, IDisposable
26         {
27                 private Uri requestUri;
28                 private Uri actualUri = null;
29                 private bool allowAutoRedirect = true;
30                 private bool allowBuffering = true;
31                 private X509CertificateCollection certificate = null;
32                 private string connectionGroup = null;
33                 private long contentLength = -1;
34                 private HttpContinueDelegate continueDelegate = null;
35                 private CookieContainer cookieContainer = null;
36                 private ICredentials credentials = null;
37                 private bool haveResponse = false;              
38                 private WebHeaderCollection webHeaders;
39                 private bool keepAlive = true;
40                 private int maxAutoRedirect = 50;
41                 private string mediaType = String.Empty;
42                 private string method;
43                 private bool pipelined = true;
44                 private bool preAuthenticate = false;
45                 private Version version;                
46                 private IWebProxy proxy;
47                 private bool sendChunked = false;
48                 private ServicePoint servicePoint = null;
49                 private int timeout = System.Threading.Timeout.Infinite;
50                 
51                 private HttpWebRequestStream requestStream = null;
52                 private HttpWebResponse webResponse = null;
53                 private AutoResetEvent requestEndEvent = null;
54                 private bool requesting = false;
55                 private bool asyncResponding = false;
56                 private Socket socket;
57                 private bool requestClosed = false;
58                 private bool responseClosed = false;
59                 private bool disposed;
60                 
61                 // Constructors
62                 
63                 internal HttpWebRequest (Uri uri) 
64                 { 
65                         this.requestUri = uri;
66                         this.actualUri = uri;
67                         this.webHeaders = new WebHeaderCollection (true);
68                         this.webHeaders.SetInternal ("Host", uri.Authority);
69                         this.webHeaders.SetInternal ("Date", DateTime.Now.ToUniversalTime ().ToString ("r", null));
70                         this.webHeaders.SetInternal ("Expect", "100-continue");
71                         this.method = "GET";
72                         this.version = HttpVersion.Version11;
73                         this.proxy = GlobalProxySelection.Select;
74                 }               
75                 
76                 [MonoTODO]
77                 protected HttpWebRequest (SerializationInfo serializationInfo, StreamingContext streamingContext) 
78                 {
79                         throw new NotImplementedException ();
80                 }
81                 
82                 // Properties
83                 
84                 public string Accept {
85                         get { return webHeaders ["Accept"]; }
86                         set {
87                                 CheckRequestStarted ();
88                                 webHeaders.SetInternal ("Accept", value);
89                         }
90                 }
91                 
92                 public Uri Address {
93                         get { return actualUri; }
94                 }
95                 
96                 public bool AllowAutoRedirect {
97                         get { return allowAutoRedirect; }
98                         set { this.allowAutoRedirect = value; }
99                 }
100                 
101                 public bool AllowWriteStreamBuffering {
102                         get { return allowBuffering; }
103                         set { this.allowBuffering = value; }
104                 }
105                 
106                 public X509CertificateCollection ClientCertificates {
107                         get { return certificate; }
108                 }
109                 
110                 public string Connection {
111                         get { return webHeaders ["Connection"]; }
112                         set {
113                                 CheckRequestStarted ();
114                                 string val = value;
115                                 if (val != null) 
116                                         val = val.Trim ().ToLower ();
117                                 if (val == null || val.Length == 0) {
118                                         webHeaders.RemoveInternal ("Connection");
119                                         return;
120                                 }
121                                 if (val == "keep-alive" || val == "close") 
122                                         throw new ArgumentException ("value");
123                                 if (KeepAlive && val.IndexOf ("keep-alive") == -1)
124                                         value = value + ", Keep-Alive";
125                                 
126                                 webHeaders.SetInternal ("Connection", value);
127                         }
128                 }               
129                 
130                 public override string ConnectionGroupName { 
131                         get { return connectionGroup; }
132                         set { connectionGroup = value; }
133                 }
134                 
135                 public override long ContentLength { 
136                         get { return contentLength; }
137                         set { 
138                                 CheckRequestStarted ();
139                                 if (value < 0)
140                                         throw new ArgumentException ("value");
141                                 contentLength = value;
142                                 webHeaders.SetInternal ("Content-Length", Convert.ToString (value));
143                         }
144                 }
145                 
146                 public override string ContentType { 
147                         get { return webHeaders ["Content-Type"]; }
148                         set {
149                                 CheckRequestStarted ();
150                                 if (value == null || value.Trim().Length == 0) {
151                                         webHeaders.RemoveInternal ("Content-Type");
152                                         return;
153                                 }
154                                 webHeaders.SetInternal ("Content-Type", value);
155                         }
156                 }
157                 
158                 public HttpContinueDelegate ContinueDelegate {
159                         get { return continueDelegate; }
160                         set { continueDelegate = value; }
161                 }
162                 
163                 public CookieContainer CookieContainer {
164                         get { return cookieContainer; }
165                         set { cookieContainer = value; }
166                 }
167                 
168                 public override ICredentials Credentials { 
169                         get { return credentials; }
170                         set { credentials = value; }
171                 }
172                 
173                 public string Expect {
174                         get { return webHeaders ["Expect"]; }
175                         set {
176                                 CheckRequestStarted ();
177                                 string val = value;
178                                 if (val != null)
179                                         val = val.Trim ().ToLower ();
180                                 if (val == null || val.Length == 0) {
181                                         webHeaders.RemoveInternal ("Expect");
182                                         return;
183                                 }
184                                 if (val == "100-continue")
185                                         throw new ArgumentException ("value");
186                                 webHeaders.SetInternal ("Expect", value);
187                         }
188                 }
189                 
190                 public bool HaveResponse {
191                         get { return haveResponse; }
192                 }
193                 
194                 public override WebHeaderCollection Headers { 
195                         get { return webHeaders; }
196                         set {
197                                 CheckRequestStarted ();
198                                 WebHeaderCollection newHeaders = new WebHeaderCollection (true);
199                                 int count = value.Count;
200                                 for (int i = 0; i < count; i++) 
201                                         newHeaders.Add (value.GetKey (i), value.Get (i));
202                                 newHeaders.SetInternal ("Host", this.webHeaders["Host"]);
203                                 newHeaders.SetInternal ("Date", this.webHeaders["Date"]);
204                                 newHeaders.SetInternal ("Expect", this.webHeaders["Expect"]);
205                                 newHeaders.SetInternal ("Connection", this.webHeaders["Connection"]);
206                                 webHeaders = newHeaders;
207                         }
208                 }
209                 
210                 public DateTime IfModifiedSince {
211                         get { 
212                                 string str = webHeaders ["If-Modified-Since"];
213                                 if (str == null)
214                                         return DateTime.Now;
215                                 try {
216                                         return MonoHttpDate.Parse (str);
217                                 } catch (Exception) {
218                                         return DateTime.Now;
219                                 }
220                         }
221                         set {
222                                 CheckRequestStarted ();
223                                 // rfc-1123 pattern
224                                 webHeaders.SetInternal ("If-Modified-Since", 
225                                         value.ToUniversalTime ().ToString ("r", null));
226                                 // TODO: check last param when using different locale
227                         }
228                 }
229
230                 public bool KeepAlive {         
231                         get {
232                                 CheckRequestStarted ();
233                                 return keepAlive;
234                         }
235                         set {
236                                 CheckRequestStarted ();
237                                 keepAlive = value;
238                                 if (Connection == null)
239                                         webHeaders.SetInternal ("Connection", value ? "Keep-Alive" : "Close");
240                         }
241                 }
242                 
243                 public int MaximumAutomaticRedirections {
244                         get { return maxAutoRedirect; }
245                         set {
246                                 if (value < 0)
247                                         throw new ArgumentException ("value");
248                                 maxAutoRedirect = value;
249                         }                       
250                 }
251                 
252                 public string MediaType {
253                         get { return mediaType; }
254                         set { 
255                                 CheckRequestStarted ();
256                                 mediaType = value;
257                         }
258                 }
259                 
260                 public override string Method { 
261                         get { return this.method; }
262                         set { 
263                                 CheckRequestStarted ();
264                                 
265                                 if (value == null ||
266                                     (value != "GET" &&
267                                      value != "HEAD" &&
268                                      value != "POST" &&
269                                      value != "PUT" &&
270                                      value != "DELETE" &&
271                                      value != "TRACE" &&
272                                      value != "OPTIONS"))
273                                         throw new ArgumentException ("not a valid method");
274                                 if (contentLength != -1 &&
275                                     value != "POST" &&
276                                     value != "PUT")
277                                         throw new ArgumentException ("method must be PUT or POST");
278                                 
279                                 method = value;
280                         }
281                 }
282                 
283                 public bool Pipelined {
284                         get { return pipelined; }
285                         set { this.pipelined = value; }
286                 }               
287                 
288                 public override bool PreAuthenticate { 
289                         get { return preAuthenticate; }
290                         set { preAuthenticate = value; }
291                 }
292                 
293                 public Version ProtocolVersion {
294                         get { return version; }
295                         set { 
296                                 if (value != HttpVersion.Version10 && value != HttpVersion.Version11)
297                                         throw new ArgumentException ("value");
298                                 version = (Version) value; 
299                         }
300                 }
301                 
302                 public override IWebProxy Proxy { 
303                         get { return proxy; }
304                         set { 
305                                 if (value == null)
306                                         throw new ArgumentNullException ("value");
307                                 proxy = value;
308                         }
309                 }
310                 
311                 public string Referer {
312                         get { return webHeaders ["Referer" ]; }
313                         set {
314                                 CheckRequestStarted ();
315                                 if (value == null || value.Trim().Length == 0) {
316                                         webHeaders.RemoveInternal ("Referer");
317                                         return;
318                                 }
319                                 webHeaders.SetInternal ("Referer", value);
320                         }
321                 }
322
323                 public override Uri RequestUri { 
324                         get { return requestUri; }
325                 }
326                 
327                 public bool SendChunked {
328                         get { return sendChunked; }
329                         set {
330                                 CheckRequestStarted ();
331                                 sendChunked = value;
332                         }
333                 }
334                 
335                 public ServicePoint ServicePoint {
336                         get { return servicePoint; }
337                 }
338                 
339                 public override int Timeout { 
340                         get { return timeout; }
341                         set { timeout = value; }
342                 }
343                 
344                 public string TransferEncoding {
345                         get { return webHeaders ["Transfer-Encoding"]; }
346                         set {
347                                 CheckRequestStarted ();
348                                 if (!sendChunked)
349                                         throw new InvalidOperationException ("SendChunked must be True");
350                                 string val = value;
351                                 if (val != null)
352                                         val = val.Trim ().ToLower ();
353                                 if (val == null || val.Length == 0) {
354                                         webHeaders.RemoveInternal ("Transfer-Encoding");
355                                         return;
356                                 }
357                                 if (val == "chunked")
358                                         throw new ArgumentException ("Cannot set value to Chunked");
359                                 webHeaders.SetInternal ("Transfer-Encoding", value);
360                         }
361                 }
362                 
363                 public string UserAgent {
364                         get { return webHeaders ["User-Agent"]; }
365                         set { webHeaders.SetInternal ("User-Agent", value); }
366                 }
367                                 
368                 // Methods
369                 
370                 public void AddRange (int range)
371                 {
372                         AddRange ("bytes", range);
373                 }
374                 
375                 public void AddRange (int from, int to)
376                 {
377                         AddRange ("bytes", from, to);
378                 }
379                 
380                 public void AddRange (string rangeSpecifier, int range)
381                 {
382                         if (rangeSpecifier == null)
383                                 throw new ArgumentNullException ("rangeSpecifier");
384                         string value = webHeaders ["Range"];
385                         if (value == null || value.Length == 0) 
386                                 value = rangeSpecifier + "=";
387                         else if (value.ToLower ().StartsWith (rangeSpecifier.ToLower () + "="))
388                                 value += ",";
389                         else
390                                 throw new InvalidOperationException ("rangeSpecifier");
391                         webHeaders.SetInternal ("Range", value + range + "-");  
392                 }
393                 
394                 public void AddRange (string rangeSpecifier, int from, int to)
395                 {
396                         if (rangeSpecifier == null)
397                                 throw new ArgumentNullException ("rangeSpecifier");
398                         if (from < 0 || to < 0 || from > to)
399                                 throw new ArgumentOutOfRangeException ();                       
400                         string value = webHeaders ["Range"];
401                         if (value == null || value.Length == 0) 
402                                 value = rangeSpecifier + "=";
403                         else if (value.ToLower ().StartsWith (rangeSpecifier.ToLower () + "="))
404                                 value += ",";
405                         else
406                                 throw new InvalidOperationException ("rangeSpecifier");
407                         webHeaders.SetInternal ("Range", value + from + "-" + to);      
408                 }
409                 
410                 public override int GetHashCode ()
411                 {
412                         return base.GetHashCode ();
413                 }
414                 
415                 private delegate Stream GetRequestStreamCallback ();
416                 private delegate WebResponse GetResponseCallback ();
417                 
418                 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) 
419                 {
420                         if (method == null || (!method.Equals ("PUT") && !method.Equals ("POST")))
421                                 throw new ProtocolViolationException ("Cannot send file when method is: " + this.method + ". Method must be PUT.");
422                         // workaround for bug 24943
423                         Exception e = null;
424                         lock (this) {
425                                 if (asyncResponding || webResponse != null)
426                                         e = new InvalidOperationException ("This operation cannot be performed after the request has been submitted.");
427                                 else if (requesting)
428                                         e = new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
429                                 else
430                                         requesting = true;
431                         }
432                         if (e != null)
433                                 throw e;
434                         /*
435                         lock (this) {
436                                 if (asyncResponding || webResponse != null)
437                                         throw new InvalidOperationException ("This operation cannot be performed after the request has been submitted.");
438                                 if (requesting)
439                                         throw new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
440                                 requesting = true;
441                         }
442                         */
443                         GetRequestStreamCallback c = new GetRequestStreamCallback (this.GetRequestStreamInternal);
444                         return c.BeginInvoke (callback, state); 
445                 }
446
447                 public override Stream EndGetRequestStream (IAsyncResult asyncResult)
448                 {
449                         if (asyncResult == null)
450                                 throw new ArgumentNullException ("asyncResult");
451                         if (!asyncResult.IsCompleted)
452                                 asyncResult.AsyncWaitHandle.WaitOne ();                         
453                         AsyncResult async = (AsyncResult) asyncResult;
454                         GetRequestStreamCallback cb = (GetRequestStreamCallback) async.AsyncDelegate;
455                         return cb.EndInvoke (asyncResult);
456                 }
457                 
458                 public override Stream GetRequestStream()
459                 {
460                         IAsyncResult asyncResult = BeginGetRequestStream (null, null);
461                         if (!(asyncResult.AsyncWaitHandle.WaitOne (timeout, false))) {
462                                 throw new WebException("The request timed out", WebExceptionStatus.Timeout);
463                         }
464                         return EndGetRequestStream (asyncResult);
465                 }
466                 
467                 internal Stream GetRequestStreamInternal ()
468                 {
469                         if (this.requestStream == null) {
470                                 this.requestStream = new HttpWebRequestStream (this,
471                                                                                 GetSocket (),
472                                                                                 new EventHandler (OnClose));
473                         }
474
475                         return this.requestStream;
476                 }
477                 
478                 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state)
479                 {
480                         // workaround for bug 24943
481                         Exception e = null;
482                         lock (this) {
483                                 if (asyncResponding)
484                                         e = new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
485                                 else 
486                                         asyncResponding = true;
487                         }
488                         if (e != null)
489                                 throw e;
490                         /*
491                         lock (this) {
492                                 if (asyncResponding)
493                                         throw new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
494                                 asyncResponding = true;
495                         }
496                         */
497                         GetResponseCallback c = new GetResponseCallback (this.GetResponseInternal);
498                         return c.BeginInvoke (callback, state);
499                 }
500                 
501                 public override WebResponse EndGetResponse (IAsyncResult asyncResult)
502                 {
503                         if (asyncResult == null)
504                                 throw new ArgumentNullException ("asyncResult");
505                         if (!asyncResult.IsCompleted)
506                                 asyncResult.AsyncWaitHandle.WaitOne ();                 
507                         AsyncResult async = (AsyncResult) asyncResult;
508                         GetResponseCallback cb = (GetResponseCallback) async.AsyncDelegate;
509                         WebResponse webResponse = cb.EndInvoke(asyncResult);
510                         asyncResponding = false;
511                         if (webResponse != null) {
512                                 HttpStatusCode code = ((HttpWebResponse) webResponse).StatusCode;
513                                 if (code >= HttpStatusCode.MultipleChoices) {
514                                         string msg = String.Format ("The remote server returned an error: " +
515                                                                     "({0}) {1}", (int) code, code);
516                                         throw new WebException (msg);
517                                 }
518                         }
519                         
520                         return webResponse;
521                 }
522                 
523                 public override WebResponse GetResponse()
524                 {
525                         IAsyncResult asyncResult = BeginGetResponse (null, null);
526                         if (!(asyncResult.AsyncWaitHandle.WaitOne (timeout, false))) {
527                                 throw new WebException("The request timed out", WebExceptionStatus.Timeout);
528                         }
529                         return EndGetResponse (asyncResult);
530                 }
531                 
532                 WebResponse GetResponseInternal ()
533                 {
534                         if (webResponse != null)
535                                 return webResponse;                     
536
537                         GetRequestStreamInternal (); // Make sure we do the request
538
539                         webResponse = new HttpWebResponse (this.actualUri,
540                                                            method,
541                                                            GetSocket (),
542                                                            timeout,
543                                                            new EventHandler (OnClose));
544                         return webResponse;
545                 }
546
547                 [MonoTODO]              
548                 public override void Abort()
549                 {
550                         this.haveResponse = true;
551                         throw new NotImplementedException ();
552                 }               
553                 
554                 [MonoTODO]
555                 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
556                                                   StreamingContext streamingContext)
557                 {
558                         throw new NotImplementedException ();
559                 }
560                 
561                 ~HttpWebRequest ()
562                 {
563                         Dispose (false);
564                 }               
565                 
566                 void IDisposable.Dispose ()
567                 {
568                         Dispose (true);
569                         GC.SuppressFinalize (this);
570                 }
571                 
572                 protected virtual void Dispose (bool disposing)
573                 {
574                         if (disposed)
575                                 return;
576
577                         disposed = true;
578                         if (disposing) {
579                                 Close ();
580                         }
581
582                         requestStream = null;
583                         webResponse = null;
584                         socket = null;
585                 }
586                 
587                 // Private Methods
588                 
589                 private void CheckRequestStarted () 
590                 {
591                         if (requesting)
592                                 throw new InvalidOperationException ("request started");
593                 }
594                 
595                 internal void Close ()
596                 {
597                         if (requestStream != null)
598                                 requestStream.Close ();
599
600                         if (webResponse != null)
601                                 webResponse.Close ();
602
603                         lock (this) {                   
604                                 requesting = false;
605                                 if (requestEndEvent != null) 
606                                         requestEndEvent.Set ();
607                                 // requestEndEvent = null;
608                         }
609                 }
610                 
611                 void OnClose (object sender, EventArgs args)
612                 {
613                         if (sender == requestStream)
614                                 requestClosed = true;
615                         else if (sender == webResponse)
616                                 responseClosed = true;
617
618                         // TODO: if both have been used, wait until both are Closed. That's what MS does.
619                         if (requestClosed && responseClosed && socket != null && socket.Connected)
620                                 socket.Close ();
621                 }
622                 
623                 Socket GetSocket ()
624                 {
625                         if (socket != null)
626                                 return socket;
627
628                         IPAddress hostAddr = Dns.Resolve (actualUri.Host).AddressList[0];
629                         IPEndPoint endPoint = new IPEndPoint (hostAddr, actualUri.Port);
630                         socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
631
632                         socket.Connect (endPoint);
633                         if (!socket.Poll (timeout, SelectMode.SelectWrite))
634                                 throw new WebException("Connection timed out.", WebExceptionStatus.Timeout);
635
636                         return socket;
637                 }
638                 
639                 // Private Classes
640                 
641                 // to catch the Close called on the NetworkStream
642                 internal class HttpWebRequestStream : NetworkStream
643                 {
644                         bool disposed;
645                         EventHandler onClose;
646                         
647                         internal HttpWebRequestStream (HttpWebRequest webRequest,
648                                                         Socket socket,
649                                                         EventHandler onClose)
650                                 : base (socket, FileAccess.Write, false)
651                         {
652                                 this.onClose = onClose;
653                                 StringBuilder sb = new StringBuilder ();
654                                 sb.Append (webRequest.Method + " " + 
655                                            webRequest.actualUri.PathAndQuery +
656                                            " HTTP/" +
657                                            webRequest.version.ToString (2) +
658                                            "\r\n");
659
660                                 foreach (string header in webRequest.webHeaders) {
661                                         sb.Append (header + ": " + webRequest.webHeaders[header] + "\r\n");
662                                 }
663
664                                 // FIXME: write cookie headers (CookieContainer not yet implemented)
665
666                                 sb.Append ("\r\n");
667                                 byte [] bytes = Encoding.ASCII.GetBytes (sb.ToString ());
668                                 if (!socket.Poll (webRequest.Timeout, SelectMode.SelectWrite))
669                                         throw new WebException("The request timed out", WebExceptionStatus.Timeout);
670                                 
671                                 this.Write (bytes, 0, bytes.Length);
672                         }
673
674                         protected override void Dispose (bool disposing)
675                         {
676                                 if (disposed)
677                                         return;
678
679                                 disposed = true;
680                                 if (disposing) {
681                                         /* This does not work !??
682                                         if (socket.Connected)
683                                                 socket.Shutdown (SocketShutdown.Send);
684                                         */
685                                         if (onClose != null)
686                                                 onClose (this, EventArgs.Empty);
687                                 }
688
689                                 onClose = null;
690                                 base.Dispose (disposing);
691                         }
692                         
693                         public override void Close() 
694                         {
695                                 GC.SuppressFinalize (this);
696                                 Dispose (true);
697                         }
698                 }               
699         }
700 }
701