Removed Consoles and ^Ms
[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 piClient;
56                 static PropertyInfo piServer;
57
58                 public WebConnection (WebConnectionGroup group, ServicePoint sPoint)
59                 {
60                         this.group = group;
61                         this.sPoint = sPoint;
62                         buffer = new byte [4096];
63                         readState = ReadState.None;
64                         Data = new WebConnectionData ();
65                         initConn = new WaitOrTimerCallback (InitConnection);
66                         abortHandler = new EventHandler (Abort);
67                         goAhead = new AutoResetEvent (true);
68                         queue = group.Queue;
69                 }
70
71                 public void Connect ()
72                 {
73                         lock (this) {
74                                 if (socket != null && socket.Connected && status == WebExceptionStatus.Success) {
75                                         reused = true;
76                                         return;
77                                 }
78
79                                 reused = false;
80                                 if (socket != null) {
81                                         socket.Close();
82                                         socket = null;
83                                 }
84                                 
85                                 chunkStream = null;
86                                 IPHostEntry hostEntry = sPoint.HostEntry;
87
88                                 if (hostEntry == null) {
89                                         status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
90                                                                     WebExceptionStatus.NameResolutionFailure;
91                                         return;
92                                 }
93
94                                 foreach (IPAddress address in hostEntry.AddressList) {
95                                         socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
96                                         try {
97                                                 socket.Connect (new IPEndPoint(address, sPoint.Address.Port));
98                                                 status = WebExceptionStatus.Success;
99                                                 break;
100                                         } catch (SocketException) {
101                                                 socket.Close();
102                                                 socket = null;
103                                                 status = WebExceptionStatus.ConnectFailure;
104                                         }
105                                 }
106                         }
107                 }
108
109                 bool CreateStream (HttpWebRequest request)
110                 {
111                         try {
112                                 NetworkStream serverStream = new NetworkStream (socket, false);
113                                 if (request.RequestUri.Scheme == Uri.UriSchemeHttps) {
114                                         ssl = true;
115                                         if (!sslCheck) {
116                                                 lock (typeof (WebConnection)) {
117                                                         sslCheck = true;
118                                                         // HttpsClientStream is an internal glue class in Mono.Security.dll
119                                                         sslStream = Type.GetType ("Mono.Security.Protocol.Tls.HttpsClientStream, " + Consts.AssemblyMono_Security, false);
120                                                         if (sslStream != null) {
121                                                                 piClient = sslStream.GetProperty ("SelectedClientCertificate");
122                                                                 piServer = sslStream.GetProperty ("ServerCertificate");
123                                                         }
124                                                 }
125                                         }
126                                         if (sslStream == null)
127                                                 throw new NotSupportedException ("Missing Mono.Security.dll assembly. Support for SSL/TLS is unavailable.");
128
129                                         object[] args = new object [4] { serverStream, request.RequestUri.Host, request.ClientCertificates, request };
130                                         nstream = (Stream) Activator.CreateInstance (sslStream, args);
131
132                                         // we also need to set ServicePoint.Certificate 
133                                         // and ServicePoint.ClientCertificate but this can
134                                         // only be done later (after handshake - which is
135                                         // done only after a read operation).
136                                 }
137                                 else
138                                         nstream = serverStream;
139                         } catch (Exception) {
140                                 status = WebExceptionStatus.ConnectFailure;
141                                 return false;
142                         }
143
144                         return true;
145                 }
146                 
147                 void HandleError (WebExceptionStatus st, Exception e)
148                 {
149                         status = st;
150                         lock (this) {
151                                 busy = false;
152                                 if (st == WebExceptionStatus.RequestCanceled)
153                                         Data = new WebConnectionData ();
154
155                                 status = st;
156                         }
157
158                         if (e == null) { // At least we now where it comes from
159                                 try {
160                                         throw new Exception (new System.Diagnostics.StackTrace ().ToString ());
161                                 } catch (Exception e2) {
162                                         e = e2;
163                                 }
164                         }
165
166                         if (Data != null && Data.request != null)
167                                 Data.request.SetResponseError (st, e);
168
169                         Close (true);
170                 }
171                 
172                 static void ReadDone (IAsyncResult result)
173                 {
174                         WebConnection cnc = (WebConnection) result.AsyncState;
175                         WebConnectionData data = cnc.Data;
176                         Stream ns = cnc.nstream;
177                         if (ns == null) {
178                                 cnc.Close (true);
179                                 return;
180                         }
181
182                         int nread = -1;
183                         try {
184                                 nread = ns.EndRead (result);
185                         } catch (Exception e) {
186                                 cnc.status = WebExceptionStatus.ReceiveFailure;
187                                 cnc.HandleError (cnc.status, e);
188                                 return;
189                         }
190
191                         if (nread == 0) {
192                                 cnc.status = WebExceptionStatus.ReceiveFailure;
193                                 cnc.HandleError (cnc.status, null);
194                                 return;
195                         }
196
197                         if (nread < 0) {
198                                 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
199                                 return;
200                         }
201
202                         //Console.WriteLine (System.Text.Encoding.Default.GetString (cnc.buffer, 0, nread + cnc.position));
203                         int pos = -1;
204                         nread += cnc.position;
205                         if (cnc.readState == ReadState.None) { 
206                                 Exception exc = null;
207                                 try {
208                                         pos = cnc.GetResponse (cnc.buffer, nread);
209                                 } catch (Exception e) {
210                                         exc = e;
211                                 }
212
213                                 if (exc != null) {
214                                         cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc);
215                                         return;
216                                 }
217                         }
218
219                         if (cnc.readState != ReadState.Content) {
220                                 int est = nread * 2;
221                                 int max = (est < cnc.buffer.Length) ? cnc.buffer.Length : est;
222                                 byte [] newBuffer = new byte [max];
223                                 Buffer.BlockCopy (cnc.buffer, 0, newBuffer, 0, nread);
224                                 cnc.buffer = newBuffer;
225                                 cnc.position = nread;
226                                 cnc.readState = ReadState.None;
227                                 InitRead (cnc);
228                                 return;
229                         }
230
231                         cnc.position = 0;
232
233                         WebConnectionStream stream = new WebConnectionStream (cnc);
234
235                         string contentType = data.Headers ["Transfer-Encoding"];
236                         cnc.chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
237                         if (!cnc.chunkedRead) {
238                                 stream.ReadBuffer = cnc.buffer;
239                                 stream.ReadBufferOffset = pos;
240                                 stream.ReadBufferSize = nread;
241                         } else if (cnc.chunkStream == null) {
242                                 cnc.chunkStream = new ChunkStream (cnc.buffer, pos, nread, data.Headers);
243                         } else {
244                                 cnc.chunkStream.ResetBuffer ();
245                                 cnc.chunkStream.Write (cnc.buffer, pos, nread);
246                         }
247
248                         data.stream = stream;
249                         
250                         lock (cnc) {
251                                 lock (cnc.queue) {
252                                         if (cnc.queue.Count > 0) {
253                                                 stream.ReadAll ();
254                                         } else {
255                                                 cnc.prevStream = stream;
256                                                 stream.CheckComplete ();
257                                         }
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.SetInternal (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                                         lock (queue) {
428                                                 queue.Enqueue (request);
429                                         }
430                                 }
431                         }
432
433                         return abortHandler;
434                 }
435                 
436                 void SendNext ()
437                 {
438                         lock (queue) {
439                                 if (queue.Count > 0) {
440                                         prevStream = null;
441                                         SendRequest ((HttpWebRequest) queue.Dequeue ());
442                                 }
443                         }
444                 }
445
446                 internal void NextRead ()
447                 {
448                         lock (this) {
449                                 busy = false;
450                                 string header = (sPoint.UsesProxy) ? "Proxy-Connection" : "Connection";
451                                 string cncHeader = (Data.Headers != null) ? Data.Headers [header] : null;
452                                 bool keepAlive = (Data.Version == HttpVersion.Version11);
453                                 if (cncHeader != null) {
454                                         cncHeader = cncHeader.ToLower ();
455                                         keepAlive = (this.keepAlive && cncHeader.IndexOf ("keep-alive") != -1);
456                                 }
457
458                                 if ((socket != null && !socket.Connected) ||
459                                    (!keepAlive || (cncHeader != null && cncHeader.IndexOf ("close") != -1))) {
460                                         Close (false);
461                                 }
462
463                                 goAhead.Set ();
464                                 lock (queue) {
465                                         if (queue.Count > 0) {
466                                                 prevStream = null;
467                                                 SendRequest ((HttpWebRequest) queue.Dequeue ());
468                                         }
469                                 }
470                         }
471                 }
472                 
473                 static bool ReadLine (byte [] buffer, ref int start, int max, ref string output)
474                 {
475                         bool foundCR = false;
476                         StringBuilder text = new StringBuilder ();
477
478                         int c = 0;
479                         while (start < max) {
480                                 c = (int) buffer [start++];
481
482                                 if (c == '\n') {                        // newline
483                                         if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
484                                                 text.Length--;
485
486                                         foundCR = false;
487                                         break;
488                                 } else if (foundCR) {
489                                         text.Length--;
490                                         break;
491                                 }
492
493                                 if (c == '\r')
494                                         foundCR = true;
495                                         
496
497                                 text.Append ((char) c);
498                         }
499
500                         if (c != '\n' && c != '\r')
501                                 return false;
502
503                         if (text.Length == 0) {
504                                 output = null;
505                                 return (c == '\n' || c == '\r');
506                         }
507
508                         if (foundCR)
509                                 text.Length--;
510
511                         output = text.ToString ();
512                         return true;
513                 }
514
515                 internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
516                 {
517                         if (nstream == null)
518                                 return null;
519
520                         IAsyncResult result = null;
521                         if (!chunkedRead || chunkStream.WantMore) {
522                                 try {
523                                         result = nstream.BeginRead (buffer, offset, size, cb, state);
524                                 } catch (Exception) {
525                                         status = WebExceptionStatus.ReceiveFailure;
526                                         throw;
527                                 }
528                         }
529
530                         if (chunkedRead) {
531                                 WebAsyncResult wr = new WebAsyncResult (null, null, buffer, offset, size);
532                                 wr.InnerAsyncResult = result;
533                                 return wr;
534                         }
535
536                         return result;
537                 }
538                 
539                 internal int EndRead (IAsyncResult result)
540                 {
541                         if (nstream == null)
542                                 return 0;
543
544                         if (chunkedRead) {
545                                 WebAsyncResult wr = (WebAsyncResult) result;
546                                 int nbytes = 0;
547                                 if (wr.InnerAsyncResult != null)
548                                         nbytes = nstream.EndRead (wr.InnerAsyncResult);
549
550                                 chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
551                                 if (nbytes == 0 && chunkStream.WantMore) {
552                                         nbytes = nstream.Read (wr.Buffer, wr.Offset, wr.Size);          
553                                         chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
554                                 }
555                                 return nbytes;
556                         }
557
558                         return nstream.EndRead (result);
559                 }
560
561                 internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
562                 {
563                         IAsyncResult result = null;
564                         if (nstream == null)
565                                 return null;
566
567                         try {
568                                 result = nstream.BeginWrite (buffer, offset, size, cb, state);
569                         } catch (Exception) {
570                                 status = WebExceptionStatus.SendFailure;
571                                 throw;
572                         }
573
574                         return result;
575                 }
576
577                 internal void EndWrite (IAsyncResult result)
578                 {
579                         if (nstream != null)
580                                 nstream.EndWrite (result);
581                 }
582
583                 internal int Read (byte [] buffer, int offset, int size)
584                 {
585                         if (nstream == null)
586                                 return 0;
587
588                         int result = 0;
589                         try {
590                                 if (!chunkedRead || chunkStream.WantMore)
591                                         result = nstream.Read (buffer, offset, size);
592
593                                 if (chunkedRead)
594                                         chunkStream.WriteAndReadBack (buffer, offset, size, ref result);
595                         } catch (Exception e) {
596                                 status = WebExceptionStatus.ReceiveFailure;
597                                 HandleError (status, e);
598                         }
599
600                         return result;
601                 }
602
603                 internal void Write (byte [] buffer, int offset, int size)
604                 {
605                         if (nstream == null)
606                                 return;
607
608                         try {
609                                 nstream.Write (buffer, offset, size);
610                                 // here SSL handshake should have been done
611                                 if (ssl && !certsAvailable) {
612                                         GetCertificates ();
613                                 }
614                         } catch (Exception) {
615                         }
616                 }
617
618                 internal bool TryReconnect ()
619                 {
620                         lock (this) {
621                                 if (!reused) {
622                                         HandleError (WebExceptionStatus.SendFailure, null);
623                                         return false;
624                                 }
625
626                                 Close (false);
627                                 reused = false;
628                                 Connect ();
629                                 if (status != WebExceptionStatus.Success) {
630                                         HandleError (WebExceptionStatus.SendFailure, null);
631                                         return false;
632                                 }
633                         
634                                 if (!CreateStream (Data.request)) {
635                                         HandleError (WebExceptionStatus.SendFailure, null);
636                                         return false;
637                                 }
638                         }
639                         return true;
640                 }
641
642                 void Close (bool sendNext)
643                 {
644                         lock (this) {
645                                 busy = false;
646                                 if (nstream != null) {
647                                         try {
648                                                 nstream.Close ();
649                                         } catch {}
650                                         nstream = null;
651                                 }
652
653                                 if (socket != null) {
654                                         try {
655                                                 socket.Close ();
656                                         } catch {}
657                                         socket = null;
658                                 }
659
660                                 if (sendNext) {
661                                         goAhead.Set ();
662                                         SendNext ();
663                                 }
664                         }
665                 }
666
667                 void Abort (object sender, EventArgs args)
668                 {
669                         HandleError (WebExceptionStatus.RequestCanceled, null);
670                 }
671
672                 internal bool Busy {
673                         get { lock (this) return busy; }
674                 }
675                 
676                 internal bool Connected {
677                         get {
678                                 lock (this) {
679                                         return (socket != null && socket.Connected);
680                                 }
681                         }
682                 }
683                 
684                 ~WebConnection ()
685                 {
686                         Close (false);
687                 }
688         }
689 }
690