2 // System.Net.HttpConnection
5 // Gonzalo Paniagua Javier (gonzalo@novell.com)
7 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
32 using System.Net.Sockets;
33 using System.Reflection;
35 using System.Threading;
36 using System.Security.Cryptography;
37 using System.Security.Cryptography.X509Certificates;
38 using Mono.Security.Protocol.Tls;
40 namespace System.Net {
41 sealed class HttpConnection
43 static AsyncCallback onread_cb = new AsyncCallback (OnRead);
44 const int BufferSize = 8192;
50 HttpListenerContext context;
51 StringBuilder current_line;
52 ListenerPrefix prefix;
53 RequestStream i_stream;
54 ResponseStream o_stream;
59 AsymmetricAlgorithm key;
60 int s_timeout = 90000; // 90k ms for first request, 15k ms from then on
63 HttpListener last_listener;
65 public HttpConnection (Socket sock, EndPointListener epl, bool secure, X509Certificate2 cert, AsymmetricAlgorithm key)
71 if (secure == false) {
72 stream = new NetworkStream (sock, false);
74 SslServerStream ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, false);
75 ssl_stream.PrivateKeyCertSelectionDelegate += OnPVKSelection;
78 timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
82 AsymmetricAlgorithm OnPVKSelection (X509Certificate certificate, string targetHost)
89 context_bound = false;
94 ms = new MemoryStream ();
96 input_state = InputState.RequestLine;
97 line_state = LineState.None;
98 context = new HttpListenerContext (this);
101 public bool IsClosed {
102 get { return (sock == null); }
106 get { return reuses; }
109 public IPEndPoint LocalEndPoint {
111 if (local_ep != null)
114 local_ep = (IPEndPoint) sock.LocalEndPoint;
119 public IPEndPoint RemoteEndPoint {
120 get { return (IPEndPoint) sock.RemoteEndPoint; }
123 public bool IsSecure {
124 get { return secure; }
127 public ListenerPrefix Prefix {
128 get { return prefix; }
129 set { prefix = value; }
132 void OnTimeout (object unused)
138 public void BeginReadRequest ()
141 buffer = new byte [BufferSize];
145 timer.Change (s_timeout, Timeout.Infinite);
146 stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
148 timer.Change (Timeout.Infinite, Timeout.Infinite);
153 public RequestStream GetRequestStream (bool chunked, long contentlength)
155 if (i_stream == null) {
156 byte [] buffer = ms.GetBuffer ();
157 int length = (int) ms.Length;
161 context.Response.SendChunked = true;
162 i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
164 i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
170 public ResponseStream GetResponseStream ()
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);
181 static void OnRead (IAsyncResult ares)
183 HttpConnection cnc = (HttpConnection) ares.AsyncState;
184 cnc.OnReadInternal (ares);
187 void OnReadInternal (IAsyncResult ares)
189 timer.Change (Timeout.Infinite, Timeout.Infinite);
192 nread = stream.EndRead (ares);
193 ms.Write (buffer, 0, nread);
194 if (ms.Length > 32768) {
195 SendError ("Bad request", 400);
200 if (ms != null && ms.Length > 0)
209 // SendError (); // Why bother?
214 if (ProcessInput (ms)) {
215 if (!context.HaveError)
216 context.Request.FinishInitialization ();
218 if (context.HaveError) {
224 if (!epl.BindContext (context)) {
225 SendError ("Invalid host", 400);
228 if (last_listener == null)
229 epl.RemoveConnection (this);
231 last_listener.RemoveConnection (this);
233 if (context.Listener != null) {
234 context.Listener.AddConnection (this);
235 context_bound = true;
237 last_listener = context.Listener;
240 stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
254 InputState input_state = InputState.RequestLine;
255 LineState line_state = LineState.None;
258 // true -> done processing
259 // false -> need more input
260 bool ProcessInput (MemoryStream ms)
262 byte [] buffer = ms.GetBuffer ();
263 int len = (int) ms.Length;
268 line = ReadLine (buffer, position, len - position, ref used);
271 context.ErrorMessage = "Bad request";
272 context.ErrorStatus = 400;
280 if (input_state == InputState.RequestLine)
287 if (input_state == InputState.RequestLine) {
288 context.Request.SetRequestLine (line);
289 input_state = InputState.Headers;
292 context.Request.AddHeader (line);
293 } catch (Exception e) {
294 context.ErrorMessage = e.Message;
295 context.ErrorStatus = 400;
300 if (context.HaveError)
306 line = ReadLine (buffer, position, len - position, ref used);
309 context.ErrorMessage = "Bad request";
310 context.ErrorStatus = 400;
313 } while (line != null);
322 string ReadLine (byte [] buffer, int offset, int len, ref int used)
324 if (current_line == null)
325 current_line = new StringBuilder (128);
326 int last = offset + len;
328 for (int i = offset; i < last && line_state != LineState.LF; i++) {
332 line_state = LineState.CR;
333 } else if (b == 10) {
334 line_state = LineState.LF;
336 current_line.Append ((char) b);
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;
350 public void SendError (string msg, int status)
353 HttpListenerResponse response = context.Response;
354 response.StatusCode = status;
355 response.ContentType = "text/html";
356 string description = HttpListenerResponse.GetStatusDescription (status);
359 str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
361 str = String.Format ("<h1>{0}</h1>", description);
363 byte [] error = context.Response.ContentEncoding.GetBytes (str);
364 response.Close (error, false);
366 // response was already closed
370 public void SendError ()
372 SendError (context.ErrorMessage, context.ErrorStatus);
378 context.Listener.RemoveConnection (this);
379 epl.UnbindContext (context);
380 context_bound = false;
400 if (last_listener == null)
401 epl.RemoveConnection (this);
404 internal void Close (bool force_close)
407 Stream st = GetResponseStream ();
413 force_close |= !context.Request.KeepAlive;
415 force_close = (context.Response.Headers ["connection"] == "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);
422 force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
426 if (!force_close && context.Request.FlushInput ()) {
427 if (chunked && context.Response.ForceCloseChunked == false) {
428 // Don't close. Keep working.
447 s.Shutdown (SocketShutdown.Both);
454 if (last_listener == null)
455 epl.RemoveConnection (this);