using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Security.Cryptography.X509Certificates; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace SocketHttpListener.Net { public sealed class HttpListener : IDisposable { internal ICryptoProvider CryptoProvider { get; private set; } internal ISocketFactory SocketFactory { get; private set; } internal IFileSystem FileSystem { get; private set; } internal IStreamHelper StreamHelper { get; private set; } internal IEnvironmentInfo EnvironmentInfo { get; private set; } public bool EnableDualMode { get; set; } private AuthenticationSchemes auth_schemes; private HttpListenerPrefixCollection prefixes; private AuthenticationSchemeSelector auth_selector; private string realm; private bool unsafe_ntlm_auth; private bool listening; private bool disposed; private Dictionary registry; private Dictionary connections; private ILogger _logger; private X509Certificate _certificate; public Action OnContext { get; set; } public HttpListener( ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IStreamHelper streamHelper, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) { _logger = logger; CryptoProvider = cryptoProvider; SocketFactory = socketFactory; StreamHelper = streamHelper; FileSystem = fileSystem; EnvironmentInfo = environmentInfo; prefixes = new HttpListenerPrefixCollection(logger, this); registry = new Dictionary(); connections = new Dictionary(); auth_schemes = AuthenticationSchemes.Anonymous; } public HttpListener( ILogger logger, X509Certificate certificate, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IStreamHelper streamHelper, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) : this(logger, cryptoProvider, socketFactory, streamHelper, fileSystem, environmentInfo) { _certificate = certificate; } public void LoadCert(X509Certificate cert) { _certificate = cert; } // TODO: Digest, NTLM and Negotiate require ControlPrincipal public AuthenticationSchemes AuthenticationSchemes { get => auth_schemes; set { CheckDisposed(); auth_schemes = value; } } public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate { get => auth_selector; set { CheckDisposed(); auth_selector = value; } } public bool IsListening => listening; public static bool IsSupported => true; public HttpListenerPrefixCollection Prefixes { get { CheckDisposed(); return prefixes; } } // TODO: use this public string Realm { get => realm; set { CheckDisposed(); realm = value; } } public bool UnsafeConnectionNtlmAuthentication { get => unsafe_ntlm_auth; set { CheckDisposed(); unsafe_ntlm_auth = value; } } //internal IMonoSslStream CreateSslStream(Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback) //{ // lock (registry) // { // if (tlsProvider == null) // tlsProvider = MonoTlsProviderFactory.GetProviderInternal(); // if (tlsSettings == null) // tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings(); // if (tlsSettings.RemoteCertificateValidationCallback == null) // tlsSettings.RemoteCertificateValidationCallback = callback; // return tlsProvider.CreateSslStream(innerStream, ownsStream, tlsSettings); // } //} internal X509Certificate Certificate => _certificate; public void Abort() { if (disposed) return; if (!listening) { return; } Close(true); } public void Close() { if (disposed) return; if (!listening) { disposed = true; return; } Close(true); disposed = true; } void Close(bool force) { CheckDisposed(); HttpEndPointManager.RemoveListener(_logger, this); Cleanup(force); } void Cleanup(bool close_existing) { lock (registry) { if (close_existing) { // Need to copy this since closing will call UnregisterContext ICollection keys = registry.Keys; var all = new HttpListenerContext[keys.Count]; keys.CopyTo(all, 0); registry.Clear(); for (int i = all.Length - 1; i >= 0; i--) all[i].Connection.Close(true); } lock (connections) { ICollection keys = connections.Keys; var conns = new HttpConnection[keys.Count]; keys.CopyTo(conns, 0); connections.Clear(); for (int i = conns.Length - 1; i >= 0; i--) conns[i].Close(true); } } } internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerContext context) { if (AuthenticationSchemeSelectorDelegate != null) return AuthenticationSchemeSelectorDelegate(context.Request); else return auth_schemes; } public void Start() { CheckDisposed(); if (listening) return; HttpEndPointManager.AddListener(_logger, this); listening = true; } public void Stop() { CheckDisposed(); listening = false; Close(false); } void IDisposable.Dispose() { if (disposed) return; Close(true); //TODO: Should we force here or not? disposed = true; } internal void CheckDisposed() { if (disposed) throw new ObjectDisposedException(GetType().Name); } internal void RegisterContext(HttpListenerContext context) { if (OnContext != null && IsListening) { OnContext(context); } lock (registry) registry[context] = context; } internal void UnregisterContext(HttpListenerContext context) { lock (registry) registry.Remove(context); } internal void AddConnection(HttpConnection cnc) { lock (connections) { connections[cnc] = cnc; } } internal void RemoveConnection(HttpConnection cnc) { lock (connections) { connections.Remove(cnc); } } } }