File: System\Net\HttpListener.cs
Web Access
Project: src\src\libraries\System.Net.HttpListener\src\System.Net.HttpListener.csproj (System.Net.HttpListener)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Security.Authentication.ExtendedProtection;
using System.Text;
using System.Threading.Tasks;
 
namespace System.Net
{
    public sealed unsafe partial class HttpListener : IDisposable
    {
        public delegate ExtendedProtectionPolicy ExtendedProtectionSelector(HttpListenerRequest request);
 
        private readonly object _internalLock;
        private volatile State _state; // _state is set only within lock blocks, but often read outside locks.
        private readonly HttpListenerPrefixCollection _prefixes;
        internal Hashtable _uriPrefixes = new Hashtable();
        private bool _ignoreWriteExceptions;
        private readonly ServiceNameStore _defaultServiceNames;
        private readonly HttpListenerTimeoutManager _timeoutManager;
        private ExtendedProtectionPolicy _extendedProtectionPolicy;
        private AuthenticationSchemeSelector? _authenticationDelegate;
        private AuthenticationSchemes _authenticationScheme = AuthenticationSchemes.Anonymous;
        private ExtendedProtectionSelector? _extendedProtectionSelectorDelegate;
        private string? _realm;
 
        internal ICollection PrefixCollection => _uriPrefixes.Keys;
 
        public HttpListener()
        {
            _state = State.Stopped;
            _internalLock = new object();
            _defaultServiceNames = new ServiceNameStore();
 
            _timeoutManager = new HttpListenerTimeoutManager(this);
            _prefixes = new HttpListenerPrefixCollection(this);
 
            // default: no CBT checks on any platform (appcompat reasons); applies also to PolicyEnforcement
            // config element
            _extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never);
        }
 
        public AuthenticationSchemeSelector? AuthenticationSchemeSelectorDelegate
        {
            get => _authenticationDelegate;
            set
            {
                CheckDisposed();
                _authenticationDelegate = value;
            }
        }
 
        [DisallowNull]
        public ExtendedProtectionSelector? ExtendedProtectionSelectorDelegate
        {
            get => _extendedProtectionSelectorDelegate;
            set
            {
                CheckDisposed();
                ArgumentNullException.ThrowIfNull(value);
 
                _extendedProtectionSelectorDelegate = value;
            }
        }
 
        public AuthenticationSchemes AuthenticationSchemes
        {
            get => _authenticationScheme;
            set
            {
                CheckDisposed();
                _authenticationScheme = value;
            }
        }
 
        public ExtendedProtectionPolicy ExtendedProtectionPolicy
        {
            get => _extendedProtectionPolicy;
            set
            {
                CheckDisposed();
                ArgumentNullException.ThrowIfNull(value);
                if (value.CustomChannelBinding != null)
                {
                    throw new ArgumentException(SR.net_listener_cannot_set_custom_cbt, nameof(value));
                }
 
                _extendedProtectionPolicy = value;
            }
        }
 
        public ServiceNameCollection DefaultServiceNames => _defaultServiceNames.ServiceNames;
 
        public HttpListenerPrefixCollection Prefixes
        {
            get
            {
                CheckDisposed();
                return _prefixes;
            }
        }
 
        internal void AddPrefix(string uriPrefix)
        {
            ArgumentNullException.ThrowIfNull(uriPrefix);
 
            string? registeredPrefix;
            try
            {
                CheckDisposed();
                int i;
                if (string.Compare(uriPrefix, 0, "http://", 0, 7, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    i = 7;
                }
                else if (string.Compare(uriPrefix, 0, "https://", 0, 8, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    i = 8;
                }
                else
                {
                    throw new ArgumentException(SR.net_listener_scheme, nameof(uriPrefix));
                }
                bool inSquareBrakets = false;
                int j = i;
                while (j < uriPrefix.Length && uriPrefix[j] != '/' && (uriPrefix[j] != ':' || inSquareBrakets))
                {
                    if (uriPrefix[j] == '[')
                    {
                        if (inSquareBrakets)
                        {
                            j = i;
                            break;
                        }
                        inSquareBrakets = true;
                    }
                    if (inSquareBrakets && uriPrefix[j] == ']')
                    {
                        inSquareBrakets = false;
                    }
                    j++;
                }
                if (i == j)
                {
                    throw new ArgumentException(SR.net_listener_host, nameof(uriPrefix));
                }
                if (!uriPrefix.EndsWith('/'))
                {
                    throw new ArgumentException(SR.net_listener_slash, nameof(uriPrefix));
                }
                registeredPrefix = CreateRegisteredPrefix(uriPrefix, j, i);
                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"mapped uriPrefix: {uriPrefix} to registeredPrefix: {registeredPrefix}");
                if (_state == State.Started)
                {
                    AddPrefixCore(registeredPrefix);
                }
                _uriPrefixes[uriPrefix] = registeredPrefix;
                _defaultServiceNames.Add(uriPrefix);
            }
            catch (Exception exception)
            {
                if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, exception);
                throw;
            }
 
            static string CreateRegisteredPrefix(string uriPrefix, int j, int i)
            {
                int length = uriPrefix.Length;
                if (uriPrefix[j] != ':')
                {
                    length += i == 7 ? ":80".Length : ":443".Length;
                }
 
                return string.Create(length, (uriPrefix, j, i), static (destination, state) =>
                {
                    if (state.uriPrefix[state.j] == ':')
                    {
                        state.uriPrefix.CopyTo(destination);
                    }
                    else
                    {
                        int indexOfNextCopy = state.j;
                        state.uriPrefix.AsSpan(0, indexOfNextCopy).CopyTo(destination);
 
                        if (state.i == 7)
                        {
                            ":80".CopyTo(destination.Slice(indexOfNextCopy));
                            indexOfNextCopy += 3;
                        }
                        else
                        {
                            ":443".CopyTo(destination.Slice(indexOfNextCopy));
                            indexOfNextCopy += 4;
                        }
 
                        state.uriPrefix.AsSpan(state.j).CopyTo(destination.Slice(indexOfNextCopy));
                    }
 
                    int toLowerLength = destination.IndexOf(':');
                    if (toLowerLength < 0)
                    {
                        toLowerLength = destination.Length;
                    }
 
                    OperationStatus operationStatus = Ascii.ToLowerInPlace(destination.Slice(0, toLowerLength), out _);
                    Debug.Assert(operationStatus == OperationStatus.Done);
                });
            }
        }
 
        internal bool ContainsPrefix(string uriPrefix) => _uriPrefixes.Contains(uriPrefix);
 
        internal bool RemovePrefix(string uriPrefix)
        {
            try
            {
                CheckDisposed();
                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"uriPrefix: {uriPrefix}");
                ArgumentNullException.ThrowIfNull(uriPrefix);
 
                if (!_uriPrefixes.Contains(uriPrefix))
                {
                    return false;
                }
 
                if (_state == State.Started)
                {
                    RemovePrefixCore((string)_uriPrefixes[uriPrefix]!);
                }
 
                _uriPrefixes.Remove(uriPrefix);
                _defaultServiceNames.Remove(uriPrefix);
            }
            catch (Exception exception)
            {
                if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, exception);
                throw;
            }
            return true;
        }
 
        internal void RemoveAll(bool clear)
        {
            CheckDisposed();
            // go through the uri list and unregister for each one of them
            if (_uriPrefixes.Count > 0)
            {
                if (_state == State.Started)
                {
                    foreach (string registeredPrefix in _uriPrefixes.Values)
                    {
                        RemovePrefixCore(registeredPrefix);
                    }
                }
 
                if (clear)
                {
                    _uriPrefixes.Clear();
                    _defaultServiceNames.Clear();
                }
            }
        }
 
        public string? Realm
        {
            get => _realm;
            set
            {
                CheckDisposed();
                _realm = value;
            }
        }
 
        public bool IsListening => _state == State.Started;
 
        public bool IgnoreWriteExceptions
        {
            get => _ignoreWriteExceptions;
            set
            {
                CheckDisposed();
                _ignoreWriteExceptions = value;
            }
        }
 
        public Task<HttpListenerContext> GetContextAsync()
        {
            return Task.Factory.FromAsync(
                (callback, state) => ((HttpListener)state!).BeginGetContext(callback, state),
                iar => ((HttpListener)iar!.AsyncState!).EndGetContext(iar),
                this);
        }
 
        public void Close()
        {
            try
            {
                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info("HttpListenerRequest::Close()");
                ((IDisposable)this).Dispose();
            }
            catch (Exception exception)
            {
                if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Close {exception}");
                throw;
            }
        }
 
        internal void CheckDisposed()
        {
            ObjectDisposedException.ThrowIf(_state == State.Closed, this);
        }
 
        private enum State
        {
            Stopped,
            Started,
            Closed,
        }
 
        void IDisposable.Dispose() => Dispose();
    }
}