| 1 | // Licensed to the .NET Foundation under one or more agreements.
|
|---|
| 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
|---|
| 3 |
|
|---|
| 4 | //
|
|---|
| 5 | // System.Net.HttpEndPointListener
|
|---|
| 6 | //
|
|---|
| 7 | // Author:
|
|---|
| 8 | // Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
|
|---|
| 9 | //
|
|---|
| 10 | // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
|
|---|
| 11 | // Copyright (c) 2012 Xamarin, Inc. (http://xamarin.com)
|
|---|
| 12 | //
|
|---|
| 13 | // Permission is hereby granted, free of charge, to any person obtaining
|
|---|
| 14 | // a copy of this software and associated documentation files (the
|
|---|
| 15 | // "Software"), to deal in the Software without restriction, including
|
|---|
| 16 | // without limitation the rights to use, copy, modify, merge, publish,
|
|---|
| 17 | // distribute, sublicense, and/or sell copies of the Software, and to
|
|---|
| 18 | // permit persons to whom the Software is furnished to do so, subject to
|
|---|
| 19 | // the following conditions:
|
|---|
| 20 | //
|
|---|
| 21 | // The above copyright notice and this permission notice shall be
|
|---|
| 22 | // included in all copies or substantial portions of the Software.
|
|---|
| 23 | //
|
|---|
| 24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|---|
| 25 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|---|
| 26 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|---|
| 27 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|---|
| 28 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|---|
| 29 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|---|
| 30 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|---|
| 31 | //
|
|---|
| 32 |
|
|---|
| 33 | // ReSharper disable ConditionIsAlwaysTrueOrFalse
|
|---|
| 34 | // ReSharper disable UnusedParameter.Local
|
|---|
| 35 |
|
|---|
| 36 | using System;
|
|---|
| 37 | using System.Collections.Generic;
|
|---|
| 38 | using System.Net;
|
|---|
| 39 | using System.Net.Sockets;
|
|---|
| 40 | using System.Security.Cryptography.X509Certificates;
|
|---|
| 41 | using System.Threading;
|
|---|
| 42 |
|
|---|
| 43 | namespace SpaceWizards.HttpListener
|
|---|
| 44 | {
|
|---|
| 45 | internal sealed class HttpEndPointListener
|
|---|
| 46 | {
|
|---|
| 47 | private readonly HttpListener _listener;
|
|---|
| 48 | private readonly IPEndPoint _endpoint;
|
|---|
| 49 | private readonly Socket _socket;
|
|---|
| 50 | private readonly Dictionary<HttpConnection, HttpConnection> _unregisteredConnections;
|
|---|
| 51 | private Dictionary<ListenerPrefix, HttpListener> _prefixes;
|
|---|
| 52 | private List<ListenerPrefix>? _unhandledPrefixes; // host = '*'
|
|---|
| 53 | private List<ListenerPrefix>? _allPrefixes; // host = '+'
|
|---|
| 54 | private X509Certificate? _cert;
|
|---|
| 55 | private bool _secure;
|
|---|
| 56 |
|
|---|
| 57 | public HttpEndPointListener(HttpListener listener, IPAddress addr, int port, bool secure)
|
|---|
| 58 | {
|
|---|
| 59 | _listener = listener;
|
|---|
| 60 |
|
|---|
| 61 | if (secure)
|
|---|
| 62 | {
|
|---|
| 63 | _secure = secure;
|
|---|
| 64 | _cert = _listener.LoadCertificateAndKey (addr, port);
|
|---|
| 65 | }
|
|---|
| 66 |
|
|---|
| 67 | _endpoint = new IPEndPoint(addr, port);
|
|---|
| 68 | _socket = new Socket(addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
|---|
| 69 |
|
|---|
| 70 | if (addr.AddressFamily == AddressFamily.InterNetworkV6) {
|
|---|
| 71 | // Make sure that all platforms behave the same for IPv6 sockets (at least Linux by
|
|---|
| 72 | // default has them in dual mode, so it would conflict with the explicit IPv4 socket)
|
|---|
| 73 | _socket.DualMode = false;
|
|---|
| 74 | }
|
|---|
| 75 |
|
|---|
| 76 | _socket.Bind(_endpoint);
|
|---|
| 77 | _socket.Listen(500);
|
|---|
| 78 |
|
|---|
| 79 | SocketAsyncEventArgs args = new SocketAsyncEventArgs();
|
|---|
| 80 | args.UserToken = this;
|
|---|
| 81 | args.Completed += OnAccept;
|
|---|
| 82 | Accept(args);
|
|---|
| 83 |
|
|---|
| 84 | _prefixes = new Dictionary<ListenerPrefix, HttpListener>();
|
|---|
| 85 | _unregisteredConnections = new Dictionary<HttpConnection, HttpConnection>();
|
|---|
| 86 | }
|
|---|
| 87 |
|
|---|
| 88 | internal HttpListener Listener
|
|---|
| 89 | {
|
|---|
| 90 | get { return _listener; }
|
|---|
| 91 | }
|
|---|
| 92 |
|
|---|
| 93 | private void Accept(SocketAsyncEventArgs e)
|
|---|
| 94 | {
|
|---|
| 95 | e.AcceptSocket = null;
|
|---|
| 96 | bool asyn;
|
|---|
| 97 | try
|
|---|
| 98 | {
|
|---|
| 99 | asyn = _socket.AcceptAsync(e);
|
|---|
| 100 | }
|
|---|
| 101 | catch (ObjectDisposedException)
|
|---|
| 102 | {
|
|---|
| 103 | // Once the listener starts running, it kicks off an async accept,
|
|---|
| 104 | // and each subsequent accept initiates the next async accept. At
|
|---|
| 105 | // point if the listener is torn down, the socket will be disposed
|
|---|
| 106 | // and the AcceptAsync on the socket can fail with an ODE. Far from
|
|---|
| 107 | // ideal, but for now just eat such exceptions.
|
|---|
| 108 | return;
|
|---|
| 109 | }
|
|---|
| 110 | if (!asyn)
|
|---|
| 111 | {
|
|---|
| 112 | ProcessAccept(e);
|
|---|
| 113 | }
|
|---|
| 114 | }
|
|---|
| 115 |
|
|---|
| 116 | private static void ProcessAccept(SocketAsyncEventArgs args)
|
|---|
| 117 | {
|
|---|
| 118 | HttpEndPointListener epl = (HttpEndPointListener)args.UserToken!;
|
|---|
| 119 |
|
|---|
| 120 | Socket? accepted = args.SocketError == SocketError.Success ? args.AcceptSocket : null;
|
|---|
| 121 | epl.Accept(args);
|
|---|
| 122 |
|
|---|
| 123 | if (accepted == null)
|
|---|
| 124 | return;
|
|---|
| 125 |
|
|---|
| 126 | if (epl._secure && epl._cert == null)
|
|---|
| 127 | {
|
|---|
| 128 | accepted.Close();
|
|---|
| 129 | return;
|
|---|
| 130 | }
|
|---|
| 131 |
|
|---|
| 132 | HttpConnection conn;
|
|---|
| 133 | try
|
|---|
| 134 | {
|
|---|
| 135 | conn = new HttpConnection(accepted, epl, epl._secure, epl._cert!);
|
|---|
| 136 | }
|
|---|
| 137 | catch
|
|---|
| 138 | {
|
|---|
| 139 | accepted.Close();
|
|---|
| 140 | return;
|
|---|
| 141 | }
|
|---|
| 142 |
|
|---|
| 143 | lock (epl._unregisteredConnections)
|
|---|
| 144 | {
|
|---|
| 145 | epl._unregisteredConnections[conn] = conn;
|
|---|
| 146 | }
|
|---|
| 147 | conn.BeginReadRequest();
|
|---|
| 148 | }
|
|---|
| 149 |
|
|---|
| 150 | private static void OnAccept(object? sender, SocketAsyncEventArgs e)
|
|---|
| 151 | {
|
|---|
| 152 | ProcessAccept(e);
|
|---|
| 153 | }
|
|---|
| 154 |
|
|---|
| 155 | internal void RemoveConnection(HttpConnection conn)
|
|---|
| 156 | {
|
|---|
| 157 | lock (_unregisteredConnections)
|
|---|
| 158 | {
|
|---|
| 159 | _unregisteredConnections.Remove(conn);
|
|---|
| 160 | }
|
|---|
| 161 | }
|
|---|
| 162 |
|
|---|
| 163 | public bool BindContext(HttpListenerContext context)
|
|---|
| 164 | {
|
|---|
| 165 | HttpListenerRequest req = context.Request;
|
|---|
| 166 | ListenerPrefix? prefix;
|
|---|
| 167 | HttpListener? listener = SearchListener(req.Url, out prefix);
|
|---|
| 168 | if (listener == null)
|
|---|
| 169 | return false;
|
|---|
| 170 |
|
|---|
| 171 | context._listener = listener;
|
|---|
| 172 | context.Connection.Prefix = prefix;
|
|---|
| 173 | return true;
|
|---|
| 174 | }
|
|---|
| 175 |
|
|---|
| 176 | public void UnbindContext(HttpListenerContext context)
|
|---|
| 177 | {
|
|---|
| 178 | if (context == null || context.Request == null)
|
|---|
| 179 | return;
|
|---|
| 180 |
|
|---|
| 181 | context._listener!.UnregisterContext(context);
|
|---|
| 182 | }
|
|---|
| 183 |
|
|---|
| 184 | private HttpListener? SearchListener(Uri? uri, out ListenerPrefix? prefix)
|
|---|
| 185 | {
|
|---|
| 186 | prefix = null;
|
|---|
| 187 | if (uri == null)
|
|---|
| 188 | return null;
|
|---|
| 189 |
|
|---|
| 190 | string host = uri.Host;
|
|---|
| 191 | int port = uri.Port;
|
|---|
| 192 | string path = WebUtility.UrlDecode(uri.AbsolutePath);
|
|---|
| 193 | string pathSlash = path[path.Length - 1] == '/' ? path : path + "/";
|
|---|
| 194 |
|
|---|
| 195 | HttpListener? bestMatch = null;
|
|---|
| 196 | int bestLength = -1;
|
|---|
| 197 |
|
|---|
| 198 | if (host != null && host != "")
|
|---|
| 199 | {
|
|---|
| 200 | Dictionary<ListenerPrefix, HttpListener> localPrefixes = _prefixes;
|
|---|
| 201 | foreach (ListenerPrefix p in localPrefixes.Keys)
|
|---|
| 202 | {
|
|---|
| 203 | string ppath = p.Path!;
|
|---|
| 204 | if (ppath.Length < bestLength)
|
|---|
| 205 | continue;
|
|---|
| 206 |
|
|---|
| 207 | if (p.Host != host || p.Port != port)
|
|---|
| 208 | continue;
|
|---|
| 209 |
|
|---|
| 210 | if (path.StartsWith(ppath, StringComparison.Ordinal) || pathSlash.StartsWith(ppath, StringComparison.Ordinal))
|
|---|
| 211 | {
|
|---|
| 212 | bestLength = ppath.Length;
|
|---|
| 213 | bestMatch = localPrefixes[p];
|
|---|
| 214 | prefix = p;
|
|---|
| 215 | }
|
|---|
| 216 | }
|
|---|
| 217 | if (bestLength != -1)
|
|---|
| 218 | return bestMatch;
|
|---|
| 219 | }
|
|---|
| 220 |
|
|---|
| 221 | List<ListenerPrefix>? list = _unhandledPrefixes;
|
|---|
| 222 | bestMatch = MatchFromList(host, path, list, out prefix);
|
|---|
| 223 |
|
|---|
| 224 | if (path != pathSlash && bestMatch == null)
|
|---|
| 225 | bestMatch = MatchFromList(host, pathSlash, list, out prefix);
|
|---|
| 226 |
|
|---|
| 227 | if (bestMatch != null)
|
|---|
| 228 | return bestMatch;
|
|---|
| 229 |
|
|---|
| 230 | list = _allPrefixes;
|
|---|
| 231 | bestMatch = MatchFromList(host, path, list, out prefix);
|
|---|
| 232 |
|
|---|
| 233 | if (path != pathSlash && bestMatch == null)
|
|---|
| 234 | bestMatch = MatchFromList(host, pathSlash, list, out prefix);
|
|---|
| 235 |
|
|---|
| 236 | if (bestMatch != null)
|
|---|
| 237 | return bestMatch;
|
|---|
| 238 |
|
|---|
| 239 | return null;
|
|---|
| 240 | }
|
|---|
| 241 |
|
|---|
| 242 | private HttpListener? MatchFromList(string? host, string path, List<ListenerPrefix>? list, out ListenerPrefix? prefix)
|
|---|
| 243 | {
|
|---|
| 244 | prefix = null;
|
|---|
| 245 | if (list == null)
|
|---|
| 246 | return null;
|
|---|
| 247 |
|
|---|
| 248 | HttpListener? bestMatch = null;
|
|---|
| 249 | int bestLength = -1;
|
|---|
| 250 |
|
|---|
| 251 | foreach (ListenerPrefix p in list)
|
|---|
| 252 | {
|
|---|
| 253 | string ppath = p.Path!;
|
|---|
| 254 | if (ppath.Length < bestLength)
|
|---|
| 255 | continue;
|
|---|
| 256 |
|
|---|
| 257 | if (path.StartsWith(ppath, StringComparison.Ordinal))
|
|---|
| 258 | {
|
|---|
| 259 | bestLength = ppath.Length;
|
|---|
| 260 | bestMatch = p._listener;
|
|---|
| 261 | prefix = p;
|
|---|
| 262 | }
|
|---|
| 263 | }
|
|---|
| 264 |
|
|---|
| 265 | return bestMatch;
|
|---|
| 266 | }
|
|---|
| 267 |
|
|---|
| 268 | private void AddSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)
|
|---|
| 269 | {
|
|---|
| 270 | if (list == null)
|
|---|
| 271 | return;
|
|---|
| 272 |
|
|---|
| 273 | foreach (ListenerPrefix p in list)
|
|---|
| 274 | {
|
|---|
| 275 | if (p.Path == prefix.Path)
|
|---|
| 276 | throw new HttpListenerException((int)HttpStatusCode.BadRequest, SR.Format(SR.net_listener_already, prefix));
|
|---|
| 277 | }
|
|---|
| 278 | list.Add(prefix);
|
|---|
| 279 | }
|
|---|
| 280 |
|
|---|
| 281 | private bool RemoveSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)
|
|---|
| 282 | {
|
|---|
| 283 | if (list == null)
|
|---|
| 284 | return false;
|
|---|
| 285 |
|
|---|
| 286 | int c = list.Count;
|
|---|
| 287 | for (int i = 0; i < c; i++)
|
|---|
| 288 | {
|
|---|
| 289 | ListenerPrefix p = list[i];
|
|---|
| 290 | if (p.Path == prefix.Path)
|
|---|
| 291 | {
|
|---|
| 292 | list.RemoveAt(i);
|
|---|
| 293 | return true;
|
|---|
| 294 | }
|
|---|
| 295 | }
|
|---|
| 296 | return false;
|
|---|
| 297 | }
|
|---|
| 298 |
|
|---|
| 299 | private void CheckIfRemove()
|
|---|
| 300 | {
|
|---|
| 301 | if (_prefixes.Count > 0)
|
|---|
| 302 | return;
|
|---|
| 303 |
|
|---|
| 304 | List<ListenerPrefix>? list = _unhandledPrefixes;
|
|---|
| 305 | if (list != null && list.Count > 0)
|
|---|
| 306 | return;
|
|---|
| 307 |
|
|---|
| 308 | list = _allPrefixes;
|
|---|
| 309 | if (list != null && list.Count > 0)
|
|---|
| 310 | return;
|
|---|
| 311 |
|
|---|
| 312 | HttpEndPointManager.RemoveEndPoint(this, _endpoint);
|
|---|
| 313 | }
|
|---|
| 314 |
|
|---|
| 315 | public void Close()
|
|---|
| 316 | {
|
|---|
| 317 | _socket.Close();
|
|---|
| 318 | lock (_unregisteredConnections)
|
|---|
| 319 | {
|
|---|
| 320 | // Clone the list because RemoveConnection can be called from Close
|
|---|
| 321 | var connections = new List<HttpConnection>(_unregisteredConnections.Keys);
|
|---|
| 322 |
|
|---|
| 323 | foreach (HttpConnection c in connections)
|
|---|
| 324 | c.Close(true);
|
|---|
| 325 | _unregisteredConnections.Clear();
|
|---|
| 326 | }
|
|---|
| 327 | }
|
|---|
| 328 |
|
|---|
| 329 | public void AddPrefix(ListenerPrefix prefix, HttpListener listener)
|
|---|
| 330 | {
|
|---|
| 331 | List<ListenerPrefix>? current;
|
|---|
| 332 | List<ListenerPrefix> future;
|
|---|
| 333 | if (prefix.Host == "*")
|
|---|
| 334 | {
|
|---|
| 335 | do
|
|---|
| 336 | {
|
|---|
| 337 | current = _unhandledPrefixes;
|
|---|
| 338 | future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
|---|
| 339 | prefix._listener = listener;
|
|---|
| 340 | AddSpecial(future, prefix);
|
|---|
| 341 | } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);
|
|---|
| 342 | return;
|
|---|
| 343 | }
|
|---|
| 344 |
|
|---|
| 345 | if (prefix.Host == "+")
|
|---|
| 346 | {
|
|---|
| 347 | do
|
|---|
| 348 | {
|
|---|
| 349 | current = _allPrefixes;
|
|---|
| 350 | future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
|---|
| 351 | prefix._listener = listener;
|
|---|
| 352 | AddSpecial(future, prefix);
|
|---|
| 353 | } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);
|
|---|
| 354 | return;
|
|---|
| 355 | }
|
|---|
| 356 |
|
|---|
| 357 | Dictionary<ListenerPrefix, HttpListener> prefs, p2;
|
|---|
| 358 | do
|
|---|
| 359 | {
|
|---|
| 360 | prefs = _prefixes;
|
|---|
| 361 | if (prefs.ContainsKey(prefix))
|
|---|
| 362 | {
|
|---|
| 363 | throw new HttpListenerException((int)HttpStatusCode.BadRequest, SR.Format(SR.net_listener_already, prefix));
|
|---|
| 364 | }
|
|---|
| 365 | p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
|
|---|
| 366 | p2[prefix] = listener;
|
|---|
| 367 | } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
|
|---|
| 368 | }
|
|---|
| 369 |
|
|---|
| 370 | public void RemovePrefix(ListenerPrefix prefix, HttpListener listener)
|
|---|
| 371 | {
|
|---|
| 372 | List<ListenerPrefix>? current;
|
|---|
| 373 | List<ListenerPrefix> future;
|
|---|
| 374 | if (prefix.Host == "*")
|
|---|
| 375 | {
|
|---|
| 376 | do
|
|---|
| 377 | {
|
|---|
| 378 | current = _unhandledPrefixes;
|
|---|
| 379 | future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
|---|
| 380 | if (!RemoveSpecial(future, prefix))
|
|---|
| 381 | break; // Prefix not found
|
|---|
| 382 | } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);
|
|---|
| 383 |
|
|---|
| 384 | CheckIfRemove();
|
|---|
| 385 | return;
|
|---|
| 386 | }
|
|---|
| 387 |
|
|---|
| 388 | if (prefix.Host == "+")
|
|---|
| 389 | {
|
|---|
| 390 | do
|
|---|
| 391 | {
|
|---|
| 392 | current = _allPrefixes;
|
|---|
| 393 | future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
|---|
| 394 | if (!RemoveSpecial(future, prefix))
|
|---|
| 395 | break; // Prefix not found
|
|---|
| 396 | } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);
|
|---|
| 397 | CheckIfRemove();
|
|---|
| 398 | return;
|
|---|
| 399 | }
|
|---|
| 400 |
|
|---|
| 401 | Dictionary<ListenerPrefix, HttpListener> prefs, p2;
|
|---|
| 402 | do
|
|---|
| 403 | {
|
|---|
| 404 | prefs = _prefixes;
|
|---|
| 405 | if (!prefs.ContainsKey(prefix))
|
|---|
| 406 | break;
|
|---|
| 407 |
|
|---|
| 408 | p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
|
|---|
| 409 | p2.Remove(prefix);
|
|---|
| 410 | } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
|
|---|
| 411 | CheckIfRemove();
|
|---|
| 412 | }
|
|---|
| 413 | }
|
|---|
| 414 | }
|
|---|