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