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