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.
31 #if MONO_SECURITY_ALIAS
32 extern alias MonoSecurity
;
35 extern alias PrebuiltSystem
;
38 #if MONO_SECURITY_ALIAS
39 using MSI
= MonoSecurity
::Mono
.Security
.Interface
;
41 using MSI
= Mono
.Security
.Interface
;
44 using XX509CertificateCollection
= PrebuiltSystem
::System
.Security
.Cryptography
.X509Certificates
.X509CertificateCollection
;
46 using XX509CertificateCollection
= System
.Security
.Cryptography
.X509Certificates
.X509CertificateCollection
;
50 using System
.Net
.Sockets
;
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;
68 HttpListenerContext context
;
69 StringBuilder current_line
;
70 ListenerPrefix prefix
;
71 RequestStream i_stream
;
72 ResponseStream o_stream
;
78 int s_timeout
= 90000; // 90k ms for first request, 15k ms from then on
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
)
92 if (secure
== false) {
93 stream
= new NetworkStream (sock
, false);
95 ssl_stream
= epl
.Listener
.CreateSslStream (new NetworkStream (sock
, false), false, (t
, c
, ch
, e
) => {
98 var c2
= c
as X509Certificate2
;
100 c2
= new X509Certificate2 (c
.GetRawCertData ());
102 client_cert_errors
= new int[] { (int)e }
;
105 stream
= ssl_stream
.AuthenticatedStream
;
107 timer
= new Timer (OnTimeout
, null, Timeout
.Infinite
, Timeout
.Infinite
);
111 internal int [] ClientCertificateErrors
{
112 get { return client_cert_errors; }
115 internal X509Certificate2 ClientCertificate
{
116 get { return client_cert; }
121 if (ssl_stream
!= null) {
122 ssl_stream
.AuthenticateAsServer (cert
, true, (SslProtocols
)ServicePointManager
.SecurityProtocol
, false);
125 context_bound
= false;
130 ms
= new MemoryStream ();
132 input_state
= InputState
.RequestLine
;
133 line_state
= LineState
.None
;
134 context
= new HttpListenerContext (this);
137 public bool IsClosed
{
138 get { return (sock == null); }
142 get { return reuses; }
145 public IPEndPoint LocalEndPoint
{
147 if (local_ep
!= null)
150 local_ep
= (IPEndPoint
) sock
.LocalEndPoint
;
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
)
174 public void BeginReadRequest ()
177 buffer
= new byte [BufferSize
];
181 timer
.Change (s_timeout
, Timeout
.Infinite
);
182 stream
.BeginRead (buffer
, 0, BufferSize
, onread_cb
, this);
184 timer
.Change (Timeout
.Infinite
, Timeout
.Infinite
);
190 public RequestStream
GetRequestStream (bool chunked
, long contentlength
)
192 if (i_stream
== null) {
193 byte [] buffer
= ms
.GetBuffer ();
194 int length
= (int) ms
.Length
;
198 context
.Response
.SendChunked
= true;
199 i_stream
= new ChunkedInputStream (context
, stream
, buffer
, position
, length
- position
);
201 i_stream
= new RequestStream (stream
, buffer
, position
, length
- position
, contentlength
);
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
);
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
);
229 nread
= stream
.EndRead (ares
);
230 ms
.Write (buffer
, 0, nread
);
231 if (ms
.Length
> 32768) {
232 SendError ("Bad request", 400);
237 if (ms
!= null && ms
.Length
> 0)
248 // SendError (); // Why bother?
254 if (ProcessInput (ms
)) {
255 if (!context
.HaveError
)
256 context
.Request
.FinishInitialization ();
258 if (context
.HaveError
) {
264 if (!epl
.BindContext (context
)) {
265 SendError ("Invalid host", 400);
269 HttpListener listener
= context
.Listener
;
270 if (last_listener
!= listener
) {
272 listener
.AddConnection (this);
273 last_listener
= listener
;
276 context_bound
= true;
277 listener
.RegisterContext (context
);
280 stream
.BeginRead (buffer
, 0, BufferSize
, onread_cb
, this);
283 void RemoveConnection ()
285 if (last_listener
== null)
286 epl
.RemoveConnection (this);
288 last_listener
.RemoveConnection (this);
302 InputState input_state
= InputState
.RequestLine
;
303 LineState line_state
= LineState
.None
;
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
;
316 if (context
.HaveError
)
323 line
= ReadLine (buffer
, position
, len
- position
, ref used
);
326 context
.ErrorMessage
= "Bad request";
327 context
.ErrorStatus
= 400;
335 if (input_state
== InputState
.RequestLine
)
342 if (input_state
== InputState
.RequestLine
) {
343 context
.Request
.SetRequestLine (line
);
344 input_state
= InputState
.Headers
;
347 context
.Request
.AddHeader (line
);
348 } catch (Exception e
) {
349 context
.ErrorMessage
= e
.Message
;
350 context
.ErrorStatus
= 400;
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
;
369 for (int i
= offset
; i
< last
&& line_state
!= LineState
.LF
; i
++) {
373 line_state
= LineState
.CR
;
374 } else if (b
== 10) {
375 line_state
= LineState
.LF
;
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;
391 public void SendError (string msg
, int status
)
394 HttpListenerResponse response
= context
.Response
;
395 response
.StatusCode
= status
;
396 response
.ContentType
= "text/html";
397 string description
= HttpListenerResponse
.GetStatusDescription (status
);
400 str
= String
.Format ("<h1>{0} ({1})</h1>", description
, msg
);
402 str
= String
.Format ("<h1>{0}</h1>", description
);
404 byte [] error
= context
.Response
.ContentEncoding
.GetBytes (str
);
405 response
.Close (error
, false);
407 // response was already closed
411 public void SendError ()
413 SendError (context
.ErrorMessage
, context
.ErrorStatus
);
419 epl
.UnbindContext (context
);
420 context_bound
= false;
443 internal void Close (bool force_close
)
446 Stream st
= GetResponseStream ();
454 force_close
|= !context
.Request
.KeepAlive
;
456 force_close
= (context
.Response
.Headers
["connection"] == "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.
488 s
.Shutdown (SocketShutdown
.Both
);