[Mono.Security]: Add 'MonoTlsProvider.SupportsCleanShutdown' and 'MonoTlsSettings...
[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 using System.IO;
31 using System.Net.Sockets;
32 using System.Text;
33 using System.Threading;
34 using System.Net.Security;
35 using System.Security.Authentication;
36 using System.Security.Cryptography;
37 using System.Security.Cryptography.X509Certificates;
38
39 namespace System.Net {
40         sealed class HttpConnection {
41                 static AsyncCallback onread_cb = new AsyncCallback (OnRead);
42                 const int BufferSize = 8192;
43                 Socket sock;
44                 Stream stream;
45                 EndPointListener epl;
46                 MemoryStream ms;
47                 byte [] buffer;
48                 HttpListenerContext context;
49                 StringBuilder current_line;
50                 ListenerPrefix prefix;
51                 RequestStream i_stream;
52                 ResponseStream o_stream;
53                 bool chunked;
54                 int reuses;
55                 bool context_bound;
56                 bool secure;
57                 X509Certificate cert;
58                 int s_timeout = 90000; // 90k ms for first request, 15k ms from then on
59                 Timer timer;
60                 IPEndPoint local_ep;
61                 HttpListener last_listener;
62                 int [] client_cert_errors;
63                 X509Certificate2 client_cert;
64                 SslStream ssl_stream;
65
66                 public HttpConnection (Socket sock, EndPointListener epl, bool secure, X509Certificate cert)
67                 {
68                         this.sock = sock;
69                         this.epl = epl;
70                         this.secure = secure;
71                         this.cert = cert;
72                         if (secure == false) {
73                                 stream = new NetworkStream (sock, false);
74                         } else {
75                                 ssl_stream = epl.Listener.CreateSslStream (new NetworkStream (sock, false), false, (t, c, ch, e) => {
76                                         if (c == null)
77                                                 return true;
78                                         var c2 = c as X509Certificate2;
79                                         if (c2 == null)
80                                                 c2 = new X509Certificate2 (c.GetRawCertData ());
81                                         client_cert = c2;
82                                         client_cert_errors = new int[] { (int)e };
83                                         return true;
84                                 });
85                                 stream = ssl_stream;
86                         }
87                         timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
88                         if (ssl_stream != null)
89                                 ssl_stream.AuthenticateAsServer (cert, true, (SslProtocols)ServicePointManager.SecurityProtocol, false);
90                         Init ();
91                 }
92
93                 internal SslStream SslStream {
94                         get { return ssl_stream; }
95                 }
96
97                 internal int [] ClientCertificateErrors {
98                         get { return client_cert_errors; }
99                 }
100
101                 internal X509Certificate2 ClientCertificate {
102                         get { return client_cert; }
103                 }
104
105                 void Init ()
106                 {
107                         context_bound = false;
108                         i_stream = null;
109                         o_stream = null;
110                         prefix = null;
111                         chunked = false;
112                         ms = new MemoryStream ();
113                         position = 0;
114                         input_state = InputState.RequestLine;
115                         line_state = LineState.None;
116                         context = new HttpListenerContext (this);
117                 }
118
119                 public bool IsClosed {
120                         get { return (sock == null); }
121                 }
122
123                 public int Reuses {
124                         get { return reuses; }
125                 }
126
127                 public IPEndPoint LocalEndPoint {
128                         get {
129                                 if (local_ep != null)
130                                         return local_ep;
131
132                                 local_ep = (IPEndPoint) sock.LocalEndPoint;
133                                 return local_ep;
134                         }
135                 }
136
137                 public IPEndPoint RemoteEndPoint {
138                         get { return (IPEndPoint) sock.RemoteEndPoint; }
139                 }
140
141                 public bool IsSecure {
142                         get { return secure; }
143                 }
144
145                 public ListenerPrefix Prefix {
146                         get { return prefix; }
147                         set { prefix = value; }
148                 }
149
150                 void OnTimeout (object unused)
151                 {
152                         CloseSocket ();
153                         Unbind ();
154                 }
155
156                 public void BeginReadRequest ()
157                 {
158                         if (buffer == null)
159                                 buffer = new byte [BufferSize];
160                         try {
161                                 if (reuses == 1)
162                                         s_timeout = 15000;
163                                 timer.Change (s_timeout, Timeout.Infinite);
164                                 stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
165                         } catch {
166                                 timer.Change (Timeout.Infinite, Timeout.Infinite);
167                                 CloseSocket ();
168                                 Unbind ();
169                         }
170                 }
171
172                 public RequestStream GetRequestStream (bool chunked, long contentlength)
173                 {
174                         if (i_stream == null) {
175                                 byte [] buffer = ms.GetBuffer ();
176                                 int length = (int) ms.Length;
177                                 ms = null;
178                                 if (chunked) {
179                                         this.chunked = true;
180                                         context.Response.SendChunked = true;
181                                         i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
182                                 } else {
183                                         i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
184                                 }
185                         }
186                         return i_stream;
187                 }
188
189                 public ResponseStream GetResponseStream ()
190                 {
191                         // TODO: can we get this stream before reading the input?
192                         if (o_stream == null) {
193                                 HttpListener listener = context.Listener;
194                                 
195                                 if(listener == null)
196                                         return new ResponseStream (stream, context.Response, true);
197
198                                 o_stream = new ResponseStream (stream, context.Response, listener.IgnoreWriteExceptions);
199                         }
200                         return o_stream;
201                 }
202
203                 static void OnRead (IAsyncResult ares)
204                 {
205                         HttpConnection cnc = (HttpConnection) ares.AsyncState;
206                         cnc.OnReadInternal (ares);
207                 }
208
209                 void OnReadInternal (IAsyncResult ares)
210                 {
211                         timer.Change (Timeout.Infinite, Timeout.Infinite);
212                         int nread = -1;
213                         try {
214                                 nread = stream.EndRead (ares);
215                                 ms.Write (buffer, 0, nread);
216                                 if (ms.Length > 32768) {
217                                         SendError ("Bad request", 400);
218                                         Close (true);
219                                         return;
220                                 }
221                         } catch {
222                                 if (ms != null && ms.Length > 0)
223                                         SendError ();
224                                 if (sock != null) {
225                                         CloseSocket ();
226                                         Unbind ();
227                                 }
228                                 return;
229                         }
230
231                         if (nread == 0) {
232                                 //if (ms.Length > 0)
233                                 //      SendError (); // Why bother?
234                                 CloseSocket ();
235                                 Unbind ();
236                                 return;
237                         }
238
239                         if (ProcessInput (ms)) {
240                                 if (!context.HaveError)
241                                         context.Request.FinishInitialization ();
242
243                                 if (context.HaveError) {
244                                         SendError ();
245                                         Close (true);
246                                         return;
247                                 }
248
249                                 if (!epl.BindContext (context)) {
250                                         SendError ("Invalid host", 400);
251                                         Close (true);
252                                         return;
253                                 }
254                                 HttpListener listener = context.Listener;
255                                 if (last_listener != listener) {
256                                         RemoveConnection ();
257                                         listener.AddConnection (this);
258                                         last_listener = listener;
259                                 }
260
261                                 context_bound = true;
262                                 listener.RegisterContext (context);
263                                 return;
264                         }
265                         stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
266                 }
267
268                 void RemoveConnection ()
269                 {
270                         if (last_listener == null)
271                                 epl.RemoveConnection (this);
272                         else
273                                 last_listener.RemoveConnection (this);
274                 }
275
276                 enum InputState {
277                         RequestLine,
278                         Headers
279                 }
280
281                 enum LineState {
282                         None,
283                         CR,
284                         LF
285                 }
286
287                 InputState input_state = InputState.RequestLine;
288                 LineState line_state = LineState.None;
289                 int position;
290
291                 // true -> done processing
292                 // false -> need more input
293                 bool ProcessInput (MemoryStream ms)
294                 {
295                         byte [] buffer = ms.GetBuffer ();
296                         int len = (int) ms.Length;
297                         int used = 0;
298                         string line;
299
300                         while (true) {
301                                 if (context.HaveError)
302                                         return true;
303
304                                 if (position >= len)
305                                         break;
306
307                                 try {
308                                         line = ReadLine (buffer, position, len - position, ref used);
309                                         position += used;
310                                 } catch {
311                                         context.ErrorMessage = "Bad request";
312                                         context.ErrorStatus = 400;
313                                         return true;
314                                 }
315
316                                 if (line == null)
317                                         break;
318
319                                 if (line == "") {
320                                         if (input_state == InputState.RequestLine)
321                                                 continue;
322                                         current_line = null;
323                                         ms = null;
324                                         return true;
325                                 }
326
327                                 if (input_state == InputState.RequestLine) {
328                                         context.Request.SetRequestLine (line);
329                                         input_state = InputState.Headers;
330                                 } else {
331                                         try {
332                                                 context.Request.AddHeader (line);
333                                         } catch (Exception e) {
334                                                 context.ErrorMessage = e.Message;
335                                                 context.ErrorStatus = 400;
336                                                 return true;
337                                         }
338                                 }
339                         }
340
341                         if (used == len) {
342                                 ms.SetLength (0);
343                                 position = 0;
344                         }
345                         return false;
346                 }
347
348                 string ReadLine (byte [] buffer, int offset, int len, ref int used)
349                 {
350                         if (current_line == null)
351                                 current_line = new StringBuilder (128);
352                         int last = offset + len;
353                         used = 0;
354                         for (int i = offset; i < last && line_state != LineState.LF; i++) {
355                                 used++;
356                                 byte b = buffer [i];
357                                 if (b == 13) {
358                                         line_state = LineState.CR;
359                                 } else if (b == 10) {
360                                         line_state = LineState.LF;
361                                 } else {
362                                         current_line.Append ((char) b);
363                                 }
364                         }
365
366                         string result = null;
367                         if (line_state == LineState.LF) {
368                                 line_state = LineState.None;
369                                 result = current_line.ToString ();
370                                 current_line.Length = 0;
371                         }
372
373                         return result;
374                 }
375
376                 public void SendError (string msg, int status)
377                 {
378                         try {
379                                 HttpListenerResponse response = context.Response;
380                                 response.StatusCode = status;
381                                 response.ContentType = "text/html";
382                                 string description = HttpStatusDescription.Get (status);
383                                 string str;
384                                 if (msg != null)
385                                         str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
386                                 else
387                                         str = String.Format ("<h1>{0}</h1>", description);
388
389                                 byte [] error = context.Response.ContentEncoding.GetBytes (str);
390                                 response.Close (error, false);
391                         } catch {
392                                 // response was already closed
393                         }
394                 }
395
396                 public void SendError ()
397                 {
398                         SendError (context.ErrorMessage, context.ErrorStatus);
399                 }
400
401                 void Unbind ()
402                 {
403                         if (context_bound) {
404                                 epl.UnbindContext (context);
405                                 context_bound = false;
406                         }
407                 }
408
409                 public void Close ()
410                 {
411                         Close (false);
412                 }
413
414                 void CloseSocket ()
415                 {
416                         if (sock == null)
417                                 return;
418
419                         try {
420                                 sock.Close ();
421                         } catch {
422                         } finally {
423                                 sock = null;
424                         }
425                         RemoveConnection ();
426                 }
427
428                 internal void Close (bool force_close)
429                 {
430                         if (sock != null) {
431                                 Stream st = GetResponseStream ();
432                                 if (st != null)
433                                         st.Close ();
434
435                                 o_stream = null;
436                         }
437
438                         if (sock != null) {
439                                 force_close |= !context.Request.KeepAlive;
440                                 if (!force_close)
441                                         force_close = (context.Response.Headers ["connection"] == "close");
442                                 /*
443                                 if (!force_close) {
444 //                                      bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
445 //                                                      status_code == 413 || status_code == 414 || status_code == 500 ||
446 //                                                      status_code == 503);
447
448                                         force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
449                                 }
450                                 */
451
452                                 if (!force_close && context.Request.FlushInput ()) {
453                                         if (chunked && context.Response.ForceCloseChunked == false) {
454                                                 // Don't close. Keep working.
455                                                 reuses++;
456                                                 Unbind ();
457                                                 Init ();
458                                                 BeginReadRequest ();
459                                                 return;
460                                         }
461
462                                         reuses++;
463                                         Unbind ();
464                                         Init ();
465                                         BeginReadRequest ();
466                                         return;
467                                 }
468
469                                 Socket s = sock;
470                                 sock = null;
471                                 try {
472                                         if (s != null)
473                                                 s.Shutdown (SocketShutdown.Both);
474                                 } catch {
475                                 } finally {
476                                         if (s != null)
477                                                 s.Close ();
478                                 }
479                                 Unbind ();
480                                 RemoveConnection ();
481                                 return;
482                         }
483                 }
484         }
485 }
486