Changed API for fetching StackFrames in bulk on multiple threads ThreadMirror.FetchFr...
[mono.git] / mcs / class / System / System.Net / HttpConnection.cs
1 //
2 // System.Net.HttpConnection
3 //
4 // Author:
5 //      Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
6 //
7 // Copyright (c) 2005-2009 Novell, Inc. (http://www.novell.com)
8 // Copyright (c) 2012 Xamarin, Inc. (http://xamarin.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 #if SECURITY_DEP
31
32 #if MONOTOUCH || MONODROID
33 using Mono.Security.Protocol.Tls;
34 #else
35 extern alias MonoSecurity;
36 using MonoSecurity::Mono.Security.Protocol.Tls;
37 #endif
38
39 using System.IO;
40 using System.Net.Sockets;
41 using System.Reflection;
42 using System.Text;
43 using System.Threading;
44 using System.Security.Cryptography;
45 using System.Security.Cryptography.X509Certificates;
46
47 namespace System.Net {
48         sealed class HttpConnection
49         {
50                 static AsyncCallback onread_cb = new AsyncCallback (OnRead);
51                 const int BufferSize = 8192;
52                 Socket sock;
53                 Stream stream;
54                 EndPointListener epl;
55                 MemoryStream ms;
56                 byte [] buffer;
57                 HttpListenerContext context;
58                 StringBuilder current_line;
59                 ListenerPrefix prefix;
60                 RequestStream i_stream;
61                 ResponseStream o_stream;
62                 bool chunked;
63                 int reuses;
64                 bool context_bound;
65                 bool secure;
66                 AsymmetricAlgorithm key;
67                 int s_timeout = 90000; // 90k ms for first request, 15k ms from then on
68                 Timer timer;
69                 IPEndPoint local_ep;
70                 HttpListener last_listener;
71                 int [] client_cert_errors;
72                 X509Certificate2 client_cert;
73
74                 public HttpConnection (Socket sock, EndPointListener epl, bool secure, X509Certificate2 cert, AsymmetricAlgorithm key)
75                 {
76                         this.sock = sock;
77                         this.epl = epl;
78                         this.secure = secure;
79                         this.key = key;
80                         if (secure == false) {
81                                 stream = new NetworkStream (sock, false);
82                         } else {
83                                 SslServerStream ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, true, false);
84                                 ssl_stream.PrivateKeyCertSelectionDelegate += OnPVKSelection;
85                                 ssl_stream.ClientCertValidationDelegate += OnClientCertificateValidation;
86                                 stream = ssl_stream;
87                         }
88                         timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
89                         Init ();
90                 }
91
92                 internal int [] ClientCertificateErrors {
93                         get { return client_cert_errors; }
94                 }
95
96                 internal X509Certificate2 ClientCertificate {
97                         get { return client_cert; }
98                 }
99
100                 bool OnClientCertificateValidation (X509Certificate certificate, int[] errors)
101                 {
102                         if (certificate == null)
103                                 return true;
104                         X509Certificate2 cert = certificate as X509Certificate2;
105                         if (cert == null)
106                                 cert = new X509Certificate2 (certificate.GetRawCertData ());
107                         client_cert = cert;
108                         client_cert_errors = errors;
109                         return true;
110                 }
111
112                 AsymmetricAlgorithm OnPVKSelection (X509Certificate certificate, string targetHost)
113                 {
114                         return key;
115                 }
116
117                 void Init ()
118                 {
119                         context_bound = false;
120                         i_stream = null;
121                         o_stream = null;
122                         prefix = null;
123                         chunked = false;
124                         ms = new MemoryStream ();
125                         position = 0;
126                         input_state = InputState.RequestLine;
127                         line_state = LineState.None;
128                         context = new HttpListenerContext (this);
129                 }
130
131                 public bool IsClosed {
132                         get { return (sock == null); }
133                 }
134
135                 public int Reuses {
136                         get { return reuses; }
137                 }
138
139                 public IPEndPoint LocalEndPoint {
140                         get {
141                                 if (local_ep != null)
142                                         return local_ep;
143
144                                 local_ep = (IPEndPoint) sock.LocalEndPoint;
145                                 return local_ep;
146                         }
147                 }
148
149                 public IPEndPoint RemoteEndPoint {
150                         get { return (IPEndPoint) sock.RemoteEndPoint; }
151                 }
152
153                 public bool IsSecure {
154                         get { return secure; }
155                 }
156
157                 public ListenerPrefix Prefix {
158                         get { return prefix; }
159                         set { prefix = value; }
160                 }
161
162                 void OnTimeout (object unused)
163                 {
164                         CloseSocket ();
165                         Unbind ();
166                 }
167
168                 public void BeginReadRequest ()
169                 {
170                         if (buffer == null)
171                                 buffer = new byte [BufferSize];
172                         try {
173                                 if (reuses == 1)
174                                         s_timeout = 15000;
175                                 timer.Change (s_timeout, Timeout.Infinite);
176                                 stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
177                         } catch {
178                                 timer.Change (Timeout.Infinite, Timeout.Infinite);
179                                 CloseSocket ();
180                                 Unbind ();
181                         }
182                 }
183
184                 public RequestStream GetRequestStream (bool chunked, long contentlength)
185                 {
186                         if (i_stream == null) {
187                                 byte [] buffer = ms.GetBuffer ();
188                                 int length = (int) ms.Length;
189                                 ms = null;
190                                 if (chunked) {
191                                         this.chunked = true;
192                                         context.Response.SendChunked = true;
193                                         i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
194                                 } else {
195                                         i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
196                                 }
197                         }
198                         return i_stream;
199                 }
200
201                 public ResponseStream GetResponseStream ()
202                 {
203                         // TODO: can we get this stream before reading the input?
204                         if (o_stream == null) {
205                                 HttpListener listener = context.Listener;
206                                 bool ign = (listener == null) ? true : listener.IgnoreWriteExceptions;
207                                 o_stream = new ResponseStream (stream, context.Response, ign);
208                         }
209                         return o_stream;
210                 }
211
212                 internal Socket Hijack (out ArraySegment<byte> buffered)
213                 {
214                         // TODO: disable normal request/response.
215                         buffered = new ArraySegment<byte> (ms.GetBuffer(), position, (int)ms.Length - position);
216                         RemoveConnection ();
217                         var s = sock;
218                         sock = null;
219                         o_stream = null;
220                         return s;
221                 }
222
223                 static void OnRead (IAsyncResult ares)
224                 {
225                         HttpConnection cnc = (HttpConnection) ares.AsyncState;
226                         cnc.OnReadInternal (ares);
227                 }
228
229                 void OnReadInternal (IAsyncResult ares)
230                 {
231                         timer.Change (Timeout.Infinite, Timeout.Infinite);
232                         int nread = -1;
233                         try {
234                                 nread = stream.EndRead (ares);
235                                 ms.Write (buffer, 0, nread);
236                                 if (ms.Length > 32768) {
237                                         SendError ("Bad request", 400);
238                                         Close (true);
239                                         return;
240                                 }
241                         } catch {
242                                 if (ms != null && ms.Length > 0)
243                                         SendError ();
244                                 if (sock != null) {
245                                         CloseSocket ();
246                                         Unbind ();
247                                 }
248                                 return;
249                         }
250
251                         if (nread == 0) {
252                                 //if (ms.Length > 0)
253                                 //      SendError (); // Why bother?
254                                 CloseSocket ();
255                                 Unbind ();
256                                 return;
257                         }
258
259                         if (ProcessInput (ms)) {
260                                 if (!context.HaveError)
261                                         context.Request.FinishInitialization ();
262
263                                 if (context.HaveError) {
264                                         SendError ();
265                                         Close (true);
266                                         return;
267                                 }
268
269                                 if (!epl.BindContext (context)) {
270                                         SendError ("Invalid host", 400);
271                                         Close (true);
272                                         return;
273                                 }
274                                 HttpListener listener = context.Listener;
275                                 if (last_listener != listener) {
276                                         RemoveConnection ();
277                                         listener.AddConnection (this);
278                                         last_listener = listener;
279                                 }
280
281                                 context_bound = true;
282                                 listener.RegisterContext (context);
283                                 return;
284                         }
285                         stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
286                 }
287
288                 void RemoveConnection ()
289                 {
290                         if (last_listener == null)
291                                 epl.RemoveConnection (this);
292                         else
293                                 last_listener.RemoveConnection (this);
294                 }
295
296                 enum InputState {
297                         RequestLine,
298                         Headers
299                 }
300
301                 enum LineState {
302                         None,
303                         CR,
304                         LF
305                 }
306
307                 InputState input_state = InputState.RequestLine;
308                 LineState line_state = LineState.None;
309                 int position;
310
311                 // true -> done processing
312                 // false -> need more input
313                 bool ProcessInput (MemoryStream ms)
314                 {
315                         byte [] buffer = ms.GetBuffer ();
316                         int len = (int) ms.Length;
317                         int used = 0;
318                         string line;
319
320                         try {
321                                 line = ReadLine (buffer, position, len - position, ref used);
322                                 position += used;
323                         } catch {
324                                 context.ErrorMessage = "Bad request";
325                                 context.ErrorStatus = 400;
326                                 return true;
327                         }
328
329                         do {
330                                 if (line == null)
331                                         break;
332                                 if (line == "") {
333                                         if (input_state == InputState.RequestLine)
334                                                 continue;
335                                         current_line = null;
336                                         ms = null;
337                                         return true;
338                                 }
339
340                                 if (input_state == InputState.RequestLine) {
341                                         context.Request.SetRequestLine (line);
342                                         input_state = InputState.Headers;
343                                 } else {
344                                         try {
345                                                 context.Request.AddHeader (line);
346                                         } catch (Exception e) {
347                                                 context.ErrorMessage = e.Message;
348                                                 context.ErrorStatus = 400;
349                                                 return true;
350                                         }
351                                 }
352
353                                 if (context.HaveError)
354                                         return true;
355
356                                 if (position >= len)
357                                         break;
358                                 try {
359                                         line = ReadLine (buffer, position, len - position, ref used);
360                                         position += used;
361                                 } catch {
362                                         context.ErrorMessage = "Bad request";
363                                         context.ErrorStatus = 400;
364                                         return true;
365                                 }
366                         } while (line != null);
367
368                         if (used == len) {
369                                 ms.SetLength (0);
370                                 position = 0;
371                         }
372                         return false;
373                 }
374
375                 string ReadLine (byte [] buffer, int offset, int len, ref int used)
376                 {
377                         if (current_line == null)
378                                 current_line = new StringBuilder (128);
379                         int last = offset + len;
380                         used = 0;
381                         for (int i = offset; i < last && line_state != LineState.LF; i++) {
382                                 used++;
383                                 byte b = buffer [i];
384                                 if (b == 13) {
385                                         line_state = LineState.CR;
386                                 } else if (b == 10) {
387                                         line_state = LineState.LF;
388                                 } else {
389                                         current_line.Append ((char) b);
390                                 }
391                         }
392
393                         string result = null;
394                         if (line_state == LineState.LF) {
395                                 line_state = LineState.None;
396                                 result = current_line.ToString ();
397                                 current_line.Length = 0;
398                         }
399
400                         return result;
401                 }
402
403                 public void SendError (string msg, int status)
404                 {
405                         try {
406                                 HttpListenerResponse response = context.Response;
407                                 response.StatusCode = status;
408                                 response.ContentType = "text/html";
409                                 string description = HttpListenerResponse.GetStatusDescription (status);
410                                 string str;
411                                 if (msg != null)
412                                         str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
413                                 else
414                                         str = String.Format ("<h1>{0}</h1>", description);
415
416                                 byte [] error = context.Response.ContentEncoding.GetBytes (str);
417                                 response.Close (error, false);
418                         } catch {
419                                 // response was already closed
420                         }
421                 }
422
423                 public void SendError ()
424                 {
425                         SendError (context.ErrorMessage, context.ErrorStatus);
426                 }
427
428                 void Unbind ()
429                 {
430                         if (context_bound) {
431                                 epl.UnbindContext (context);
432                                 context_bound = false;
433                         }
434                 }
435
436                 public void Close ()
437                 {
438                         Close (false);
439                 }
440
441                 void CloseSocket ()
442                 {
443                         if (sock == null)
444                                 return;
445
446                         try {
447                                 sock.Close ();
448                         } catch {
449                         } finally {
450                                 sock = null;
451                         }
452                         RemoveConnection ();
453                 }
454
455                 internal void Close (bool force_close)
456                 {
457                         if (sock != null) {
458                                 Stream st = GetResponseStream ();
459                                 if (st != null)
460                                         st.Close ();
461
462                                 o_stream = null;
463                         }
464
465                         if (sock != null) {
466                                 force_close |= !context.Request.KeepAlive;
467                                 if (!force_close)
468                                         force_close = (context.Response.Headers ["connection"] == "close");
469                                 /*
470                                 if (!force_close) {
471 //                                      bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
472 //                                                      status_code == 413 || status_code == 414 || status_code == 500 ||
473 //                                                      status_code == 503);
474
475                                         force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
476                                 }
477                                 */
478
479                                 if (!force_close && context.Request.FlushInput ()) {
480                                         if (chunked && context.Response.ForceCloseChunked == false) {
481                                                 // Don't close. Keep working.
482                                                 reuses++;
483                                                 Unbind ();
484                                                 Init ();
485                                                 BeginReadRequest ();
486                                                 return;
487                                         }
488
489                                         reuses++;
490                                         Unbind ();
491                                         Init ();
492                                         BeginReadRequest ();
493                                         return;
494                                 }
495
496                                 Socket s = sock;
497                                 sock = null;
498                                 try {
499                                         if (s != null)
500                                                 s.Shutdown (SocketShutdown.Both);
501                                 } catch {
502                                 } finally {
503                                         if (s != null)
504                                                 s.Close ();
505                                 }
506                                 Unbind ();
507                                 RemoveConnection ();
508                                 return;
509                         }
510                 }
511         }
512 }
513 #endif
514