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