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