1 // 2 // System.Net.HttpListener 3 // 4 // Authors: 5 // Gonzalo Paniagua Javier (gonzalo@novell.com) 6 // Marek Safar (marek.safar@gmail.com) 7 // 8 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) 9 // Copyright 2011 Xamarin Inc. 10 // 11 // Permission is hereby granted, free of charge, to any person obtaining 12 // a copy of this software and associated documentation files (the 13 // "Software"), to deal in the Software without restriction, including 14 // without limitation the rights to use, copy, modify, merge, publish, 15 // distribute, sublicense, and/or sell copies of the Software, and to 16 // permit persons to whom the Software is furnished to do so, subject to 17 // the following conditions: 18 // 19 // The above copyright notice and this permission notice shall be 20 // included in all copies or substantial portions of the Software. 21 // 22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 // 30 31 using System.IO; 32 using System.Collections; 33 using System.Threading; 34 using System.Threading.Tasks; 35 using System.Net.Security; 36 using System.Security.Authentication.ExtendedProtection; 37 using System.Security.Cryptography; 38 using System.Security.Cryptography.X509Certificates; 39 40 //TODO: logging 41 namespace System.Net { 42 public sealed partial class HttpListener : IDisposable { 43 AuthenticationSchemes auth_schemes; 44 HttpListenerPrefixCollection prefixes; 45 AuthenticationSchemeSelector auth_selector; 46 string realm; 47 bool ignore_write_exceptions; 48 bool unsafe_ntlm_auth; 49 bool listening; 50 bool disposed; 51 52 readonly object _internalLock; // don't rename to match CoreFx 53 54 Hashtable registry; // Dictionary<HttpListenerContext,HttpListenerContext> 55 ArrayList ctx_queue; // List<HttpListenerContext> ctx_queue; 56 ArrayList wait_queue; // List<ListenerAsyncResult> wait_queue; 57 Hashtable connections; 58 59 ServiceNameStore defaultServiceNames; 60 ExtendedProtectionPolicy extendedProtectionPolicy; 61 ExtendedProtectionSelector extendedProtectionSelectorDelegate; 62 ExtendedProtectionSelector(HttpListenerRequest request)63 public delegate ExtendedProtectionPolicy ExtendedProtectionSelector (HttpListenerRequest request); 64 HttpListener()65 public HttpListener () 66 { 67 _internalLock = new object (); 68 prefixes = new HttpListenerPrefixCollection (this); 69 registry = new Hashtable (); 70 connections = Hashtable.Synchronized (new Hashtable ()); 71 ctx_queue = new ArrayList (); 72 wait_queue = new ArrayList (); 73 auth_schemes = AuthenticationSchemes.Anonymous; 74 defaultServiceNames = new ServiceNameStore (); 75 extendedProtectionPolicy = new ExtendedProtectionPolicy (PolicyEnforcement.Never); 76 } 77 78 79 // TODO: Digest, NTLM and Negotiate require ControlPrincipal 80 public AuthenticationSchemes AuthenticationSchemes { 81 get { return auth_schemes; } 82 set { 83 CheckDisposed (); 84 auth_schemes = value; 85 } 86 } 87 88 public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate { 89 get { return auth_selector; } 90 set { 91 CheckDisposed (); 92 auth_selector = value; 93 } 94 } 95 96 public ExtendedProtectionSelector ExtendedProtectionSelectorDelegate 97 { 98 get { return extendedProtectionSelectorDelegate; } 99 set { 100 CheckDisposed(); 101 if (value == null) 102 throw new ArgumentNullException (); 103 104 if (!AuthenticationManager.OSSupportsExtendedProtection) 105 throw new PlatformNotSupportedException (SR.GetString (SR.security_ExtendedProtection_NoOSSupport)); 106 107 extendedProtectionSelectorDelegate = value; 108 } 109 } 110 111 public bool IgnoreWriteExceptions { 112 get { return ignore_write_exceptions; } 113 set { 114 CheckDisposed (); 115 ignore_write_exceptions = value; 116 } 117 } 118 119 public bool IsListening { 120 get { return listening; } 121 } 122 123 public static bool IsSupported { 124 get { return true; } 125 } 126 127 public HttpListenerPrefixCollection Prefixes { 128 get { 129 CheckDisposed (); 130 return prefixes; 131 } 132 } 133 134 [MonoTODO] 135 public HttpListenerTimeoutManager TimeoutManager { 136 get { 137 throw new NotImplementedException (); 138 } 139 } 140 141 [MonoTODO ("not used anywhere in the implementation")] 142 public ExtendedProtectionPolicy ExtendedProtectionPolicy 143 { 144 get { 145 return extendedProtectionPolicy; 146 } 147 set { 148 CheckDisposed (); 149 150 if (value == null) 151 throw new ArgumentNullException ("value"); 152 153 if (!AuthenticationManager.OSSupportsExtendedProtection && value.PolicyEnforcement == PolicyEnforcement.Always) 154 throw new PlatformNotSupportedException (SR.GetString(SR.security_ExtendedProtection_NoOSSupport)); 155 156 if (value.CustomChannelBinding != null) 157 throw new ArgumentException (SR.GetString (SR.net_listener_cannot_set_custom_cbt), "CustomChannelBinding"); 158 159 extendedProtectionPolicy = value; 160 } 161 } 162 163 public ServiceNameCollection DefaultServiceNames 164 { 165 get { 166 return defaultServiceNames.ServiceNames; 167 } 168 } 169 170 // TODO: use this 171 public string Realm { 172 get { return realm; } 173 set { 174 CheckDisposed (); 175 realm = value; 176 } 177 } 178 179 [MonoTODO ("Support for NTLM needs some loving.")] 180 public bool UnsafeConnectionNtlmAuthentication { 181 get { return unsafe_ntlm_auth; } 182 set { 183 CheckDisposed (); 184 unsafe_ntlm_auth = value; 185 } 186 } 187 Abort()188 public void Abort () 189 { 190 if (disposed) 191 return; 192 193 if (!listening) { 194 return; 195 } 196 197 Close (true); 198 } 199 Close()200 public void Close () 201 { 202 if (disposed) 203 return; 204 205 if (!listening) { 206 disposed = true; 207 return; 208 } 209 210 Close (true); 211 disposed = true; 212 } 213 Close(bool force)214 void Close (bool force) 215 { 216 CheckDisposed (); 217 EndPointManager.RemoveListener (this); 218 Cleanup (force); 219 } 220 Cleanup(bool close_existing)221 void Cleanup (bool close_existing) 222 { 223 lock (_internalLock) { 224 if (close_existing) { 225 // Need to copy this since closing will call UnregisterContext 226 ICollection keys = registry.Keys; 227 var all = new HttpListenerContext [keys.Count]; 228 keys.CopyTo (all, 0); 229 registry.Clear (); 230 for (int i = all.Length - 1; i >= 0; i--) 231 all [i].Connection.Close (true); 232 } 233 234 lock (connections.SyncRoot) { 235 ICollection keys = connections.Keys; 236 var conns = new HttpConnection [keys.Count]; 237 keys.CopyTo (conns, 0); 238 connections.Clear (); 239 for (int i = conns.Length - 1; i >= 0; i--) 240 conns [i].Close (true); 241 } 242 lock (ctx_queue) { 243 var ctxs = (HttpListenerContext []) ctx_queue.ToArray (typeof (HttpListenerContext)); 244 ctx_queue.Clear (); 245 for (int i = ctxs.Length - 1; i >= 0; i--) 246 ctxs [i].Connection.Close (true); 247 } 248 249 lock (wait_queue) { 250 Exception exc = new ObjectDisposedException ("listener"); 251 foreach (ListenerAsyncResult ares in wait_queue) { 252 ares.Complete (exc); 253 } 254 wait_queue.Clear (); 255 } 256 } 257 } 258 BeginGetContext(AsyncCallback callback, Object state)259 public IAsyncResult BeginGetContext (AsyncCallback callback, Object state) 260 { 261 CheckDisposed (); 262 if (!listening) 263 throw new InvalidOperationException ("Please, call Start before using this method."); 264 265 ListenerAsyncResult ares = new ListenerAsyncResult (callback, state); 266 267 // lock wait_queue early to avoid race conditions 268 lock (wait_queue) { 269 lock (ctx_queue) { 270 HttpListenerContext ctx = GetContextFromQueue (); 271 if (ctx != null) { 272 ares.Complete (ctx, true); 273 return ares; 274 } 275 } 276 277 wait_queue.Add (ares); 278 } 279 280 return ares; 281 } 282 EndGetContext(IAsyncResult asyncResult)283 public HttpListenerContext EndGetContext (IAsyncResult asyncResult) 284 { 285 CheckDisposed (); 286 if (asyncResult == null) 287 throw new ArgumentNullException ("asyncResult"); 288 289 ListenerAsyncResult ares = asyncResult as ListenerAsyncResult; 290 if (ares == null) 291 throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult"); 292 if (ares.EndCalled) 293 throw new ArgumentException ("Cannot reuse this IAsyncResult"); 294 ares.EndCalled = true; 295 296 if (!ares.IsCompleted) 297 ares.AsyncWaitHandle.WaitOne (); 298 299 lock (wait_queue) { 300 int idx = wait_queue.IndexOf (ares); 301 if (idx >= 0) 302 wait_queue.RemoveAt (idx); 303 } 304 305 HttpListenerContext context = ares.GetContext (); 306 context.ParseAuthentication (SelectAuthenticationScheme (context)); 307 return context; // This will throw on error. 308 } 309 SelectAuthenticationScheme(HttpListenerContext context)310 internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context) 311 { 312 if (AuthenticationSchemeSelectorDelegate != null) 313 return AuthenticationSchemeSelectorDelegate (context.Request); 314 else 315 return auth_schemes; 316 } 317 GetContext()318 public HttpListenerContext GetContext () 319 { 320 // The prefixes are not checked when using the async interface!? 321 if (prefixes.Count == 0) 322 throw new InvalidOperationException ("Please, call AddPrefix before using this method."); 323 324 ListenerAsyncResult ares = (ListenerAsyncResult) BeginGetContext (null, null); 325 ares.InGet = true; 326 return EndGetContext (ares); 327 } 328 Start()329 public void Start () 330 { 331 CheckDisposed (); 332 if (listening) 333 return; 334 335 EndPointManager.AddListener (this); 336 listening = true; 337 } 338 Stop()339 public void Stop () 340 { 341 CheckDisposed (); 342 listening = false; 343 Close (false); 344 } 345 IDisposable.Dispose()346 void IDisposable.Dispose () 347 { 348 if (disposed) 349 return; 350 351 Close (true); //TODO: Should we force here or not? 352 disposed = true; 353 } 354 GetContextAsync()355 public Task<HttpListenerContext> GetContextAsync () 356 { 357 return Task<HttpListenerContext>.Factory.FromAsync (BeginGetContext, EndGetContext, null); 358 } 359 CheckDisposed()360 internal void CheckDisposed () 361 { 362 if (disposed) 363 throw new ObjectDisposedException (GetType ().ToString ()); 364 } 365 366 // Must be called with a lock on ctx_queue GetContextFromQueue()367 HttpListenerContext GetContextFromQueue () 368 { 369 if (ctx_queue.Count == 0) 370 return null; 371 372 HttpListenerContext context = (HttpListenerContext) ctx_queue [0]; 373 ctx_queue.RemoveAt (0); 374 return context; 375 } 376 RegisterContext(HttpListenerContext context)377 internal void RegisterContext (HttpListenerContext context) 378 { 379 lock (_internalLock) 380 registry [context] = context; 381 382 ListenerAsyncResult ares = null; 383 lock (wait_queue) { 384 if (wait_queue.Count == 0) { 385 lock (ctx_queue) 386 ctx_queue.Add (context); 387 } else { 388 ares = (ListenerAsyncResult) wait_queue [0]; 389 wait_queue.RemoveAt (0); 390 } 391 } 392 if (ares != null) 393 ares.Complete (context); 394 } 395 UnregisterContext(HttpListenerContext context)396 internal void UnregisterContext (HttpListenerContext context) 397 { 398 lock (_internalLock) 399 registry.Remove (context); 400 lock (ctx_queue) { 401 int idx = ctx_queue.IndexOf (context); 402 if (idx >= 0) 403 ctx_queue.RemoveAt (idx); 404 } 405 } 406 AddConnection(HttpConnection cnc)407 internal void AddConnection (HttpConnection cnc) 408 { 409 connections [cnc] = cnc; 410 } 411 RemoveConnection(HttpConnection cnc)412 internal void RemoveConnection (HttpConnection cnc) 413 { 414 connections.Remove (cnc); 415 } 416 } 417 } 418