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. 28 // 29 30 using System.IO; 31 using System.Net.Sockets; 32 using System.Text; 33 using System.Threading; 34 using System.Net.Security; 35 using System.Security.Authentication; 36 using System.Security.Cryptography; 37 using System.Security.Cryptography.X509Certificates; 38 39 namespace System.Net { 40 sealed class HttpConnection { 41 static AsyncCallback onread_cb = new AsyncCallback (OnRead); 42 const int BufferSize = 8192; 43 Socket sock; 44 Stream stream; 45 EndPointListener epl; 46 MemoryStream ms; 47 byte [] buffer; 48 HttpListenerContext context; 49 StringBuilder current_line; 50 ListenerPrefix prefix; 51 RequestStream i_stream; 52 ResponseStream o_stream; 53 bool chunked; 54 int reuses; 55 bool context_bound; 56 bool secure; 57 X509Certificate cert; 58 int s_timeout = 90000; // 90k ms for first request, 15k ms from then on 59 Timer timer; 60 IPEndPoint local_ep; 61 HttpListener last_listener; 62 int [] client_cert_errors; 63 X509Certificate2 client_cert; 64 SslStream ssl_stream; 65 HttpConnection(Socket sock, EndPointListener epl, bool secure, X509Certificate cert)66 public HttpConnection (Socket sock, EndPointListener epl, bool secure, X509Certificate cert) 67 { 68 this.sock = sock; 69 this.epl = epl; 70 this.secure = secure; 71 this.cert = cert; 72 if (secure == false) { 73 stream = new NetworkStream (sock, false); 74 } else { 75 ssl_stream = epl.Listener.CreateSslStream (new NetworkStream (sock, false), false, (t, c, ch, e) => { 76 if (c == null) 77 return true; 78 var c2 = c as X509Certificate2; 79 if (c2 == null) 80 c2 = new X509Certificate2 (c.GetRawCertData ()); 81 client_cert = c2; 82 client_cert_errors = new int[] { (int)e }; 83 return true; 84 }); 85 stream = ssl_stream; 86 } 87 timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite); 88 if (ssl_stream != null) 89 ssl_stream.AuthenticateAsServer (cert, true, (SslProtocols)ServicePointManager.SecurityProtocol, false); 90 Init (); 91 } 92 93 internal SslStream SslStream { 94 get { return ssl_stream; } 95 } 96 97 internal int [] ClientCertificateErrors { 98 get { return client_cert_errors; } 99 } 100 101 internal X509Certificate2 ClientCertificate { 102 get { return client_cert; } 103 } 104 Init()105 void Init () 106 { 107 context_bound = false; 108 i_stream = null; 109 o_stream = null; 110 prefix = null; 111 chunked = false; 112 ms = new MemoryStream (); 113 position = 0; 114 input_state = InputState.RequestLine; 115 line_state = LineState.None; 116 context = new HttpListenerContext (this); 117 } 118 119 public bool IsClosed { 120 get { return (sock == null); } 121 } 122 123 public int Reuses { 124 get { return reuses; } 125 } 126 127 public IPEndPoint LocalEndPoint { 128 get { 129 if (local_ep != null) 130 return local_ep; 131 132 local_ep = (IPEndPoint) sock.LocalEndPoint; 133 return local_ep; 134 } 135 } 136 137 public IPEndPoint RemoteEndPoint { 138 get { return (IPEndPoint) sock.RemoteEndPoint; } 139 } 140 141 public bool IsSecure { 142 get { return secure; } 143 } 144 145 public ListenerPrefix Prefix { 146 get { return prefix; } 147 set { prefix = value; } 148 } 149 OnTimeout(object unused)150 void OnTimeout (object unused) 151 { 152 CloseSocket (); 153 Unbind (); 154 } 155 BeginReadRequest()156 public void BeginReadRequest () 157 { 158 if (buffer == null) 159 buffer = new byte [BufferSize]; 160 try { 161 if (reuses == 1) 162 s_timeout = 15000; 163 timer.Change (s_timeout, Timeout.Infinite); 164 stream.BeginRead (buffer, 0, BufferSize, onread_cb, this); 165 } catch { 166 timer.Change (Timeout.Infinite, Timeout.Infinite); 167 CloseSocket (); 168 Unbind (); 169 } 170 } 171 GetRequestStream(bool chunked, long contentlength)172 public RequestStream GetRequestStream (bool chunked, long contentlength) 173 { 174 if (i_stream == null) { 175 byte [] buffer = ms.GetBuffer (); 176 int length = (int) ms.Length; 177 ms = null; 178 if (chunked) { 179 this.chunked = true; 180 context.Response.SendChunked = true; 181 i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position); 182 } else { 183 i_stream = new RequestStream (stream, buffer, position, length - position, contentlength); 184 } 185 } 186 return i_stream; 187 } 188 GetResponseStream()189 public ResponseStream GetResponseStream () 190 { 191 // TODO: can we get this stream before reading the input? 192 if (o_stream == null) { 193 HttpListener listener = context.Listener; 194 195 if(listener == null) 196 return new ResponseStream (stream, context.Response, true); 197 198 o_stream = new ResponseStream (stream, context.Response, listener.IgnoreWriteExceptions); 199 } 200 return o_stream; 201 } 202 OnRead(IAsyncResult ares)203 static void OnRead (IAsyncResult ares) 204 { 205 HttpConnection cnc = (HttpConnection) ares.AsyncState; 206 cnc.OnReadInternal (ares); 207 } 208 OnReadInternal(IAsyncResult ares)209 void OnReadInternal (IAsyncResult ares) 210 { 211 timer.Change (Timeout.Infinite, Timeout.Infinite); 212 int nread = -1; 213 try { 214 nread = stream.EndRead (ares); 215 ms.Write (buffer, 0, nread); 216 if (ms.Length > 32768) { 217 SendError ("Bad request", 400); 218 Close (true); 219 return; 220 } 221 } catch { 222 if (ms != null && ms.Length > 0) 223 SendError (); 224 if (sock != null) { 225 CloseSocket (); 226 Unbind (); 227 } 228 return; 229 } 230 231 if (nread == 0) { 232 //if (ms.Length > 0) 233 // SendError (); // Why bother? 234 CloseSocket (); 235 Unbind (); 236 return; 237 } 238 239 if (ProcessInput (ms)) { 240 if (!context.HaveError) 241 context.Request.FinishInitialization (); 242 243 if (context.HaveError) { 244 SendError (); 245 Close (true); 246 return; 247 } 248 249 if (!epl.BindContext (context)) { 250 SendError ("Invalid host", 400); 251 Close (true); 252 return; 253 } 254 HttpListener listener = context.Listener; 255 if (last_listener != listener) { 256 RemoveConnection (); 257 listener.AddConnection (this); 258 last_listener = listener; 259 } 260 261 context_bound = true; 262 listener.RegisterContext (context); 263 return; 264 } 265 stream.BeginRead (buffer, 0, BufferSize, onread_cb, this); 266 } 267 RemoveConnection()268 void RemoveConnection () 269 { 270 if (last_listener == null) 271 epl.RemoveConnection (this); 272 else 273 last_listener.RemoveConnection (this); 274 } 275 276 enum InputState { 277 RequestLine, 278 Headers 279 } 280 281 enum LineState { 282 None, 283 CR, 284 LF 285 } 286 287 InputState input_state = InputState.RequestLine; 288 LineState line_state = LineState.None; 289 int position; 290 291 // true -> done processing 292 // false -> need more input ProcessInput(MemoryStream ms)293 bool ProcessInput (MemoryStream ms) 294 { 295 byte [] buffer = ms.GetBuffer (); 296 int len = (int) ms.Length; 297 int used = 0; 298 string line; 299 300 while (true) { 301 if (context.HaveError) 302 return true; 303 304 if (position >= len) 305 break; 306 307 try { 308 line = ReadLine (buffer, position, len - position, ref used); 309 position += used; 310 } catch { 311 context.ErrorMessage = "Bad request"; 312 context.ErrorStatus = 400; 313 return true; 314 } 315 316 if (line == null) 317 break; 318 319 if (line == "") { 320 if (input_state == InputState.RequestLine) 321 continue; 322 current_line = null; 323 ms = null; 324 return true; 325 } 326 327 if (input_state == InputState.RequestLine) { 328 context.Request.SetRequestLine (line); 329 input_state = InputState.Headers; 330 } else { 331 try { 332 context.Request.AddHeader (line); 333 } catch (Exception e) { 334 context.ErrorMessage = e.Message; 335 context.ErrorStatus = 400; 336 return true; 337 } 338 } 339 } 340 341 if (used == len) { 342 ms.SetLength (0); 343 position = 0; 344 } 345 return false; 346 } 347 ReadLine(byte [] buffer, int offset, int len, ref int used)348 string ReadLine (byte [] buffer, int offset, int len, ref int used) 349 { 350 if (current_line == null) 351 current_line = new StringBuilder (128); 352 int last = offset + len; 353 used = 0; 354 for (int i = offset; i < last && line_state != LineState.LF; i++) { 355 used++; 356 byte b = buffer [i]; 357 if (b == 13) { 358 line_state = LineState.CR; 359 } else if (b == 10) { 360 line_state = LineState.LF; 361 } else { 362 current_line.Append ((char) b); 363 } 364 } 365 366 string result = null; 367 if (line_state == LineState.LF) { 368 line_state = LineState.None; 369 result = current_line.ToString (); 370 current_line.Length = 0; 371 } 372 373 return result; 374 } 375 SendError(string msg, int status)376 public void SendError (string msg, int status) 377 { 378 try { 379 HttpListenerResponse response = context.Response; 380 response.StatusCode = status; 381 response.ContentType = "text/html"; 382 string description = HttpStatusDescription.Get (status); 383 string str; 384 if (msg != null) 385 str = String.Format ("<h1>{0} ({1})</h1>", description, msg); 386 else 387 str = String.Format ("<h1>{0}</h1>", description); 388 389 byte [] error = context.Response.ContentEncoding.GetBytes (str); 390 response.Close (error, false); 391 } catch { 392 // response was already closed 393 } 394 } 395 SendError()396 public void SendError () 397 { 398 SendError (context.ErrorMessage, context.ErrorStatus); 399 } 400 Unbind()401 void Unbind () 402 { 403 if (context_bound) { 404 epl.UnbindContext (context); 405 context_bound = false; 406 } 407 } 408 Close()409 public void Close () 410 { 411 Close (false); 412 } 413 CloseSocket()414 void CloseSocket () 415 { 416 if (sock == null) 417 return; 418 419 try { 420 sock.Close (); 421 } catch { 422 } finally { 423 sock = null; 424 } 425 RemoveConnection (); 426 } 427 Close(bool force_close)428 internal void Close (bool force_close) 429 { 430 if (sock != null) { 431 Stream st = GetResponseStream (); 432 if (st != null) 433 st.Close (); 434 435 o_stream = null; 436 } 437 438 if (sock != null) { 439 force_close |= !context.Request.KeepAlive; 440 if (!force_close) 441 force_close = (context.Response.Headers ["connection"] == "close"); 442 /* 443 if (!force_close) { 444 // bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 || 445 // status_code == 413 || status_code == 414 || status_code == 500 || 446 // status_code == 503); 447 448 force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10); 449 } 450 */ 451 452 if (!force_close && context.Request.FlushInput ()) { 453 if (chunked && context.Response.ForceCloseChunked == false) { 454 // Don't close. Keep working. 455 reuses++; 456 Unbind (); 457 Init (); 458 BeginReadRequest (); 459 return; 460 } 461 462 reuses++; 463 Unbind (); 464 Init (); 465 BeginReadRequest (); 466 return; 467 } 468 469 Socket s = sock; 470 sock = null; 471 try { 472 if (s != null) 473 s.Shutdown (SocketShutdown.Both); 474 } catch { 475 } finally { 476 if (s != null) 477 s.Close (); 478 } 479 Unbind (); 480 RemoveConnection (); 481 return; 482 } 483 } 484 } 485 } 486 487