Merge branch 'sgen-disable-gc'
[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                         }
151                 }
152
153                 public RequestStream GetRequestStream (bool chunked, long contentlength)
154                 {
155                         if (i_stream == null) {
156                                 byte [] buffer = ms.GetBuffer ();
157                                 int length = (int) ms.Length;
158                                 ms = null;
159                                 if (chunked) {
160                                         this.chunked = true;
161                                         context.Response.SendChunked = true;
162                                         i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
163                                 } else {
164                                         i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
165                                 }
166                         }
167                         return i_stream;
168                 }
169
170                 public ResponseStream GetResponseStream ()
171                 {
172                         // TODO: can we get this stream before reading the input?
173                         if (o_stream == null) {
174                                 HttpListener listener = context.Listener;
175                                 bool ign = (listener == null) ? true : listener.IgnoreWriteExceptions;
176                                 o_stream = new ResponseStream (stream, context.Response, ign);
177                         }
178                         return o_stream;
179                 }
180
181                 static void OnRead (IAsyncResult ares)
182                 {
183                         HttpConnection cnc = (HttpConnection) ares.AsyncState;
184                         cnc.OnReadInternal (ares);
185                 }
186
187                 void OnReadInternal (IAsyncResult ares)
188                 {
189                         timer.Change (Timeout.Infinite, Timeout.Infinite);
190                         int nread = -1;
191                         try {
192                                 nread = stream.EndRead (ares);
193                                 ms.Write (buffer, 0, nread);
194                                 if (ms.Length > 32768) {
195                                         SendError ("Bad request", 400);
196                                         Close (true);
197                                         return;
198                                 }
199                         } catch {
200                                 if (ms != null && ms.Length > 0)
201                                         SendError ();
202                                 if (sock != null)
203                                         CloseSocket ();
204                                 return;
205                         }
206
207                         if (nread == 0) {
208                                 //if (ms.Length > 0)
209                                 //      SendError (); // Why bother?
210                                 CloseSocket ();
211                                 return;
212                         }
213
214                         if (ProcessInput (ms)) {
215                                 if (!context.HaveError)
216                                         context.Request.FinishInitialization ();
217
218                                 if (context.HaveError) {
219                                         SendError ();
220                                         Close (true);
221                                         return;
222                                 }
223
224                                 if (!epl.BindContext (context)) {
225                                         SendError ("Invalid host", 400);
226                                         Close (true);
227                                 }
228                                 if (last_listener == null)
229                                         epl.RemoveConnection (this);
230                                 else
231                                         last_listener.RemoveConnection (this);
232
233                                 if (context.Listener != null) {
234                                         context.Listener.AddConnection (this);
235                                         context_bound = true;
236                                 }
237                                 last_listener = context.Listener;
238                                 return;
239                         }
240                         stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
241                 }
242
243                 enum InputState {
244                         RequestLine,
245                         Headers
246                 }
247
248                 enum LineState {
249                         None,
250                         CR,
251                         LF
252                 }
253
254                 InputState input_state = InputState.RequestLine;
255                 LineState line_state = LineState.None;
256                 int position;
257
258                 // true -> done processing
259                 // false -> need more input
260                 bool ProcessInput (MemoryStream ms)
261                 {
262                         byte [] buffer = ms.GetBuffer ();
263                         int len = (int) ms.Length;
264                         int used = 0;
265                         string line;
266
267                         try {
268                                 line = ReadLine (buffer, position, len - position, ref used);
269                                 position += used;
270                         } catch {
271                                 context.ErrorMessage = "Bad request";
272                                 context.ErrorStatus = 400;
273                                 return true;
274                         }
275
276                         do {
277                                 if (line == null)
278                                         break;
279                                 if (line == "") {
280                                         if (input_state == InputState.RequestLine)
281                                                 continue;
282                                         current_line = null;
283                                         ms = null;
284                                         return true;
285                                 }
286
287                                 if (input_state == InputState.RequestLine) {
288                                         context.Request.SetRequestLine (line);
289                                         input_state = InputState.Headers;
290                                 } else {
291                                         try {
292                                                 context.Request.AddHeader (line);
293                                         } catch (Exception e) {
294                                                 context.ErrorMessage = e.Message;
295                                                 context.ErrorStatus = 400;
296                                                 return true;
297                                         }
298                                 }
299
300                                 if (context.HaveError)
301                                         return true;
302
303                                 if (position >= len)
304                                         break;
305                                 try {
306                                         line = ReadLine (buffer, position, len - position, ref used);
307                                         position += used;
308                                 } catch {
309                                         context.ErrorMessage = "Bad request";
310                                         context.ErrorStatus = 400;
311                                         return true;
312                                 }
313                         } while (line != null);
314
315                         if (used == len) {
316                                 ms.SetLength (0);
317                                 position = 0;
318                         }
319                         return false;
320                 }
321
322                 string ReadLine (byte [] buffer, int offset, int len, ref int used)
323                 {
324                         if (current_line == null)
325                                 current_line = new StringBuilder (128);
326                         int last = offset + len;
327                         used = 0;
328                         for (int i = offset; i < last && line_state != LineState.LF; i++) {
329                                 used++;
330                                 byte b = buffer [i];
331                                 if (b == 13) {
332                                         line_state = LineState.CR;
333                                 } else if (b == 10) {
334                                         line_state = LineState.LF;
335                                 } else {
336                                         current_line.Append ((char) b);
337                                 }
338                         }
339
340                         string result = null;
341                         if (line_state == LineState.LF) {
342                                 line_state = LineState.None;
343                                 result = current_line.ToString ();
344                                 current_line.Length = 0;
345                         }
346
347                         return result;
348                 }
349
350                 public void SendError (string msg, int status)
351                 {
352                         try {
353                                 HttpListenerResponse response = context.Response;
354                                 response.StatusCode = status;
355                                 response.ContentType = "text/html";
356                                 string description = HttpListenerResponse.GetStatusDescription (status);
357                                 string str;
358                                 if (msg != null)
359                                         str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
360                                 else
361                                         str = String.Format ("<h1>{0}</h1>", description);
362
363                                 byte [] error = context.Response.ContentEncoding.GetBytes (str);
364                                 response.Close (error, false);
365                         } catch {
366                                 // response was already closed
367                         }
368                 }
369
370                 public void SendError ()
371                 {
372                         SendError (context.ErrorMessage, context.ErrorStatus);
373                 }
374
375                 void Unbind ()
376                 {
377                         if (context_bound) {
378                                 context.Listener.RemoveConnection (this);
379                                 epl.UnbindContext (context);
380                                 context_bound = false;
381                         }
382                 }
383
384                 public void Close ()
385                 {
386                         Close (false);
387                 }
388
389                 void CloseSocket ()
390                 {
391                         if (sock == null)
392                                 return;
393
394                         try {
395                                 sock.Close ();
396                         } catch {
397                         } finally {
398                                 sock = null;
399                         }
400                         if (last_listener == null)
401                                 epl.RemoveConnection (this);
402                 }
403
404                 internal void Close (bool force_close)
405                 {
406                         if (sock != null) {
407                                 Stream st = GetResponseStream ();
408                                 st.Close ();
409                                 o_stream = null;
410                         }
411
412                         if (sock != null) {
413                                 force_close |= !context.Request.KeepAlive;
414                                 if (!force_close)
415                                         force_close = (context.Response.Headers ["connection"] == "close");
416                                 /*
417                                 if (!force_close) {
418 //                                      bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
419 //                                                      status_code == 413 || status_code == 414 || status_code == 500 ||
420 //                                                      status_code == 503);
421
422                                         force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
423                                 }
424                                 */
425
426                                 if (!force_close && context.Request.FlushInput ()) {
427                                         if (chunked && context.Response.ForceCloseChunked == false) {
428                                                 // Don't close. Keep working.
429                                                 reuses++;
430                                                 Unbind ();
431                                                 Init ();
432                                                 BeginReadRequest ();
433                                                 return;
434                                         }
435
436                                         reuses++;
437                                         Unbind ();
438                                         Init ();
439                                         BeginReadRequest ();
440                                         return;
441                                 }
442
443                                 Socket s = sock;
444                                 sock = null;
445                                 try {
446                                         if (s != null)
447                                                 s.Shutdown (SocketShutdown.Both);
448                                 } catch {
449                                 } finally {
450                                         if (s != null)
451                                                 s.Close ();
452                                 }
453                                 Unbind ();
454                                 if (last_listener == null)
455                                         epl.RemoveConnection (this);
456                                 return;
457                         }
458                 }
459         }
460 }
461 #endif
462