2 // System.Net.HttpConnection
5 // Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
7 // Copyright (c) 2005-2009 Novell, Inc. (http://www.novell.com)
8 // Copyright (c) 2012 Xamarin, Inc. (http://xamarin.com)
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:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
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.
32 #if MONOTOUCH || MONODROID
33 using Mono.Security.Protocol.Tls;
35 extern alias MonoSecurity;
36 using MonoSecurity::Mono.Security.Protocol.Tls;
40 using System.Net.Sockets;
41 using System.Reflection;
43 using System.Threading;
44 using System.Security.Cryptography;
45 using System.Security.Cryptography.X509Certificates;
47 namespace System.Net {
48 sealed class HttpConnection
50 static AsyncCallback onread_cb = new AsyncCallback (OnRead);
51 const int BufferSize = 8192;
57 HttpListenerContext context;
58 StringBuilder current_line;
59 ListenerPrefix prefix;
60 RequestStream i_stream;
61 ResponseStream o_stream;
66 AsymmetricAlgorithm key;
67 int s_timeout = 90000; // 90k ms for first request, 15k ms from then on
70 HttpListener last_listener;
71 int [] client_cert_errors;
72 X509Certificate2 client_cert;
74 public HttpConnection (Socket sock, EndPointListener epl, bool secure, X509Certificate2 cert, AsymmetricAlgorithm key)
80 if (secure == false) {
81 stream = new NetworkStream (sock, false);
83 SslServerStream ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, true, false);
84 ssl_stream.PrivateKeyCertSelectionDelegate += OnPVKSelection;
85 ssl_stream.ClientCertValidationDelegate += OnClientCertificateValidation;
88 timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
92 internal int [] ClientCertificateErrors {
93 get { return client_cert_errors; }
96 internal X509Certificate2 ClientCertificate {
97 get { return client_cert; }
100 bool OnClientCertificateValidation (X509Certificate certificate, int[] errors)
102 if (certificate == null)
104 X509Certificate2 cert = certificate as X509Certificate2;
106 cert = new X509Certificate2 (certificate.GetRawCertData ());
108 client_cert_errors = errors;
112 AsymmetricAlgorithm OnPVKSelection (X509Certificate certificate, string targetHost)
119 context_bound = false;
124 ms = new MemoryStream ();
126 input_state = InputState.RequestLine;
127 line_state = LineState.None;
128 context = new HttpListenerContext (this);
131 public bool IsClosed {
132 get { return (sock == null); }
136 get { return reuses; }
139 public IPEndPoint LocalEndPoint {
141 if (local_ep != null)
144 local_ep = (IPEndPoint) sock.LocalEndPoint;
149 public IPEndPoint RemoteEndPoint {
150 get { return (IPEndPoint) sock.RemoteEndPoint; }
153 public bool IsSecure {
154 get { return secure; }
157 public ListenerPrefix Prefix {
158 get { return prefix; }
159 set { prefix = value; }
162 void OnTimeout (object unused)
168 public void BeginReadRequest ()
171 buffer = new byte [BufferSize];
175 timer.Change (s_timeout, Timeout.Infinite);
176 stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
178 timer.Change (Timeout.Infinite, Timeout.Infinite);
184 public RequestStream GetRequestStream (bool chunked, long contentlength)
186 if (i_stream == null) {
187 byte [] buffer = ms.GetBuffer ();
188 int length = (int) ms.Length;
192 context.Response.SendChunked = true;
193 i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
195 i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
201 public ResponseStream GetResponseStream ()
203 // TODO: can we get this stream before reading the input?
204 if (o_stream == null) {
205 HttpListener listener = context.Listener;
206 bool ign = (listener == null) ? true : listener.IgnoreWriteExceptions;
207 o_stream = new ResponseStream (stream, context.Response, ign);
212 internal Socket Hijack (out ArraySegment<byte> buffered)
214 // TODO: disable normal request/response.
215 buffered = new ArraySegment<byte> (ms.GetBuffer(), position, (int)ms.Length - position);
223 static void OnRead (IAsyncResult ares)
225 HttpConnection cnc = (HttpConnection) ares.AsyncState;
226 cnc.OnReadInternal (ares);
229 void OnReadInternal (IAsyncResult ares)
231 timer.Change (Timeout.Infinite, Timeout.Infinite);
234 nread = stream.EndRead (ares);
235 ms.Write (buffer, 0, nread);
236 if (ms.Length > 32768) {
237 SendError ("Bad request", 400);
242 if (ms != null && ms.Length > 0)
253 // SendError (); // Why bother?
259 if (ProcessInput (ms)) {
260 if (!context.HaveError)
261 context.Request.FinishInitialization ();
263 if (context.HaveError) {
269 if (!epl.BindContext (context)) {
270 SendError ("Invalid host", 400);
274 HttpListener listener = context.Listener;
275 if (last_listener != listener) {
277 listener.AddConnection (this);
278 last_listener = listener;
281 context_bound = true;
282 listener.RegisterContext (context);
285 stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
288 void RemoveConnection ()
290 if (last_listener == null)
291 epl.RemoveConnection (this);
293 last_listener.RemoveConnection (this);
307 InputState input_state = InputState.RequestLine;
308 LineState line_state = LineState.None;
311 // true -> done processing
312 // false -> need more input
313 bool ProcessInput (MemoryStream ms)
315 byte [] buffer = ms.GetBuffer ();
316 int len = (int) ms.Length;
321 line = ReadLine (buffer, position, len - position, ref used);
324 context.ErrorMessage = "Bad request";
325 context.ErrorStatus = 400;
333 if (input_state == InputState.RequestLine)
340 if (input_state == InputState.RequestLine) {
341 context.Request.SetRequestLine (line);
342 input_state = InputState.Headers;
345 context.Request.AddHeader (line);
346 } catch (Exception e) {
347 context.ErrorMessage = e.Message;
348 context.ErrorStatus = 400;
353 if (context.HaveError)
359 line = ReadLine (buffer, position, len - position, ref used);
362 context.ErrorMessage = "Bad request";
363 context.ErrorStatus = 400;
366 } while (line != null);
375 string ReadLine (byte [] buffer, int offset, int len, ref int used)
377 if (current_line == null)
378 current_line = new StringBuilder (128);
379 int last = offset + len;
381 for (int i = offset; i < last && line_state != LineState.LF; i++) {
385 line_state = LineState.CR;
386 } else if (b == 10) {
387 line_state = LineState.LF;
389 current_line.Append ((char) b);
393 string result = null;
394 if (line_state == LineState.LF) {
395 line_state = LineState.None;
396 result = current_line.ToString ();
397 current_line.Length = 0;
403 public void SendError (string msg, int status)
406 HttpListenerResponse response = context.Response;
407 response.StatusCode = status;
408 response.ContentType = "text/html";
409 string description = HttpListenerResponse.GetStatusDescription (status);
412 str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
414 str = String.Format ("<h1>{0}</h1>", description);
416 byte [] error = context.Response.ContentEncoding.GetBytes (str);
417 response.Close (error, false);
419 // response was already closed
423 public void SendError ()
425 SendError (context.ErrorMessage, context.ErrorStatus);
431 epl.UnbindContext (context);
432 context_bound = false;
455 internal void Close (bool force_close)
458 Stream st = GetResponseStream ();
466 force_close |= !context.Request.KeepAlive;
468 force_close = (context.Response.Headers ["connection"] == "close");
471 // bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
472 // status_code == 413 || status_code == 414 || status_code == 500 ||
473 // status_code == 503);
475 force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
479 if (!force_close && context.Request.FlushInput ()) {
480 if (chunked && context.Response.ForceCloseChunked == false) {
481 // Don't close. Keep working.
500 s.Shutdown (SocketShutdown.Both);