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