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