2004-02-26 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System / System.Net / WebConnection.cs
1 //
2 // System.Net.WebConnection
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (C) 2003 Ximian, Inc (http://www.ximian.com)
8 //
9
10 using System.IO;
11 using System.Collections;
12 using System.Net.Sockets;
13 using System.Reflection;
14 using System.Security.Cryptography.X509Certificates;
15 using System.Text;
16 using System.Threading;
17
18 namespace System.Net
19 {
20         enum ReadState
21         {
22                 None,
23                 Status,
24                 Headers,
25                 Content
26         }
27         
28         class WebConnection
29         {
30                 ServicePoint sPoint;
31                 Stream nstream;
32                 Socket socket;
33                 WebExceptionStatus status;
34                 WebConnectionGroup group;
35                 bool busy;
36                 WaitOrTimerCallback initConn;
37                 bool keepAlive;
38                 byte [] buffer;
39                 static AsyncCallback readDoneDelegate = new AsyncCallback (ReadDone);
40                 EventHandler abortHandler;
41                 ReadState readState;
42                 internal WebConnectionData Data;
43                 WebConnectionStream prevStream;
44                 bool chunkedRead;
45                 ChunkStream chunkStream;
46                 AutoResetEvent goAhead;
47                 Queue queue;
48                 bool reused;
49                 int position;
50
51                 bool ssl;
52                 bool certsAvailable;
53                 static bool sslCheck;
54                 static Type sslStream;
55                 static PropertyInfo piCRL;
56                 static PropertyInfo piClient;
57                 static PropertyInfo piServer;
58
59                 public WebConnection (WebConnectionGroup group, ServicePoint sPoint)
60                 {
61                         this.group = group;
62                         this.sPoint = sPoint;
63                         buffer = new byte [4096];
64                         readState = ReadState.None;
65                         Data = new WebConnectionData ();
66                         initConn = new WaitOrTimerCallback (InitConnection);
67                         abortHandler = new EventHandler (Abort);
68                         goAhead = new AutoResetEvent (true);
69                         queue = new Queue (1);
70                 }
71
72                 public void Connect ()
73                 {
74                         lock (this) {
75                                 if (socket != null && socket.Connected && status == WebExceptionStatus.Success) {
76                                         reused = true;
77                                         return;
78                                 }
79
80                                 reused = false;
81                                 if (socket != null) {
82                                         socket.Close();
83                                         socket = null;
84                                 }
85                                 
86                                 chunkStream = null;
87                                 IPHostEntry hostEntry = sPoint.HostEntry;
88
89                                 if (hostEntry == null) {
90                                         status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
91                                                                     WebExceptionStatus.NameResolutionFailure;
92                                         return;
93                                 }
94
95                                 foreach (IPAddress address in hostEntry.AddressList) {
96                                         socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
97                                         try {
98                                                 socket.Connect (new IPEndPoint(address, sPoint.Address.Port));
99                                                 status = WebExceptionStatus.Success;
100                                                 break;
101                                         } catch (SocketException) {
102                                                 socket.Close();
103                                                 socket = null;
104                                                 status = WebExceptionStatus.ConnectFailure;
105                                         }
106                                 }
107                         }
108                 }
109
110                 bool CreateStream (HttpWebRequest request)
111                 {
112                         try {
113                                 NetworkStream serverStream = new NetworkStream (socket, false);
114                                 if (request.RequestUri.Scheme == Uri.UriSchemeHttps) {
115                                         ssl = true;
116                                         if (!sslCheck) {
117                                                 lock (typeof (WebConnection)) {
118                                                         sslCheck = true;
119                                                         // HttpsClientStream is an internal glue class in Mono.Security.dll
120                                                         sslStream = Type.GetType ("Mono.Security.Protocol.Tls.HttpsClientStream, Mono.Security, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
121                                                         if (sslStream != null) {
122                                                                 piClient = sslStream.GetProperty ("SelectedClientCertificate");
123                                                                 piServer = sslStream.GetProperty ("ServerCertificate");
124                                                         }
125                                                 }
126                                         }
127                                         if (sslStream == null)
128                                                 throw new NotSupportedException ("Missing Mono.Security.dll assembly. Support for SSL/TLS is unavailable.");
129
130                                         object[] args = new object [4] { serverStream, request.RequestUri.Host, request.ClientCertificates, request };
131                                         nstream = (Stream) Activator.CreateInstance (sslStream, args);
132
133                                         // we also need to set ServicePoint.Certificate 
134                                         // and ServicePoint.ClientCertificate but this can
135                                         // only be done later (after handshake - which is
136                                         // done only after a read operation).
137                                 }
138                                 else
139                                         nstream = serverStream;
140                         } catch (Exception) {
141                                 status = WebExceptionStatus.ConnectFailure;
142                                 return false;
143                         }
144
145                         return true;
146                 }
147                 
148                 void HandleError (WebExceptionStatus st, Exception e)
149                 {
150                         status = st;
151                         lock (this) {
152                                 busy = false;
153                                 if (st == WebExceptionStatus.RequestCanceled)
154                                         Data = new WebConnectionData ();
155
156                                 status = st;
157                         }
158
159                         if (e == null) { // At least we now where it comes from
160                                 try {
161                                         throw new Exception (new System.Diagnostics.StackTrace ().ToString ());
162                                 } catch (Exception e2) {
163                                         e = e2;
164                                 }
165                         }
166
167                         if (Data != null && Data.request != null)
168                                 Data.request.SetResponseError (st, e);
169
170                         Close (true);
171                 }
172                 
173                 static void ReadDone (IAsyncResult result)
174                 {
175                         WebConnection cnc = (WebConnection) result.AsyncState;
176                         WebConnectionData data = cnc.Data;
177                         Stream ns = cnc.nstream;
178                         if (ns == null) {
179                                 cnc.Close (true);
180                                 return;
181                         }
182
183                         int nread = -1;
184                         try {
185                                 nread = ns.EndRead (result);
186                         } catch (Exception e) {
187                                 cnc.status = WebExceptionStatus.ReceiveFailure;
188                                 cnc.HandleError (cnc.status, e);
189                                 return;
190                         }
191
192                         if (nread == 0) {
193                                 cnc.status = WebExceptionStatus.ReceiveFailure;
194                                 cnc.HandleError (cnc.status, null);
195                                 return;
196                         }
197
198                         if (nread < 0) {
199                                 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
200                                 return;
201                         }
202
203                         //Console.WriteLine (System.Text.Encoding.Default.GetString (cnc.buffer, 0, nread + cnc.position));
204                         int pos = -1;
205                         nread += cnc.position;
206                         if (cnc.readState == ReadState.None) { 
207                                 Exception exc = null;
208                                 try {
209                                         pos = cnc.GetResponse (cnc.buffer, nread);
210                                 } catch (Exception e) {
211                                         exc = e;
212                                 }
213
214                                 if (exc != null) {
215                                         cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc);
216                                         return;
217                                 }
218                         }
219
220                         if (cnc.readState != ReadState.Content) {
221                                 int est = nread * 2;
222                                 int max = (est < cnc.buffer.Length) ? cnc.buffer.Length : est;
223                                 byte [] newBuffer = new byte [max];
224                                 Buffer.BlockCopy (cnc.buffer, 0, newBuffer, 0, nread);
225                                 cnc.buffer = newBuffer;
226                                 cnc.position = nread;
227                                 cnc.readState = ReadState.None;
228                                 InitRead (cnc);
229                                 return;
230                         }
231
232                         cnc.position = 0;
233
234                         WebConnectionStream stream = new WebConnectionStream (cnc);
235
236                         string contentType = data.Headers ["Transfer-Encoding"];
237                         cnc.chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
238                         if (!cnc.chunkedRead) {
239                                 stream.ReadBuffer = cnc.buffer;
240                                 stream.ReadBufferOffset = pos;
241                                 stream.ReadBufferSize = nread;
242                         } else if (cnc.chunkStream == null) {
243                                 cnc.chunkStream = new ChunkStream (cnc.buffer, pos, nread, data.Headers);
244                         } else {
245                                 cnc.chunkStream.ResetBuffer ();
246                                 cnc.chunkStream.Write (cnc.buffer, pos, nread);
247                         }
248
249                         data.stream = stream;
250                         
251                         lock (cnc) {
252                                 if (cnc.queue.Count > 0)
253                                         stream.ReadAll ();
254                                 else
255                                 {
256                                         cnc.prevStream = stream;
257                                         stream.CheckComplete ();
258                                 }
259                         }
260                         
261                         data.request.SetResponseData (data);
262                 }
263
264                 internal void GetCertificates () 
265                 {
266                         // here the SSL negotiation have been done
267                         X509Certificate client = (X509Certificate) piClient.GetValue (nstream, null);
268                         X509Certificate server = (X509Certificate) piServer.GetValue (nstream, null);
269                         sPoint.SetCertificates (client, server);
270                         certsAvailable = (server != null);
271                 }
272                 
273                 static void InitRead (object state)
274                 {
275                         WebConnection cnc = (WebConnection) state;
276                         Stream ns = cnc.nstream;
277
278                         try {
279                                 int size = cnc.buffer.Length - cnc.position;
280                                 ns.BeginRead (cnc.buffer, cnc.position, size, readDoneDelegate, cnc);
281                         } catch (Exception e) {
282                                 cnc.HandleError (WebExceptionStatus.ReceiveFailure, e);
283                         }
284                 }
285                 
286                 int GetResponse (byte [] buffer, int max)
287                 {
288                         int pos = 0;
289                         string line = null;
290                         bool lineok = false;
291                         bool isContinue = false;
292                         do {
293                                 if (readState == ReadState.None) {
294                                         lineok = ReadLine (buffer, ref pos, max, ref line);
295                                         if (!lineok)
296                                                 return -1;
297
298                                         readState = ReadState.Status;
299
300                                         string [] parts = line.Split (' ');
301                                         if (parts.Length < 2)
302                                                 return -1;
303
304                                         if (String.Compare (parts [0], "HTTP/1.1", true) == 0) {
305                                                 Data.Version = HttpVersion.Version11;
306                                                 sPoint.SetVersion (HttpVersion.Version11);
307                                         } else {
308                                                 Data.Version = HttpVersion.Version10;
309                                                 sPoint.SetVersion (HttpVersion.Version10);
310                                         }
311
312                                         Data.StatusCode = (int) UInt32.Parse (parts [1]);
313                                         if (parts.Length >= 3)
314                                                 Data.StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
315                                         else
316                                                 Data.StatusDescription = "";
317
318                                         if (pos >= max)
319                                                 return pos;
320                                 }
321
322                                 if (readState == ReadState.Status) {
323                                         readState = ReadState.Headers;
324                                         Data.Headers = new WebHeaderCollection ();
325                                         ArrayList headers = new ArrayList ();
326                                         bool finished = false;
327                                         while (!finished) {
328                                                 if (ReadLine (buffer, ref pos, max, ref line) == false)
329                                                         break;
330                                         
331                                                 if (line == null) {
332                                                         // Empty line: end of headers
333                                                         finished = true;
334                                                         continue;
335                                                 }
336                                         
337                                                 if (line.Length > 0 && (line [0] == ' ' || line [0] == '\t')) {
338                                                         int count = headers.Count - 1;
339                                                         if (count < 0)
340                                                                 break;
341
342                                                         string prev = (string) headers [count] + line;
343                                                         headers [count] = prev;
344                                                 } else {
345                                                         headers.Add (line);
346                                                 }
347                                         }
348
349                                         if (!finished)
350                                                 return -1;
351
352                                         foreach (string s in headers)
353                                                 Data.Headers.Add (s);
354
355                                         if (Data.StatusCode == (int) HttpStatusCode.Continue) {
356                                                 sPoint.SendContinue = true;
357                                                 if (pos >= max)
358                                                         return pos;
359
360                                                 if (Data.request.ExpectContinue) {
361                                                         Data.request.DoContinueDelegate (Data.StatusCode, Data.Headers);
362                                                         // Prevent double calls when getting the
363                                                         // headers in several packets.
364                                                         Data.request.ExpectContinue = false;
365                                                 }
366
367                                                 readState = ReadState.None;
368                                                 isContinue = true;
369                                         }
370                                         else {
371                                                 readState = ReadState.Content;
372                                                 return pos;
373                                         }
374                                 }
375                         } while (isContinue == true);
376
377                         return -1;
378                 }
379                 
380                 void InitConnection (object state, bool notUsed)
381                 {
382                         HttpWebRequest request = (HttpWebRequest) state;
383
384                         if (status == WebExceptionStatus.RequestCanceled) {
385                                 busy = false;
386                                 Data = new WebConnectionData ();
387                                 goAhead.Set ();
388                                 SendNext ();
389                                 return;
390                         }
391
392                         keepAlive = request.KeepAlive;
393                         Data = new WebConnectionData ();
394                         Data.request = request;
395
396                         Connect ();
397                         if (status != WebExceptionStatus.Success) {
398                                 request.SetWriteStreamError (status);
399                                 Close (true);
400                                 return;
401                         }
402                         
403                         if (!CreateStream (request)) {
404                                 request.SetWriteStreamError (status);
405                                 Close (true);
406                                 return;
407                         }
408
409                         readState = ReadState.None;
410                         request.SetWriteStream (new WebConnectionStream (this, request));
411                         InitRead (this);
412                 }
413                 
414                 internal EventHandler SendRequest (HttpWebRequest request)
415                 {
416                         lock (this) {
417                                 if (prevStream != null && socket != null && socket.Connected) {
418                                         prevStream.ReadAll ();
419                                         prevStream = null;
420                                 }
421
422                                 if (!busy) {
423                                         busy = true;
424                                         ThreadPool.RegisterWaitForSingleObject (goAhead, initConn,
425                                                                                 request, -1, true);
426                                 } else {
427                                         queue.Enqueue (request);
428                                 }
429                         }
430
431                         return abortHandler;
432                 }
433                 
434                 void SendNext ()
435                 {
436                         lock (this) {
437                                 if (queue.Count > 0) {
438                                         prevStream = null;
439                                         SendRequest ((HttpWebRequest) queue.Dequeue ());
440                                 }
441                         }
442                 }
443
444                 internal void NextRead ()
445                 {
446                         lock (this) {
447                                 busy = false;
448                                 string header = (sPoint.UsesProxy) ? "Proxy-Connection" : "Connection";
449                                 string cncHeader = (Data.Headers != null) ? Data.Headers [header] : null;
450                                 bool keepAlive = (Data.Version == HttpVersion.Version11);
451                                 if (cncHeader != null) {
452                                         cncHeader = cncHeader.ToLower ();
453                                         keepAlive = (this.keepAlive && cncHeader.IndexOf ("keep-alive") != -1);
454                                 }
455
456                                 if ((socket != null && !socket.Connected) ||
457                                    (!keepAlive || (cncHeader != null && cncHeader.IndexOf ("close") != -1))) {
458                                         Close (false);
459                                 }
460
461                                 goAhead.Set ();
462                                 if (queue.Count > 0) {
463                                         prevStream = null;
464                                         SendRequest ((HttpWebRequest) queue.Dequeue ());
465                                 }
466                         }
467                 }
468                 
469                 static bool ReadLine (byte [] buffer, ref int start, int max, ref string output)
470                 {
471                         bool foundCR = false;
472                         StringBuilder text = new StringBuilder ();
473
474                         int c = 0;
475                         while (start < max) {
476                                 c = (int) buffer [start++];
477
478                                 if (c == '\n') {                        // newline
479                                         if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
480                                                 text.Length--;
481
482                                         foundCR = false;
483                                         break;
484                                 } else if (foundCR) {
485                                         text.Length--;
486                                         break;
487                                 }
488
489                                 if (c == '\r')
490                                         foundCR = true;
491                                         
492
493                                 text.Append ((char) c);
494                         }
495
496                         if (c != '\n' && c != '\r')
497                                 return false;
498
499                         if (text.Length == 0) {
500                                 output = null;
501                                 return (c == '\n' || c == '\r');
502                         }
503
504                         if (foundCR)
505                                 text.Length--;
506
507                         output = text.ToString ();
508                         return true;
509                 }
510
511                 internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
512                 {
513                         if (nstream == null)
514                                 return null;
515
516                         IAsyncResult result = null;
517                         if (!chunkedRead || chunkStream.WantMore) {
518                                 try {
519                                         result = nstream.BeginRead (buffer, offset, size, cb, state);
520                                 } catch (Exception) {
521                                         status = WebExceptionStatus.ReceiveFailure;
522                                         throw;
523                                 }
524                         }
525
526                         if (chunkedRead) {
527                                 WebAsyncResult wr = new WebAsyncResult (null, null, buffer, offset, size);
528                                 wr.InnerAsyncResult = result;
529                                 return wr;
530                         }
531
532                         return result;
533                 }
534                 
535                 internal int EndRead (IAsyncResult result)
536                 {
537                         if (nstream == null)
538                                 return 0;
539
540                         if (chunkedRead) {
541                                 WebAsyncResult wr = (WebAsyncResult) result;
542                                 int nbytes = 0;
543                                 if (wr.InnerAsyncResult != null)
544                                         nbytes = nstream.EndRead (wr.InnerAsyncResult);
545
546                                 chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
547                                 if (nbytes == 0 && chunkStream.WantMore) {
548                                         nbytes = nstream.Read (wr.Buffer, wr.Offset, wr.Size);          
549                                         chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
550                                 }
551                                 return nbytes;
552                         }
553
554                         return nstream.EndRead (result);
555                 }
556
557                 internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
558                 {
559                         IAsyncResult result = null;
560                         if (nstream == null)
561                                 return null;
562
563                         try {
564                                 result = nstream.BeginWrite (buffer, offset, size, cb, state);
565                         } catch (Exception) {
566                                 status = WebExceptionStatus.SendFailure;
567                                 throw;
568                         }
569
570                         return result;
571                 }
572
573                 internal void EndWrite (IAsyncResult result)
574                 {
575                         if (nstream != null)
576                                 nstream.EndWrite (result);
577                 }
578
579                 internal int Read (byte [] buffer, int offset, int size)
580                 {
581                         if (nstream == null)
582                                 return 0;
583
584                         int result = 0;
585                         try {
586                                 if (!chunkedRead || chunkStream.WantMore)
587                                         result = nstream.Read (buffer, offset, size);
588
589                                 if (chunkedRead)
590                                         chunkStream.WriteAndReadBack (buffer, offset, size, ref result);
591                         } catch (Exception e) {
592                                 status = WebExceptionStatus.ReceiveFailure;
593                                 HandleError (status, e);
594                         }
595
596                         return result;
597                 }
598
599                 internal void Write (byte [] buffer, int offset, int size)
600                 {
601                         if (nstream == null)
602                                 return;
603
604                         try {
605                                 nstream.Write (buffer, offset, size);
606                                 // here SSL handshake should have been done
607                                 if (ssl && !certsAvailable) {
608                                         GetCertificates ();
609                                 }
610                         } catch (Exception) {
611                         }
612                 }
613
614                 internal bool TryReconnect ()
615                 {
616                         lock (this) {
617                                 if (!reused) {
618                                         HandleError (WebExceptionStatus.SendFailure, null);
619                                         return false;
620                                 }
621
622                                 Close (false);
623                                 reused = false;
624                                 Connect ();
625                                 if (status != WebExceptionStatus.Success) {
626                                         HandleError (WebExceptionStatus.SendFailure, null);
627                                         return false;
628                                 }
629                         
630                                 if (!CreateStream (Data.request)) {
631                                         HandleError (WebExceptionStatus.SendFailure, null);
632                                         return false;
633                                 }
634                         }
635                         return true;
636                 }
637
638                 void Close (bool sendNext)
639                 {
640                         lock (this) {
641                                 busy = false;
642                                 if (nstream != null) {
643                                         try {
644                                                 nstream.Close ();
645                                         } catch {}
646                                         nstream = null;
647                                 }
648
649                                 if (socket != null) {
650                                         try {
651                                                 socket.Close ();
652                                         } catch {}
653                                         socket = null;
654                                 }
655
656                                 if (sendNext) {
657                                         goAhead.Set ();
658                                         SendNext ();
659                                 }
660                         }
661                 }
662
663                 void Abort (object sender, EventArgs args)
664                 {
665                         HandleError (WebExceptionStatus.RequestCanceled, null);
666                 }
667
668                 internal bool Busy {
669                         get { lock (this) return busy; }
670                 }
671                 
672                 internal bool Connected {
673                         get {
674                                 lock (this) {
675                                         return (socket != null && socket.Connected);
676                                 }
677                         }
678                 }
679                 
680                 ~WebConnection ()
681                 {
682                         Close (false);
683                 }
684         }
685 }
686