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