1 //
2 // System.Net.EndPointListener
3 //
4 // Author:
5 //	Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
6 //
7 // Copyright (c) 2005 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.Collections;
33 using System.Collections.Generic;
34 using System.Security.Cryptography;
35 using System.Security.Cryptography.X509Certificates;
36 using System.Threading;
37 
38 namespace System.Net {
39 	sealed class EndPointListener
40 	{
41 		HttpListener listener;
42 		IPEndPoint endpoint;
43 		Socket sock;
44 		Hashtable prefixes;  // Dictionary <ListenerPrefix, HttpListener>
45 		ArrayList unhandled; // List<ListenerPrefix> unhandled; host = '*'
46 		ArrayList all;       // List<ListenerPrefix> all;  host = '+'
47 		X509Certificate cert;
48 		bool secure;
49 		Dictionary<HttpConnection, HttpConnection> unregistered;
50 
EndPointListener(HttpListener listener, IPAddress addr, int port, bool secure)51 		public EndPointListener (HttpListener listener, IPAddress addr, int port, bool secure)
52 		{
53 			this.listener = listener;
54 
55 			if (secure) {
56 				this.secure = secure;
57 				cert = listener.LoadCertificateAndKey (addr, port);
58 			}
59 
60 			endpoint = new IPEndPoint (addr, port);
61 			sock = new Socket (addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
62 			sock.Bind (endpoint);
63 			sock.Listen (500);
64 			SocketAsyncEventArgs args = new SocketAsyncEventArgs ();
65 			args.UserToken = this;
66 			args.Completed += OnAccept;
67 			Socket dummy = null;
68 			Accept (sock, args, ref dummy);
69 			prefixes = new Hashtable ();
70 			unregistered = new Dictionary<HttpConnection, HttpConnection> ();
71 		}
72 
73 		internal HttpListener Listener {
74 			get { return listener; }
75 		}
76 
Accept(Socket socket, SocketAsyncEventArgs e, ref Socket accepted)77 		static void Accept (Socket socket, SocketAsyncEventArgs e, ref Socket accepted) {
78 			e.AcceptSocket = null;
79 			bool asyn;
80 			try {
81 				asyn = socket.AcceptAsync(e);
82 			} catch {
83 				if (accepted != null) {
84 					try {
85 						accepted.Close ();
86 					} catch {
87 					}
88 					accepted = null;
89 				}
90 				return;
91 			}
92 			if (!asyn) {
93 				ProcessAccept(e);
94 			}
95 		}
96 
97 
ProcessAccept(SocketAsyncEventArgs args)98 		static void ProcessAccept (SocketAsyncEventArgs args)
99 		{
100 			Socket accepted = null;
101 			if (args.SocketError == SocketError.Success)
102 				accepted = args.AcceptSocket;
103 
104 			EndPointListener epl = (EndPointListener) args.UserToken;
105 
106 
107 			Accept (epl.sock, args, ref accepted);
108 			if (accepted == null)
109 				return;
110 
111 			if (epl.secure && epl.cert == null) {
112 				accepted.Close ();
113 				return;
114 			}
115 			HttpConnection conn;
116 			try {
117 				conn = new HttpConnection (accepted, epl, epl.secure, epl.cert);
118 			} catch {
119 				accepted.Close ();
120 				return;
121 			}
122 			lock (epl.unregistered) {
123 				epl.unregistered [conn] = conn;
124 			}
125 			conn.BeginReadRequest ();
126 		}
127 
OnAccept(object sender, SocketAsyncEventArgs e)128 		static void OnAccept (object sender, SocketAsyncEventArgs e)
129 		{
130 			ProcessAccept (e);
131 		}
132 
RemoveConnection(HttpConnection conn)133 		internal void RemoveConnection (HttpConnection conn)
134 		{
135 			lock (unregistered) {
136 				unregistered.Remove (conn);
137 			}
138 		}
139 
BindContext(HttpListenerContext context)140 		public bool BindContext (HttpListenerContext context)
141 		{
142 			HttpListenerRequest req = context.Request;
143 			ListenerPrefix prefix;
144 			HttpListener listener = SearchListener (req.Url, out prefix);
145 			if (listener == null)
146 				return false;
147 
148 			context.Listener = listener;
149 			context.Connection.Prefix = prefix;
150 			return true;
151 		}
152 
UnbindContext(HttpListenerContext context)153 		public void UnbindContext (HttpListenerContext context)
154 		{
155 			if (context == null || context.Request == null)
156 				return;
157 
158 			context.Listener.UnregisterContext (context);
159 		}
160 
SearchListener(Uri uri, out ListenerPrefix prefix)161 		HttpListener SearchListener (Uri uri, out ListenerPrefix prefix)
162 		{
163 			prefix = null;
164 			if (uri == null)
165 				return null;
166 
167 			string host = uri.Host;
168 			int port = uri.Port;
169 			string path = WebUtility.UrlDecode (uri.AbsolutePath);
170 			string path_slash = path [path.Length - 1] == '/' ? path : path + "/";
171 
172 			HttpListener best_match = null;
173 			int best_length = -1;
174 
175 			if (host != null && host != "") {
176 				Hashtable p_ro = prefixes;
177 				foreach (ListenerPrefix p in p_ro.Keys) {
178 					string ppath = p.Path;
179 					if (ppath.Length < best_length)
180 						continue;
181 
182 					if (p.Host != host || p.Port != port)
183 						continue;
184 
185 					if (path.StartsWith (ppath) || path_slash.StartsWith (ppath)) {
186 						best_length = ppath.Length;
187 						best_match = (HttpListener) p_ro [p];
188 						prefix = p;
189 					}
190 				}
191 				if (best_length != -1)
192 					return best_match;
193 			}
194 
195 			ArrayList list = unhandled;
196 			best_match = MatchFromList (host, path, list, out prefix);
197 			if (path != path_slash && best_match == null)
198 				best_match = MatchFromList (host, path_slash, list, out prefix);
199 			if (best_match != null)
200 				return best_match;
201 
202 			list = all;
203 			best_match = MatchFromList (host, path, list, out prefix);
204 			if (path != path_slash && best_match == null)
205 				best_match = MatchFromList (host, path_slash, list, out prefix);
206 			if (best_match != null)
207 				return best_match;
208 
209 			return null;
210 		}
211 
MatchFromList(string host, string path, ArrayList list, out ListenerPrefix prefix)212 		HttpListener MatchFromList (string host, string path, ArrayList list, out ListenerPrefix prefix)
213 		{
214 			prefix = null;
215 			if (list == null)
216 				return null;
217 
218 			HttpListener best_match = null;
219 			int best_length = -1;
220 
221 			foreach (ListenerPrefix p in list) {
222 				string ppath = p.Path;
223 				if (ppath.Length < best_length)
224 					continue;
225 
226 				if (path.StartsWith (ppath)) {
227 					best_length = ppath.Length;
228 					best_match = p.Listener;
229 					prefix = p;
230 				}
231 			}
232 
233 			return best_match;
234 		}
235 
AddSpecial(ArrayList coll, ListenerPrefix prefix)236 		void AddSpecial (ArrayList coll, ListenerPrefix prefix)
237 		{
238 			if (coll == null)
239 				return;
240 
241 			foreach (ListenerPrefix p in coll) {
242 				if (p.Path == prefix.Path) //TODO: code
243 					throw new HttpListenerException (400, "Prefix already in use.");
244 			}
245 			coll.Add (prefix);
246 		}
247 
RemoveSpecial(ArrayList coll, ListenerPrefix prefix)248 		bool RemoveSpecial (ArrayList coll, ListenerPrefix prefix)
249 		{
250 			if (coll == null)
251 				return false;
252 
253 			int c = coll.Count;
254 			for (int i = 0; i < c; i++) {
255 				ListenerPrefix p = (ListenerPrefix) coll [i];
256 				if (p.Path == prefix.Path) {
257 					coll.RemoveAt (i);
258 					return true;
259 				}
260 			}
261 			return false;
262 		}
263 
CheckIfRemove()264 		void CheckIfRemove ()
265 		{
266 			if (prefixes.Count > 0)
267 				return;
268 
269 			ArrayList list = unhandled;
270 			if (list != null && list.Count > 0)
271 				return;
272 
273 			list = all;
274 			if (list != null && list.Count > 0)
275 				return;
276 
277 			EndPointManager.RemoveEndPoint (this, endpoint);
278 		}
279 
Close()280 		public void Close ()
281 		{
282 			sock.Close ();
283 			lock (unregistered) {
284 				//
285 				// Clone the list because RemoveConnection can be called from Close
286 				//
287 				var connections = new List<HttpConnection> (unregistered.Keys);
288 
289 				foreach (HttpConnection c in connections)
290 					c.Close (true);
291 				unregistered.Clear ();
292 			}
293 		}
294 
AddPrefix(ListenerPrefix prefix, HttpListener listener)295 		public void AddPrefix (ListenerPrefix prefix, HttpListener listener)
296 		{
297 			ArrayList current;
298 			ArrayList future;
299 			if (prefix.Host == "*") {
300 				do {
301 					current = unhandled;
302 					future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
303 					prefix.Listener = listener;
304 					AddSpecial (future, prefix);
305 				} while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
306 				return;
307 			}
308 
309 			if (prefix.Host == "+") {
310 				do {
311 					current = all;
312 					future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
313 					prefix.Listener = listener;
314 					AddSpecial (future, prefix);
315 				} while (Interlocked.CompareExchange (ref all, future, current) != current);
316 				return;
317 			}
318 
319 			Hashtable prefs, p2;
320 			do {
321 				prefs = prefixes;
322 				if (prefs.ContainsKey (prefix)) {
323 					HttpListener other = (HttpListener) prefs [prefix];
324 					if (other != listener) // TODO: code.
325 						throw new HttpListenerException (400, "There's another listener for " + prefix);
326 					return;
327 				}
328 				p2 = (Hashtable) prefs.Clone ();
329 				p2 [prefix] = listener;
330 			} while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
331 		}
332 
RemovePrefix(ListenerPrefix prefix, HttpListener listener)333 		public void RemovePrefix (ListenerPrefix prefix, HttpListener listener)
334 		{
335 			ArrayList current;
336 			ArrayList future;
337 			if (prefix.Host == "*") {
338 				do {
339 					current = unhandled;
340 					future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
341 					if (!RemoveSpecial (future, prefix))
342 						break; // Prefix not found
343 				} while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
344 				CheckIfRemove ();
345 				return;
346 			}
347 
348 			if (prefix.Host == "+") {
349 				do {
350 					current = all;
351 					future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
352 					if (!RemoveSpecial (future, prefix))
353 						break; // Prefix not found
354 				} while (Interlocked.CompareExchange (ref all, future, current) != current);
355 				CheckIfRemove ();
356 				return;
357 			}
358 
359 			Hashtable prefs, p2;
360 			do {
361 				prefs = prefixes;
362 				if (!prefs.ContainsKey (prefix))
363 					break;
364 
365 				p2 = (Hashtable) prefs.Clone ();
366 				p2.Remove (prefix);
367 			} while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
368 			CheckIfRemove ();
369 		}
370 	}
371 }
372 
373