[System] Exclude code that tries to load System.Windows.Forms.dll dynamically on...
[mono-project.git] / mcs / class / System / System.Net / HttpConnection.cs
blob7adfdf8d95660c51e673d3304118705a55d19d0a
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.
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
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
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;
58 namespace System.Net {
59 sealed class HttpConnection
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 X509Certificate 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;
86 public HttpConnection (Socket sock, EndPointListener epl, bool secure, X509Certificate cert)
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;
105 stream = ssl_stream.AuthenticatedStream;
107 timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
108 Init ();
111 internal int [] ClientCertificateErrors {
112 get { return client_cert_errors; }
115 internal X509Certificate2 ClientCertificate {
116 get { return client_cert; }
119 void Init ()
121 if (ssl_stream != null) {
122 ssl_stream.AuthenticateAsServer (cert, true, (SslProtocols)ServicePointManager.SecurityProtocol, false);
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);
137 public bool IsClosed {
138 get { return (sock == null); }
141 public int Reuses {
142 get { return reuses; }
145 public IPEndPoint LocalEndPoint {
146 get {
147 if (local_ep != null)
148 return local_ep;
150 local_ep = (IPEndPoint) sock.LocalEndPoint;
151 return local_ep;
155 public IPEndPoint RemoteEndPoint {
156 get { return (IPEndPoint) sock.RemoteEndPoint; }
159 public bool IsSecure {
160 get { return secure; }
163 public ListenerPrefix Prefix {
164 get { return prefix; }
165 set { prefix = value; }
168 void OnTimeout (object unused)
170 CloseSocket ();
171 Unbind ();
174 public void BeginReadRequest ()
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 ();
190 public RequestStream GetRequestStream (bool chunked, long contentlength)
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);
204 return i_stream;
207 public ResponseStream GetResponseStream ()
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);
215 return o_stream;
218 static void OnRead (IAsyncResult ares)
220 HttpConnection cnc = (HttpConnection) ares.AsyncState;
221 cnc.OnReadInternal (ares);
224 void OnReadInternal (IAsyncResult ares)
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;
236 } catch {
237 if (ms != null && ms.Length > 0)
238 SendError ();
239 if (sock != null) {
240 CloseSocket ();
241 Unbind ();
243 return;
246 if (nread == 0) {
247 //if (ms.Length > 0)
248 // SendError (); // Why bother?
249 CloseSocket ();
250 Unbind ();
251 return;
254 if (ProcessInput (ms)) {
255 if (!context.HaveError)
256 context.Request.FinishInitialization ();
258 if (context.HaveError) {
259 SendError ();
260 Close (true);
261 return;
264 if (!epl.BindContext (context)) {
265 SendError ("Invalid host", 400);
266 Close (true);
267 return;
269 HttpListener listener = context.Listener;
270 if (last_listener != listener) {
271 RemoveConnection ();
272 listener.AddConnection (this);
273 last_listener = listener;
276 context_bound = true;
277 listener.RegisterContext (context);
278 return;
280 stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
283 void RemoveConnection ()
285 if (last_listener == null)
286 epl.RemoveConnection (this);
287 else
288 last_listener.RemoveConnection (this);
291 enum InputState {
292 RequestLine,
293 Headers
296 enum LineState {
297 None,
302 InputState input_state = InputState.RequestLine;
303 LineState line_state = LineState.None;
304 int position;
306 // true -> done processing
307 // false -> need more input
308 bool ProcessInput (MemoryStream ms)
310 byte [] buffer = ms.GetBuffer ();
311 int len = (int) ms.Length;
312 int used = 0;
313 string line;
315 while (true) {
316 if (context.HaveError)
317 return true;
319 if (position >= len)
320 break;
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;
331 if (line == null)
332 break;
334 if (line == "") {
335 if (input_state == InputState.RequestLine)
336 continue;
337 current_line = null;
338 ms = null;
339 return true;
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;
356 if (used == len) {
357 ms.SetLength (0);
358 position = 0;
360 return false;
363 string ReadLine (byte [] buffer, int offset, int len, ref int used)
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);
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;
388 return result;
391 public void SendError (string msg, int status)
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);
404 byte [] error = context.Response.ContentEncoding.GetBytes (str);
405 response.Close (error, false);
406 } catch {
407 // response was already closed
411 public void SendError ()
413 SendError (context.ErrorMessage, context.ErrorStatus);
416 void Unbind ()
418 if (context_bound) {
419 epl.UnbindContext (context);
420 context_bound = false;
424 public void Close ()
426 Close (false);
429 void CloseSocket ()
431 if (sock == null)
432 return;
434 try {
435 sock.Close ();
436 } catch {
437 } finally {
438 sock = null;
440 RemoveConnection ();
443 internal void Close (bool force_close)
445 if (sock != null) {
446 Stream st = GetResponseStream ();
447 if (st != null)
448 st.Close ();
450 o_stream = null;
453 if (sock != null) {
454 force_close |= !context.Request.KeepAlive;
455 if (!force_close)
456 force_close = (context.Response.Headers ["connection"] == "close");
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);
463 force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
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;
477 reuses++;
478 Unbind ();
479 Init ();
480 BeginReadRequest ();
481 return;
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 ();
494 Unbind ();
495 RemoveConnection ();
496 return;
501 #endif