Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data
This commit is contained in:
243
Vendor/EmbedIO-3.5.2/Net/CookieList.cs
vendored
Normal file
243
Vendor/EmbedIO-3.5.2/Net/CookieList.cs
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Net.Internal;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Provides a collection container for instances of <see cref="Cookie"/>.</para>
|
||||
/// <para>This class is meant to be used internally by EmbedIO; you don't need to
|
||||
/// use this class directly.</para>
|
||||
/// </summary>
|
||||
#pragma warning disable CA1710 // Rename class to end in 'Collection' - it ends in 'List', i.e. 'Indexed Collection'.
|
||||
public sealed class CookieList : List<Cookie>, ICookieCollection
|
||||
#pragma warning restore CA1710
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool IsSynchronized => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Cookie? this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
if (Count == 0)
|
||||
return null;
|
||||
|
||||
var list = new List<Cookie>(this);
|
||||
|
||||
list.Sort(CompareCookieWithinSorted);
|
||||
|
||||
return list.FirstOrDefault(cookie => cookie.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a <see cref="CookieList"/> by parsing
|
||||
/// the value of one or more <c>Cookie</c> or <c>Set-Cookie</c> headers.</summary>
|
||||
/// <param name="headerValue">The value, or comma-separated list of values,
|
||||
/// of the header or headers.</param>
|
||||
/// <returns>A newly-created instance of <see cref="CookieList"/>.</returns>
|
||||
public static CookieList Parse(string headerValue)
|
||||
{
|
||||
var cookies = new CookieList();
|
||||
|
||||
Cookie? cookie = null;
|
||||
var pairs = SplitCookieHeaderValue(headerValue);
|
||||
|
||||
for (var i = 0; i < pairs.Length; i++)
|
||||
{
|
||||
var pair = pairs[i].Trim();
|
||||
if (pair.Length == 0)
|
||||
continue;
|
||||
|
||||
if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Version = int.Parse(GetValue(pair, true), CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
var buff = new StringBuilder(GetValue(pair), 32);
|
||||
if (i < pairs.Length - 1)
|
||||
buff.AppendFormat(CultureInfo.InvariantCulture, ", {0}", pairs[++i].Trim());
|
||||
|
||||
if (!HttpDate.TryParse(buff.ToString(), out var expires))
|
||||
expires = DateTimeOffset.Now;
|
||||
|
||||
if (cookie.Expires == DateTime.MinValue)
|
||||
cookie.Expires = expires.LocalDateTime;
|
||||
}
|
||||
else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
var max = int.Parse(GetValue(pair, true), CultureInfo.InvariantCulture);
|
||||
|
||||
cookie.Expires = DateTime.Now.AddSeconds(max);
|
||||
}
|
||||
else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Path = GetValue(pair);
|
||||
}
|
||||
else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Domain = GetValue(pair);
|
||||
}
|
||||
else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Port = pair.Equals("port", StringComparison.OrdinalIgnoreCase)
|
||||
? "\"\""
|
||||
: GetValue(pair);
|
||||
}
|
||||
else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Comment = WebUtility.UrlDecode(GetValue(pair));
|
||||
}
|
||||
else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.CommentUri = UriUtility.StringToUri(GetValue(pair, true));
|
||||
}
|
||||
else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Discard = true;
|
||||
}
|
||||
else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Secure = true;
|
||||
}
|
||||
else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.HttpOnly = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
cookie = ParseCookie(pair);
|
||||
}
|
||||
}
|
||||
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public new void Add(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
|
||||
var pos = SearchCookie(cookie);
|
||||
if (pos == -1)
|
||||
{
|
||||
base.Add(cookie);
|
||||
return;
|
||||
}
|
||||
|
||||
this[pos] = cookie;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
if (array == null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
|
||||
|
||||
if (array.Rank > 1)
|
||||
throw new ArgumentException("Multidimensional.", nameof(array));
|
||||
|
||||
if (array.Length - index < Count)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The number of elements in this collection is greater than the available space of the destination array.");
|
||||
}
|
||||
|
||||
if (array.GetType().GetElementType()?.IsAssignableFrom(typeof(Cookie)) != true)
|
||||
{
|
||||
throw new InvalidCastException(
|
||||
"The elements in this collection cannot be cast automatically to the type of the destination array.");
|
||||
}
|
||||
|
||||
((IList) this).CopyTo(array, index);
|
||||
}
|
||||
|
||||
private static string? GetValue(string nameAndValue, bool unquote = false)
|
||||
{
|
||||
var idx = nameAndValue.IndexOf('=');
|
||||
|
||||
if (idx < 0 || idx == nameAndValue.Length - 1)
|
||||
return null;
|
||||
|
||||
var val = nameAndValue.Substring(idx + 1).Trim();
|
||||
return unquote ? val.Unquote() : val;
|
||||
}
|
||||
|
||||
private static string[] SplitCookieHeaderValue(string value) => value.SplitHeaderValue(true).ToArray();
|
||||
|
||||
private static int CompareCookieWithinSorted(Cookie x, Cookie y)
|
||||
{
|
||||
var ret = x.Version - y.Version;
|
||||
return ret != 0
|
||||
? ret
|
||||
: (ret = string.Compare(x.Name, y.Name, StringComparison.Ordinal)) != 0
|
||||
? ret
|
||||
: y.Path.Length - x.Path.Length;
|
||||
}
|
||||
|
||||
private static Cookie ParseCookie(string pair)
|
||||
{
|
||||
string name;
|
||||
var val = string.Empty;
|
||||
|
||||
var pos = pair.IndexOf('=');
|
||||
if (pos == -1)
|
||||
{
|
||||
name = pair;
|
||||
}
|
||||
else if (pos == pair.Length - 1)
|
||||
{
|
||||
name = pair.Substring(0, pos).TrimEnd(' ');
|
||||
}
|
||||
else
|
||||
{
|
||||
name = pair.Substring(0, pos).TrimEnd(' ');
|
||||
val = pair.Substring(pos + 1).TrimStart(' ');
|
||||
}
|
||||
|
||||
return new Cookie(name, val);
|
||||
}
|
||||
|
||||
private int SearchCookie(Cookie cookie)
|
||||
{
|
||||
var name = cookie.Name;
|
||||
var path = cookie.Path;
|
||||
var domain = cookie.Domain;
|
||||
var ver = cookie.Version;
|
||||
|
||||
for (var i = Count - 1; i >= 0; i--)
|
||||
{
|
||||
var c = this[i];
|
||||
if (c.Name.Equals(name, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.Path.Equals(path, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.Domain.Equals(domain, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.Version == ver)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
141
Vendor/EmbedIO-3.5.2/Net/EndPointManager.cs
vendored
Normal file
141
Vendor/EmbedIO-3.5.2/Net/EndPointManager.cs
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using EmbedIO.Net.Internal;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the EndPoint Manager.
|
||||
/// </summary>
|
||||
public static class EndPointManager
|
||||
{
|
||||
private static readonly ConcurrentDictionary<IPAddress, ConcurrentDictionary<int, EndPointListener>> IPToEndpoints = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [use IPv6]. By default, this flag is set.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [use IPv6]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public static bool UseIpv6 { get; set; } = true;
|
||||
|
||||
internal static void AddListener(HttpListener listener)
|
||||
{
|
||||
var added = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var prefix in listener.Prefixes)
|
||||
{
|
||||
AddPrefix(prefix, listener);
|
||||
added.Add(prefix);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.Log(nameof(AddListener));
|
||||
|
||||
foreach (var prefix in added)
|
||||
{
|
||||
RemovePrefix(prefix, listener);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RemoveEndPoint(EndPointListener epl, IPEndPoint ep)
|
||||
{
|
||||
if (IPToEndpoints.TryGetValue(ep.Address, out var p))
|
||||
{
|
||||
if (p.TryRemove(ep.Port, out _) && p.Count == 0)
|
||||
{
|
||||
_ = IPToEndpoints.TryRemove(ep.Address, out _);
|
||||
}
|
||||
}
|
||||
|
||||
epl.Dispose();
|
||||
}
|
||||
|
||||
internal static void RemoveListener(HttpListener listener)
|
||||
{
|
||||
foreach (var prefix in listener.Prefixes)
|
||||
{
|
||||
RemovePrefix(prefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void AddPrefix(string p, HttpListener listener)
|
||||
{
|
||||
var lp = new ListenerPrefix(p);
|
||||
|
||||
if (!lp.IsValid())
|
||||
{
|
||||
throw new HttpListenerException(400, "Invalid path.");
|
||||
}
|
||||
|
||||
// listens on all the interfaces if host name cannot be parsed by IPAddress.
|
||||
var epl = GetEpListener(lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.AddPrefix(lp, listener);
|
||||
}
|
||||
|
||||
private static EndPointListener GetEpListener(string host, int port, HttpListener listener, bool secure = false)
|
||||
{
|
||||
var address = ResolveAddress(host);
|
||||
|
||||
var p = IPToEndpoints.GetOrAdd(address, x => new ConcurrentDictionary<int, EndPointListener>());
|
||||
return p.GetOrAdd(port, x => new EndPointListener(listener, address, x, secure));
|
||||
}
|
||||
|
||||
private static IPAddress ResolveAddress(string host)
|
||||
{
|
||||
if (host == "*" || host == "+" || host == "0.0.0.0")
|
||||
{
|
||||
return UseIpv6 ? IPAddress.IPv6Any : IPAddress.Any;
|
||||
}
|
||||
|
||||
if (IPAddress.TryParse(host, out var address))
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var hostEntry = new IPHostEntry {
|
||||
HostName = host,
|
||||
AddressList = Dns.GetHostAddresses(host),
|
||||
};
|
||||
|
||||
return hostEntry.AddressList[0];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return UseIpv6 ? IPAddress.IPv6Any : IPAddress.Any;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemovePrefix(string prefix, HttpListener listener)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lp = new ListenerPrefix(prefix);
|
||||
|
||||
if (!lp.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var epl = GetEpListener(lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.RemovePrefix(lp);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
Vendor/EmbedIO-3.5.2/Net/HttpListener.cs
vendored
Normal file
161
Vendor/EmbedIO-3.5.2/Net/HttpListener.cs
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Net.Internal;
|
||||
|
||||
namespace EmbedIO.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// The EmbedIO implementation of the standard HTTP Listener class.
|
||||
///
|
||||
/// Based on MONO HttpListener class.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
public sealed class HttpListener : IHttpListener
|
||||
{
|
||||
private readonly SemaphoreSlim _ctxQueueSem = new (0);
|
||||
private readonly ConcurrentDictionary<string, HttpListenerContext> _ctxQueue;
|
||||
private readonly ConcurrentDictionary<HttpConnection, object> _connections;
|
||||
private readonly HttpListenerPrefixCollection _prefixes;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListener" /> class.
|
||||
/// </summary>
|
||||
/// <param name="certificate">The certificate.</param>
|
||||
public HttpListener(X509Certificate? certificate = null)
|
||||
{
|
||||
Certificate = certificate;
|
||||
|
||||
_prefixes = new HttpListenerPrefixCollection(this);
|
||||
_connections = new ConcurrentDictionary<HttpConnection, object>();
|
||||
_ctxQueue = new ConcurrentDictionary<string, HttpListenerContext>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IgnoreWriteExceptions { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsListening { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name { get; } = "Unosquare HTTP Listener";
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<string> Prefixes => _prefixes.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the certificate.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The certificate.
|
||||
/// </value>
|
||||
internal X509Certificate? Certificate { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start()
|
||||
{
|
||||
if (IsListening)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndPointManager.AddListener(this);
|
||||
IsListening = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Stop()
|
||||
{
|
||||
IsListening = false;
|
||||
Close(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddPrefix(string urlPrefix) => _prefixes.Add(urlPrefix);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Close(true);
|
||||
_ctxQueueSem.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IHttpContextImpl> GetContextAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await _ctxQueueSem.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var key in _ctxQueue.Keys)
|
||||
{
|
||||
if (_ctxQueue.TryRemove(key, out var context))
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RegisterContext(HttpListenerContext context)
|
||||
{
|
||||
if (!_ctxQueue.TryAdd(context.Id, context))
|
||||
{
|
||||
throw new InvalidOperationException("Unable to register context");
|
||||
}
|
||||
|
||||
_ = _ctxQueueSem.Release();
|
||||
}
|
||||
|
||||
internal void UnregisterContext(HttpListenerContext context) => _ctxQueue.TryRemove(context.Id, out _);
|
||||
|
||||
internal void AddConnection(HttpConnection cnc) => _connections[cnc] = cnc;
|
||||
|
||||
internal void RemoveConnection(HttpConnection cnc) => _connections.TryRemove(cnc, out _);
|
||||
|
||||
private void Close(bool closeExisting)
|
||||
{
|
||||
EndPointManager.RemoveListener(this);
|
||||
|
||||
var keys = _connections.Keys;
|
||||
var connections = new HttpConnection[keys.Count];
|
||||
keys.CopyTo(connections, 0);
|
||||
_connections.Clear();
|
||||
var list = new List<HttpConnection>(connections);
|
||||
|
||||
for (var i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
list[i].Close(true);
|
||||
}
|
||||
|
||||
if (!closeExisting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (!_ctxQueue.IsEmpty)
|
||||
{
|
||||
foreach (var key in _ctxQueue.Keys.ToArray())
|
||||
{
|
||||
if (_ctxQueue.TryGetValue(key, out var context))
|
||||
{
|
||||
context.Connection.Close(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
429
Vendor/EmbedIO-3.5.2/Net/Internal/EndPointListener.cs
vendored
Normal file
429
Vendor/EmbedIO-3.5.2/Net/Internal/EndPointListener.cs
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal sealed class EndPointListener : IDisposable
|
||||
{
|
||||
private readonly Dictionary<HttpConnection, HttpConnection> _unregistered;
|
||||
private readonly IPEndPoint _endpoint;
|
||||
private readonly Socket _sock;
|
||||
private Dictionary<ListenerPrefix, HttpListener> _prefixes;
|
||||
private List<ListenerPrefix>? _unhandled; // unhandled; host = '*'
|
||||
private List<ListenerPrefix>? _all; // all; host = '+
|
||||
|
||||
public EndPointListener(HttpListener listener, IPAddress address, int port, bool secure)
|
||||
{
|
||||
Listener = listener;
|
||||
Secure = secure;
|
||||
_endpoint = new IPEndPoint(address, port);
|
||||
_sock = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
if (address.AddressFamily == AddressFamily.InterNetworkV6 && EndPointManager.UseIpv6)
|
||||
{
|
||||
_sock.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
|
||||
}
|
||||
|
||||
_sock.Bind(_endpoint);
|
||||
_sock.Listen(500);
|
||||
var args = new SocketAsyncEventArgs { UserToken = this };
|
||||
args.Completed += OnAccept;
|
||||
Socket? dummy = null;
|
||||
Accept(_sock, args, ref dummy);
|
||||
_prefixes = new Dictionary<ListenerPrefix, HttpListener>();
|
||||
_unregistered = new Dictionary<HttpConnection, HttpConnection>();
|
||||
}
|
||||
|
||||
internal HttpListener Listener { get; }
|
||||
|
||||
internal bool Secure { get; }
|
||||
|
||||
public bool BindContext(HttpListenerContext context)
|
||||
{
|
||||
var req = context.Request;
|
||||
var listener = SearchListener(req.Url, out var prefix);
|
||||
|
||||
if (listener == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context.Listener = listener;
|
||||
context.Connection.Prefix = prefix;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UnbindContext(HttpListenerContext context) => context.Listener.UnregisterContext(context);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_sock.Dispose();
|
||||
List<HttpConnection> connections;
|
||||
|
||||
lock (_unregistered)
|
||||
{
|
||||
// Clone the list because RemoveConnection can be called from Close
|
||||
connections = new List<HttpConnection>(_unregistered.Keys);
|
||||
_unregistered.Clear();
|
||||
}
|
||||
|
||||
foreach (var c in connections)
|
||||
{
|
||||
c.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPrefix(ListenerPrefix prefix, HttpListener listener)
|
||||
{
|
||||
List<ListenerPrefix>? current;
|
||||
List<ListenerPrefix> future;
|
||||
|
||||
if (prefix.Host == "*")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _unhandled;
|
||||
|
||||
// TODO: Should we clone the items?
|
||||
future = current?.ToList() ?? new List<ListenerPrefix>();
|
||||
prefix.Listener = listener;
|
||||
AddSpecial(future, prefix);
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _unhandled, future, current) != current);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix.Host == "+")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _all;
|
||||
future = current?.ToList() ?? new List<ListenerPrefix>();
|
||||
prefix.Listener = listener;
|
||||
AddSpecial(future, prefix);
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _all, future, current) != current);
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
|
||||
|
||||
do
|
||||
{
|
||||
prefs = _prefixes;
|
||||
if (prefs.ContainsKey(prefix))
|
||||
{
|
||||
if (prefs[prefix] != listener)
|
||||
{
|
||||
throw new HttpListenerException(400, $"There is another listener for {prefix}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
p2 = prefs.ToDictionary(x => x.Key, x => x.Value);
|
||||
p2[prefix] = listener;
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
|
||||
}
|
||||
|
||||
public void RemovePrefix(ListenerPrefix prefix)
|
||||
{
|
||||
List<ListenerPrefix>? current;
|
||||
List<ListenerPrefix> future;
|
||||
|
||||
if (prefix.Host == "*")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _unhandled;
|
||||
future = current?.ToList() ?? new List<ListenerPrefix>();
|
||||
if (!RemoveSpecial(future, prefix))
|
||||
{
|
||||
break; // Prefix not found
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _unhandled, future, current) != current);
|
||||
|
||||
CheckIfRemove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix.Host == "+")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _all;
|
||||
future = current?.ToList() ?? new List<ListenerPrefix>();
|
||||
if (!RemoveSpecial(future, prefix))
|
||||
{
|
||||
break; // Prefix not found
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _all, future, current) != current);
|
||||
|
||||
CheckIfRemove();
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
|
||||
|
||||
do
|
||||
{
|
||||
prefs = _prefixes;
|
||||
var prefixKey = _prefixes.Keys.FirstOrDefault(p => p.Path == prefix.Path);
|
||||
|
||||
if (prefixKey is null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
p2 = prefs.ToDictionary(x => x.Key, x => x.Value);
|
||||
_ = p2.Remove(prefixKey);
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
|
||||
|
||||
CheckIfRemove();
|
||||
}
|
||||
|
||||
internal void RemoveConnection(HttpConnection conn)
|
||||
{
|
||||
lock (_unregistered)
|
||||
{
|
||||
_ = _unregistered.Remove(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Accept(Socket socket, SocketAsyncEventArgs e, ref Socket? accepted)
|
||||
{
|
||||
e.AcceptSocket = null;
|
||||
bool acceptPending;
|
||||
|
||||
try
|
||||
{
|
||||
acceptPending = socket.AcceptAsync(e);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
accepted?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
accepted = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!acceptPending)
|
||||
{
|
||||
ProcessAccept(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessAccept(SocketAsyncEventArgs args)
|
||||
{
|
||||
Socket? accepted = null;
|
||||
if (args.SocketError == SocketError.Success)
|
||||
{
|
||||
accepted = args.AcceptSocket;
|
||||
}
|
||||
|
||||
var epl = (EndPointListener)args.UserToken;
|
||||
|
||||
Accept(epl._sock, args, ref accepted);
|
||||
if (accepted == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (epl.Secure && epl.Listener.Certificate == null)
|
||||
{
|
||||
accepted.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
HttpConnection conn;
|
||||
try
|
||||
{
|
||||
conn = new HttpConnection(accepted, epl);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (epl._unregistered)
|
||||
{
|
||||
epl._unregistered[conn] = conn;
|
||||
}
|
||||
|
||||
_ = conn.BeginReadRequest();
|
||||
}
|
||||
|
||||
private static void OnAccept(object sender, SocketAsyncEventArgs e) => ProcessAccept(e);
|
||||
|
||||
private static HttpListener? MatchFromList(string path, List<ListenerPrefix>? list, out ListenerPrefix? prefix)
|
||||
{
|
||||
prefix = null;
|
||||
if (list == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpListener? bestMatch = null;
|
||||
var bestLength = -1;
|
||||
|
||||
foreach (var p in list)
|
||||
{
|
||||
if (p.Path.Length < bestLength || !path.StartsWith(p.Path, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bestLength = p.Path.Length;
|
||||
bestMatch = p.Listener;
|
||||
prefix = p;
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
private static void AddSpecial(ICollection<ListenerPrefix> coll, ListenerPrefix prefix)
|
||||
{
|
||||
if (coll == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (coll.Any(p => p.Path == prefix.Path))
|
||||
{
|
||||
throw new HttpListenerException(400, "Prefix already in use.");
|
||||
}
|
||||
|
||||
coll.Add(prefix);
|
||||
}
|
||||
|
||||
private static bool RemoveSpecial(IList<ListenerPrefix> coll, ListenerPrefix prefix)
|
||||
{
|
||||
if (coll == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var c = coll.Count;
|
||||
for (var i = 0; i < c; i++)
|
||||
{
|
||||
if (coll[i].Path != prefix.Path)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
coll.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private HttpListener? SearchListener(Uri uri, out ListenerPrefix? prefix)
|
||||
{
|
||||
prefix = null;
|
||||
if (uri == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var host = uri.Host;
|
||||
var port = uri.Port;
|
||||
var path = WebUtility.UrlDecode(uri.AbsolutePath);
|
||||
var pathSlash = path[path.Length - 1] == '/' ? path : path + "/";
|
||||
|
||||
HttpListener? bestMatch = null;
|
||||
var bestLength = -1;
|
||||
|
||||
if (!string.IsNullOrEmpty(host))
|
||||
{
|
||||
var result = _prefixes;
|
||||
|
||||
foreach (var p in result.Keys)
|
||||
{
|
||||
if (p.Path.Length < bestLength)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p.Host != host || p.Port != port)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!path.StartsWith(p.Path, StringComparison.Ordinal) && !pathSlash.StartsWith(p.Path, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bestLength = p.Path.Length;
|
||||
bestMatch = result[p];
|
||||
prefix = p;
|
||||
}
|
||||
|
||||
if (bestLength != -1)
|
||||
{
|
||||
return bestMatch;
|
||||
}
|
||||
}
|
||||
|
||||
var list = _unhandled;
|
||||
bestMatch = MatchFromList(path, list, out prefix);
|
||||
if (path != pathSlash && bestMatch == null)
|
||||
{
|
||||
bestMatch = MatchFromList(pathSlash, list, out prefix);
|
||||
}
|
||||
|
||||
if (bestMatch != null)
|
||||
{
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
list = _all;
|
||||
bestMatch = MatchFromList(path, list, out prefix);
|
||||
if (path != pathSlash && bestMatch == null)
|
||||
{
|
||||
bestMatch = MatchFromList(pathSlash, list, out prefix);
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
private void CheckIfRemove()
|
||||
{
|
||||
if (_prefixes.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var list = _unhandled;
|
||||
if (list != null && list.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
list = _all;
|
||||
if (list != null && list.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndPointManager.RemoveEndPoint(this, _endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Vendor/EmbedIO-3.5.2/Net/Internal/HeaderUtility.cs
vendored
Normal file
23
Vendor/EmbedIO-3.5.2/Net/Internal/HeaderUtility.cs
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal static class HeaderUtility
|
||||
{
|
||||
public static string? GetCharset(string? contentType)
|
||||
=> contentType?
|
||||
.Split(';')
|
||||
.Select(p => p.Trim())
|
||||
.Where(part => part.StartsWith("charset", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(GetAttributeValue)
|
||||
.FirstOrDefault();
|
||||
|
||||
public static string? GetAttributeValue(string nameAndValue)
|
||||
{
|
||||
var idx = nameAndValue.IndexOf('=');
|
||||
|
||||
return idx < 0 || idx == nameAndValue.Length - 1 ? null : nameAndValue.Substring(idx + 1).Trim().Unquote();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.InputState.cs
vendored
Normal file
11
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.InputState.cs
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
partial class HttpConnection
|
||||
{
|
||||
private enum InputState
|
||||
{
|
||||
RequestLine,
|
||||
Headers,
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.LineState.cs
vendored
Normal file
12
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.LineState.cs
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
partial class HttpConnection
|
||||
{
|
||||
private enum LineState
|
||||
{
|
||||
None,
|
||||
Cr,
|
||||
Lf,
|
||||
}
|
||||
}
|
||||
}
|
||||
416
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.cs
vendored
Normal file
416
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.cs
vendored
Normal file
@@ -0,0 +1,416 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal sealed partial class HttpConnection : IDisposable
|
||||
{
|
||||
private const int BufferSize = 8192;
|
||||
|
||||
private readonly Timer _timer;
|
||||
private readonly EndPointListener _epl;
|
||||
private Socket? _sock;
|
||||
private MemoryStream? _ms;
|
||||
private byte[]? _buffer;
|
||||
private HttpListenerContext _context;
|
||||
private StringBuilder? _currentLine;
|
||||
private RequestStream? _iStream;
|
||||
private ResponseStream? _oStream;
|
||||
private bool _contextBound;
|
||||
private int _sTimeout = 90000; // 90k ms for first request, 15k ms from then on
|
||||
private HttpListener? _lastListener;
|
||||
private InputState _inputState = InputState.RequestLine;
|
||||
private LineState _lineState = LineState.None;
|
||||
private int _position;
|
||||
private string? _errorMessage;
|
||||
|
||||
public HttpConnection(Socket sock, EndPointListener epl)
|
||||
{
|
||||
_sock = sock;
|
||||
_epl = epl;
|
||||
IsSecure = epl.Secure;
|
||||
LocalEndPoint = (IPEndPoint) sock.LocalEndPoint;
|
||||
RemoteEndPoint = (IPEndPoint) sock.RemoteEndPoint;
|
||||
|
||||
Stream = new NetworkStream(sock, false);
|
||||
if (IsSecure)
|
||||
{
|
||||
var sslStream = new SslStream(Stream, true);
|
||||
|
||||
try
|
||||
{
|
||||
sslStream.AuthenticateAsServer(epl.Listener.Certificate);
|
||||
}
|
||||
catch
|
||||
{
|
||||
CloseSocket();
|
||||
throw;
|
||||
}
|
||||
|
||||
Stream = sslStream;
|
||||
}
|
||||
|
||||
_timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
|
||||
_context = null!; // Silence warning about uninitialized field - _context will be initialized by the Init method
|
||||
Init();
|
||||
}
|
||||
|
||||
public int Reuses { get; private set; }
|
||||
|
||||
public Stream Stream { get; }
|
||||
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
public bool IsSecure { get; }
|
||||
|
||||
public ListenerPrefix? Prefix { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Close(true);
|
||||
|
||||
_timer.Dispose();
|
||||
_sock?.Dispose();
|
||||
_ms?.Dispose();
|
||||
_iStream?.Dispose();
|
||||
_oStream?.Dispose();
|
||||
Stream?.Dispose();
|
||||
_lastListener?.Dispose();
|
||||
}
|
||||
|
||||
public async Task BeginReadRequest()
|
||||
{
|
||||
_buffer ??= new byte[BufferSize];
|
||||
|
||||
try
|
||||
{
|
||||
if (Reuses == 1)
|
||||
{
|
||||
_sTimeout = 15000;
|
||||
}
|
||||
|
||||
_ = _timer.Change(_sTimeout, Timeout.Infinite);
|
||||
|
||||
var data = await Stream.ReadAsync(_buffer, 0, BufferSize).ConfigureAwait(false);
|
||||
await OnReadInternal(data).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_ = _timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
public RequestStream GetRequestStream(long contentLength)
|
||||
{
|
||||
if (_iStream == null)
|
||||
{
|
||||
var buffer = _ms.ToArray();
|
||||
var length = (int) _ms.Length;
|
||||
_ms = null;
|
||||
|
||||
_iStream = new RequestStream(Stream, buffer, _position, length - _position, contentLength);
|
||||
}
|
||||
|
||||
return _iStream;
|
||||
}
|
||||
|
||||
public ResponseStream GetResponseStream() => _oStream ??= new ResponseStream(Stream, _context.HttpListenerResponse, _context.Listener?.IgnoreWriteExceptions ?? true);
|
||||
|
||||
internal void SetError(string message) => _errorMessage = message;
|
||||
|
||||
internal void ForceClose() => Close(true);
|
||||
|
||||
internal void Close(bool forceClose = false)
|
||||
{
|
||||
if (_sock != null)
|
||||
{
|
||||
_oStream?.Dispose();
|
||||
_oStream = null;
|
||||
}
|
||||
|
||||
if (_sock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
forceClose = forceClose
|
||||
|| !_context.Request.KeepAlive
|
||||
|| _context.Response.Headers["connection"] == "close";
|
||||
|
||||
if (!forceClose)
|
||||
{
|
||||
if (_context.HttpListenerRequest.FlushInput())
|
||||
{
|
||||
Reuses++;
|
||||
Unbind();
|
||||
Init();
|
||||
_ = BeginReadRequest();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
using (var s = _sock)
|
||||
{
|
||||
_sock = null;
|
||||
try
|
||||
{
|
||||
s?.Shutdown(SocketShutdown.Both);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
Unbind();
|
||||
RemoveConnection();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
_contextBound = false;
|
||||
_iStream = null;
|
||||
_oStream = null;
|
||||
Prefix = null;
|
||||
_ms = new MemoryStream();
|
||||
_position = 0;
|
||||
_inputState = InputState.RequestLine;
|
||||
_lineState = LineState.None;
|
||||
_context = new HttpListenerContext(this);
|
||||
}
|
||||
|
||||
private void OnTimeout(object unused)
|
||||
{
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
}
|
||||
|
||||
private async Task OnReadInternal(int offset)
|
||||
{
|
||||
_ = _timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
|
||||
// Continue reading until full header is received.
|
||||
// Especially important for multipart requests when the second part of the header arrives after a tiny delay
|
||||
// because the web browser has to measure the content length first.
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _ms.WriteAsync(_buffer, 0, offset).ConfigureAwait(false);
|
||||
if (_ms.Length > 32768)
|
||||
{
|
||||
Close(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProcessInput(_ms))
|
||||
{
|
||||
if (_errorMessage is null)
|
||||
{
|
||||
_context.HttpListenerRequest.FinishInitialization();
|
||||
}
|
||||
|
||||
if (_errorMessage != null || !_epl.BindContext(_context))
|
||||
{
|
||||
Close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var listener = _context.Listener;
|
||||
if (_lastListener != listener)
|
||||
{
|
||||
RemoveConnection();
|
||||
listener.AddConnection(this);
|
||||
_lastListener = listener;
|
||||
}
|
||||
|
||||
_contextBound = true;
|
||||
listener.RegisterContext(_context);
|
||||
return;
|
||||
}
|
||||
|
||||
offset = await Stream.ReadAsync(_buffer, 0, BufferSize).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveConnection()
|
||||
{
|
||||
if (_lastListener != null)
|
||||
{
|
||||
_lastListener.RemoveConnection(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
_epl.RemoveConnection(this);
|
||||
}
|
||||
}
|
||||
|
||||
// true -> done processing
|
||||
// false -> need more input
|
||||
private bool ProcessInput(MemoryStream ms)
|
||||
{
|
||||
var buffer = ms.ToArray();
|
||||
var len = (int)ms.Length;
|
||||
var used = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (_errorMessage != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_position >= len)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string? line;
|
||||
try
|
||||
{
|
||||
line = ReadLine(buffer, _position, len - _position, out used);
|
||||
_position += used;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_errorMessage = "Bad request";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
if (_inputState == InputState.RequestLine)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_currentLine = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_inputState == InputState.RequestLine)
|
||||
{
|
||||
_context.HttpListenerRequest.SetRequestLine(line);
|
||||
_inputState = InputState.Headers;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.HttpListenerRequest.AddHeader(line);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorMessage = e.Message;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (used == len)
|
||||
{
|
||||
ms.SetLength(0);
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string? ReadLine(byte[] buffer, int offset, int len, out int used)
|
||||
{
|
||||
_currentLine ??= new StringBuilder(128);
|
||||
|
||||
var last = offset + len;
|
||||
used = 0;
|
||||
for (var i = offset; i < last && _lineState != LineState.Lf; i++)
|
||||
{
|
||||
used++;
|
||||
var b = buffer[i];
|
||||
|
||||
switch (b)
|
||||
{
|
||||
case 13:
|
||||
_lineState = LineState.Cr;
|
||||
break;
|
||||
case 10:
|
||||
_lineState = LineState.Lf;
|
||||
break;
|
||||
default:
|
||||
_ = _currentLine.Append((char)b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_lineState != LineState.Lf)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_lineState = LineState.None;
|
||||
var result = _currentLine.ToString();
|
||||
_currentLine.Length = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Unbind()
|
||||
{
|
||||
if (!_contextBound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_epl.UnbindContext(_context);
|
||||
_contextBound = false;
|
||||
}
|
||||
|
||||
private void CloseSocket()
|
||||
{
|
||||
if (_sock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_sock.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sock = null;
|
||||
}
|
||||
|
||||
RemoveConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
129
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerContext.cs
vendored
Normal file
129
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerContext.cs
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Authentication;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.Sessions;
|
||||
using EmbedIO.Utilities;
|
||||
using EmbedIO.WebSockets;
|
||||
using EmbedIO.WebSockets.Internal;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
// Provides access to the request and response objects used by the HttpListener class.
|
||||
internal sealed class HttpListenerContext : IHttpContextImpl
|
||||
{
|
||||
private readonly Lazy<IDictionary<object, object>> _items = new (() => new Dictionary<object, object>(), true);
|
||||
|
||||
private readonly TimeKeeper _ageKeeper = new ();
|
||||
|
||||
private readonly Stack<Action<IHttpContext>> _closeCallbacks = new ();
|
||||
|
||||
private bool _closed;
|
||||
|
||||
internal HttpListenerContext(HttpConnection cnc)
|
||||
{
|
||||
Connection = cnc;
|
||||
HttpListenerRequest = new HttpListenerRequest(this);
|
||||
User = Auth.NoUser;
|
||||
HttpListenerResponse = new HttpListenerResponse(this);
|
||||
Id = UniqueIdGenerator.GetNext();
|
||||
LocalEndPoint = Request.LocalEndPoint;
|
||||
RemoteEndPoint = Request.RemoteEndPoint;
|
||||
Route = RouteMatch.None;
|
||||
Session = SessionProxy.None;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
public long Age => _ageKeeper.ElapsedTime;
|
||||
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
public IHttpRequest Request => HttpListenerRequest;
|
||||
|
||||
public RouteMatch Route { get; set; }
|
||||
|
||||
public string RequestedPath => Route.SubPath ?? string.Empty; // It will never be empty, because modules are matched via base routes - this is just to silence a warning.
|
||||
|
||||
public IHttpResponse Response => HttpListenerResponse;
|
||||
|
||||
public IPrincipal User { get; set; }
|
||||
|
||||
public ISessionProxy Session { get; set; }
|
||||
|
||||
public bool SupportCompressedRequests { get; set; }
|
||||
|
||||
public IDictionary<object, object> Items => _items.Value;
|
||||
|
||||
public bool IsHandled { get; private set; }
|
||||
|
||||
public MimeTypeProviderStack MimeTypeProviders { get; } = new MimeTypeProviderStack();
|
||||
|
||||
internal HttpListenerRequest HttpListenerRequest { get; }
|
||||
|
||||
internal HttpListenerResponse HttpListenerResponse { get; }
|
||||
|
||||
internal HttpListener? Listener { get; set; }
|
||||
|
||||
internal HttpConnection Connection { get; }
|
||||
|
||||
public void SetHandled() => IsHandled = true;
|
||||
|
||||
public void OnClose(Action<IHttpContext> callback)
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
throw new InvalidOperationException("HTTP context has already been closed.");
|
||||
}
|
||||
|
||||
_closeCallbacks.Push(Validate.NotNull(nameof(callback), callback));
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_closed = true;
|
||||
|
||||
// Always close the response stream no matter what.
|
||||
Response.Close();
|
||||
|
||||
foreach (var callback in _closeCallbacks)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Log("HTTP context", $"[{Id}] Exception thrown by a HTTP context close callback.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IWebSocketContext> AcceptWebSocketAsync(
|
||||
IEnumerable<string> requestedProtocols,
|
||||
string acceptedProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var webSocket = await WebSocket.AcceptAsync(this, acceptedProtocol).ConfigureAwait(false);
|
||||
return new WebSocketContext(this, WebSocket.SupportedVersion, requestedProtocols, acceptedProtocol, webSocket, cancellationToken);
|
||||
}
|
||||
|
||||
public string GetMimeType(string extension)
|
||||
=> MimeTypeProviders.GetMimeType(extension);
|
||||
|
||||
public bool TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
=> MimeTypeProviders.TryDetermineCompression(mimeType, out preferCompression);
|
||||
}
|
||||
}
|
||||
30
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerPrefixCollection.cs
vendored
Normal file
30
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerPrefixCollection.cs
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal class HttpListenerPrefixCollection : List<string>
|
||||
{
|
||||
private readonly HttpListener _listener;
|
||||
|
||||
internal HttpListenerPrefixCollection(HttpListener listener)
|
||||
{
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
public new void Add(string uriPrefix)
|
||||
{
|
||||
ListenerPrefix.CheckUri(uriPrefix);
|
||||
if (Contains(uriPrefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.Add(uriPrefix);
|
||||
|
||||
if (_listener.IsListening)
|
||||
{
|
||||
EndPointManager.AddPrefix(uriPrefix, _listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
497
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerRequest.cs
vendored
Normal file
497
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerRequest.cs
vendored
Normal file
@@ -0,0 +1,497 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an HTTP Listener Request.
|
||||
/// </summary>
|
||||
internal sealed partial class HttpListenerRequest : IHttpRequest
|
||||
{
|
||||
private static readonly byte[] HttpStatus100 = WebServer.DefaultEncoding.GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
|
||||
private static readonly char[] Separators = { ' ' };
|
||||
|
||||
private readonly HttpConnection _connection;
|
||||
private CookieList? _cookies;
|
||||
private Stream? _inputStream;
|
||||
private bool _kaSet;
|
||||
private bool _keepAlive;
|
||||
|
||||
internal HttpListenerRequest(HttpListenerContext context)
|
||||
{
|
||||
_connection = context.Connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MIME accept types.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The accept types.
|
||||
/// </value>
|
||||
public string[] AcceptTypes { get; private set; } = Array.Empty<string>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Encoding ContentEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!HasEntityBody || ContentType == null)
|
||||
{
|
||||
return WebServer.DefaultEncoding;
|
||||
}
|
||||
|
||||
var charSet = HeaderUtility.GetCharset(ContentType);
|
||||
if (string.IsNullOrEmpty(charSet))
|
||||
{
|
||||
return WebServer.DefaultEncoding;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding(charSet);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return WebServer.DefaultEncoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public long ContentLength64 => long.TryParse(Headers[HttpHeaderNames.ContentLength], out var val) ? val : 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentType => Headers[HttpHeaderNames.ContentType];
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICookieCollection Cookies => _cookies ??= new CookieList();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasEntityBody => ContentLength64 > 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public NameValueCollection Headers { get; } = new ();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string HttpMethod { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpVerbs HttpVerb { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream InputStream => _inputStream ??= ContentLength64 > 0 ? _connection.GetRequestStream(ContentLength64) : Stream.Null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAuthenticated => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLocal => LocalEndPoint.Address?.Equals(RemoteEndPoint.Address) ?? true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSecureConnection => _connection.IsSecure;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool KeepAlive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_kaSet)
|
||||
{
|
||||
var cnc = Headers.GetValues(HttpHeaderNames.Connection);
|
||||
_keepAlive = ProtocolVersion < HttpVersion.Version11
|
||||
? cnc != null && cnc.Length == 1 && string.Compare(cnc[0], "keep-alive", StringComparison.OrdinalIgnoreCase) == 0
|
||||
: cnc == null || cnc.All(s => string.Compare(s, "close", StringComparison.OrdinalIgnoreCase) != 0);
|
||||
|
||||
_kaSet = true;
|
||||
}
|
||||
|
||||
return _keepAlive;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPEndPoint LocalEndPoint => _connection.LocalEndPoint;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version ProtocolVersion { get; private set; } = HttpVersion.Version11;
|
||||
|
||||
/// <inheritdoc />
|
||||
public NameValueCollection QueryString { get; } = new ();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string RawUrl { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPEndPoint RemoteEndPoint => _connection.RemoteEndPoint;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri Url { get; private set; } = WebServer.NullUri;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri? UrlReferrer { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UserAgent => Headers[HttpHeaderNames.UserAgent];
|
||||
|
||||
public string UserHostAddress => LocalEndPoint.ToString();
|
||||
|
||||
public string UserHostName => Headers[HttpHeaderNames.Host];
|
||||
|
||||
public string[] UserLanguages { get; private set; } = Array.Empty<string>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsWebSocketRequest
|
||||
=> HttpVerb == HttpVerbs.Get
|
||||
&& ProtocolVersion >= HttpVersion.Version11
|
||||
&& Headers.Contains(HttpHeaderNames.Upgrade, "websocket")
|
||||
&& Headers.Contains(HttpHeaderNames.Connection, "Upgrade");
|
||||
|
||||
internal void SetRequestLine(string req)
|
||||
{
|
||||
const string forbiddenMethodChars = "\"(),/:;<=>?@[\\]{}";
|
||||
|
||||
var parts = req.Split(Separators, 3);
|
||||
if (parts.Length != 3)
|
||||
{
|
||||
_connection.SetError("Invalid request line (parts).");
|
||||
return;
|
||||
}
|
||||
|
||||
HttpMethod = parts[0];
|
||||
foreach (var c in HttpMethod)
|
||||
{
|
||||
// See https://tools.ietf.org/html/rfc7230#section-3.2.6
|
||||
// for the list of allowed characters
|
||||
if (c < 32 || c >= 127 || forbiddenMethodChars.IndexOf(c) >= 0)
|
||||
{
|
||||
_connection.SetError("(Invalid verb)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HttpVerb = IsKnownHttpMethod(HttpMethod, out var verb) ? verb : HttpVerbs.Any;
|
||||
|
||||
RawUrl = parts[1];
|
||||
if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/", StringComparison.Ordinal))
|
||||
{
|
||||
_connection.SetError("Invalid request line (missing HTTP version).");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProtocolVersion = new Version(parts[2].Substring(5));
|
||||
|
||||
if (ProtocolVersion.Major < 1)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_connection.SetError("Invalid request line (could not parse HTTP version).");
|
||||
}
|
||||
}
|
||||
|
||||
internal void FinishInitialization()
|
||||
{
|
||||
var host = UserHostName;
|
||||
if (ProtocolVersion > HttpVersion.Version10 && string.IsNullOrEmpty(host))
|
||||
{
|
||||
_connection.SetError("Invalid host name");
|
||||
return;
|
||||
}
|
||||
|
||||
var rawUri = UriUtility.StringToAbsoluteUri(RawUrl.ToLowerInvariant());
|
||||
var path = rawUri?.PathAndQuery ?? RawUrl;
|
||||
|
||||
if (string.IsNullOrEmpty(host))
|
||||
{
|
||||
host = rawUri?.Host ?? UserHostAddress;
|
||||
}
|
||||
|
||||
var colonPos = host.LastIndexOf(':');
|
||||
var closedSquareBracketPos = host.LastIndexOf(']');
|
||||
if (colonPos >= 0 && closedSquareBracketPos < colonPos)
|
||||
{
|
||||
host = host.Substring(0, colonPos);
|
||||
}
|
||||
|
||||
// var baseUri = $"{(IsSecureConnection ? "https" : "http")}://{host}:{LocalEndPoint.Port}";
|
||||
var baseUri = $"http://{host}:{LocalEndPoint.Port}";
|
||||
|
||||
if (!Uri.TryCreate(baseUri + path, UriKind.Absolute, out var url))
|
||||
{
|
||||
_connection.SetError(WebUtility.HtmlEncode($"Invalid url: {baseUri}{path}"));
|
||||
return;
|
||||
}
|
||||
|
||||
Url = url;
|
||||
InitializeQueryString(Url.Query);
|
||||
|
||||
if (ContentLength64 == 0 && (HttpVerb == HttpVerbs.Post || HttpVerb == HttpVerbs.Put))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Compare(Headers["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
_connection.GetResponseStream().InternalWrite(HttpStatus100, 0, HttpStatus100.Length);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddHeader(string header)
|
||||
{
|
||||
var colon = header.IndexOf(':');
|
||||
if (colon == -1 || colon == 0)
|
||||
{
|
||||
_connection.SetError("Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
var name = header.Substring(0, colon).Trim();
|
||||
var val = header.Substring(colon + 1).Trim();
|
||||
|
||||
Headers.Set(name, val);
|
||||
|
||||
switch (name.ToLowerInvariant())
|
||||
{
|
||||
case "accept-language":
|
||||
UserLanguages = val.SplitByComma(); // yes, only split with a ','
|
||||
break;
|
||||
case "accept":
|
||||
AcceptTypes = val.SplitByComma(); // yes, only split with a ','
|
||||
break;
|
||||
case "content-length":
|
||||
Headers[HttpHeaderNames.ContentLength] = val.Trim();
|
||||
|
||||
if (ContentLength64 < 0)
|
||||
{
|
||||
_connection.SetError("Invalid Content-Length.");
|
||||
}
|
||||
|
||||
break;
|
||||
case "referer":
|
||||
try
|
||||
{
|
||||
UrlReferrer = new Uri(val);
|
||||
}
|
||||
catch
|
||||
{
|
||||
UrlReferrer = null;
|
||||
}
|
||||
|
||||
break;
|
||||
case "cookie":
|
||||
ParseCookies(val);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// returns true is the stream could be reused.
|
||||
internal bool FlushInput()
|
||||
{
|
||||
if (!HasEntityBody)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var length = 2048;
|
||||
if (ContentLength64 > 0)
|
||||
{
|
||||
length = (int)Math.Min(ContentLength64, length);
|
||||
}
|
||||
|
||||
var bytes = new byte[length];
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (InputStream.Read(bytes, 0, length) <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
_inputStream = null;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optimized for the following list of methods:
|
||||
// "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"
|
||||
// ***NOTE***: The verb parameter is NOT VALID upon exit if false is returned.
|
||||
private static bool IsKnownHttpMethod(string method, out HttpVerbs verb)
|
||||
{
|
||||
switch (method.Length)
|
||||
{
|
||||
case 3:
|
||||
switch (method[0])
|
||||
{
|
||||
case 'G':
|
||||
verb = HttpVerbs.Get;
|
||||
return method[1] == 'E' && method[2] == 'T';
|
||||
|
||||
case 'P':
|
||||
verb = HttpVerbs.Put;
|
||||
return method[1] == 'U' && method[2] == 'T';
|
||||
|
||||
default:
|
||||
verb = HttpVerbs.Any;
|
||||
return false;
|
||||
}
|
||||
|
||||
case 4:
|
||||
switch (method[0])
|
||||
{
|
||||
case 'H':
|
||||
verb = HttpVerbs.Head;
|
||||
return method[1] == 'E' && method[2] == 'A' && method[3] == 'D';
|
||||
|
||||
case 'P':
|
||||
verb = HttpVerbs.Post;
|
||||
return method[1] == 'O' && method[2] == 'S' && method[3] == 'T';
|
||||
|
||||
default:
|
||||
verb = HttpVerbs.Any;
|
||||
return false;
|
||||
}
|
||||
|
||||
case 5:
|
||||
verb = HttpVerbs.Patch;
|
||||
return method[0] == 'P'
|
||||
&& method[1] == 'A'
|
||||
&& method[2] == 'T'
|
||||
&& method[3] == 'C'
|
||||
&& method[4] == 'H';
|
||||
|
||||
case 6:
|
||||
verb = HttpVerbs.Delete;
|
||||
return method[0] == 'D'
|
||||
&& method[1] == 'E'
|
||||
&& method[2] == 'L'
|
||||
&& method[3] == 'E'
|
||||
&& method[4] == 'T'
|
||||
&& method[5] == 'E';
|
||||
|
||||
case 7:
|
||||
verb = HttpVerbs.Options;
|
||||
return method[0] == 'O'
|
||||
&& method[1] == 'P'
|
||||
&& method[2] == 'T'
|
||||
&& method[3] == 'I'
|
||||
&& method[4] == 'O'
|
||||
&& method[5] == 'N'
|
||||
&& method[6] == 'S';
|
||||
|
||||
default:
|
||||
verb = HttpVerbs.Any;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseCookies(string val)
|
||||
{
|
||||
_cookies ??= new CookieList();
|
||||
|
||||
var cookieStrings = val.SplitByAny(';', ',')
|
||||
.Where(x => !string.IsNullOrEmpty(x));
|
||||
Cookie? current = null;
|
||||
var version = 0;
|
||||
|
||||
foreach (var str in cookieStrings)
|
||||
{
|
||||
if (str.StartsWith("$Version", StringComparison.Ordinal))
|
||||
{
|
||||
version = int.Parse(str.Substring(str.IndexOf('=') + 1).Unquote(), CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (str.StartsWith("$Path", StringComparison.Ordinal) && current != null)
|
||||
{
|
||||
current.Path = str.Substring(str.IndexOf('=') + 1).Trim();
|
||||
}
|
||||
else if (str.StartsWith("$Domain", StringComparison.Ordinal) && current != null)
|
||||
{
|
||||
current.Domain = str.Substring(str.IndexOf('=') + 1).Trim();
|
||||
}
|
||||
else if (str.StartsWith("$Port", StringComparison.Ordinal) && current != null)
|
||||
{
|
||||
current.Port = str.Substring(str.IndexOf('=') + 1).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current != null)
|
||||
{
|
||||
_cookies.Add(current);
|
||||
}
|
||||
|
||||
current = new Cookie();
|
||||
var idx = str.IndexOf('=');
|
||||
if (idx > 0)
|
||||
{
|
||||
current.Name = str.Substring(0, idx).Trim();
|
||||
current.Value = str.Substring(idx + 1).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
current.Name = str.Trim();
|
||||
current.Value = string.Empty;
|
||||
}
|
||||
|
||||
current.Version = version;
|
||||
}
|
||||
}
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
_cookies.Add(current);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeQueryString(string query)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (query[0] == '?')
|
||||
{
|
||||
query = query.Substring(1);
|
||||
}
|
||||
|
||||
var components = query.Split('&');
|
||||
|
||||
foreach (var kv in components)
|
||||
{
|
||||
var pos = kv.IndexOf('=');
|
||||
if (pos == -1)
|
||||
{
|
||||
QueryString.Add(null, WebUtility.UrlDecode(kv));
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = WebUtility.UrlDecode(kv.Substring(0, pos));
|
||||
var val = WebUtility.UrlDecode(kv.Substring(pos + 1));
|
||||
|
||||
QueryString.Add(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
411
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerResponse.cs
vendored
Normal file
411
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerResponse.cs
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an HTTP Listener's response.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
internal sealed class HttpListenerResponse : IHttpResponse, IDisposable
|
||||
{
|
||||
private readonly HttpConnection _connection;
|
||||
private readonly HttpListenerRequest _request;
|
||||
private readonly string _id;
|
||||
private bool _disposed;
|
||||
private string _contentType = MimeType.Html; // Same default value as Microsoft's implementation
|
||||
private CookieList? _cookies;
|
||||
private bool _keepAlive;
|
||||
private ResponseStream? _outputStream;
|
||||
private int _statusCode = 200;
|
||||
private bool _chunked;
|
||||
|
||||
internal HttpListenerResponse(HttpListenerContext context)
|
||||
{
|
||||
_request = context.HttpListenerRequest;
|
||||
_connection = context.Connection;
|
||||
_id = context.Id;
|
||||
_keepAlive = context.Request.KeepAlive;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Encoding? ContentEncoding { get; set; } = WebServer.DefaultEncoding;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ObjectDisposedException">This instance has been disposed.</exception>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and headers were already sent.</exception>
|
||||
public long ContentLength64
|
||||
{
|
||||
get => Headers.ContainsKey(HttpHeaderNames.ContentLength) && long.TryParse(Headers[HttpHeaderNames.ContentLength], out var val) ? val : 0;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
if (value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Must be >= 0");
|
||||
}
|
||||
|
||||
Headers[HttpHeaderNames.ContentLength] = value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ObjectDisposedException">This instance has been disposed.</exception>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and headers were already sent.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">This property is being set to the empty string.</exception>
|
||||
public string ContentType
|
||||
{
|
||||
get => _contentType;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
_contentType = Validate.NotNullOrEmpty(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICookieCollection Cookies => CookieCollection;
|
||||
|
||||
/// <inheritdoc />
|
||||
public WebHeaderCollection Headers { get; } = new WebHeaderCollection();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool KeepAlive
|
||||
{
|
||||
get => _keepAlive;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
_keepAlive = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OutputStream => _outputStream ??= _connection.GetResponseStream();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version ProtocolVersion => _request.ProtocolVersion;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ObjectDisposedException">This instance has been disposed.</exception>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and headers were already sent.</exception>
|
||||
public bool SendChunked
|
||||
{
|
||||
get => _chunked;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
_chunked = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ObjectDisposedException">This instance has been disposed.</exception>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and headers were already sent.</exception>
|
||||
public int StatusCode
|
||||
{
|
||||
get => _statusCode;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
if (value < 100 || value > 999)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(StatusCode), "StatusCode must be between 100 and 999.");
|
||||
}
|
||||
|
||||
_statusCode = value;
|
||||
StatusDescription = HttpListenerResponseHelper.GetStatusDescription(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string StatusDescription { get; set; } = "OK";
|
||||
|
||||
internal CookieList CookieCollection
|
||||
{
|
||||
get => _cookies ??= new CookieList();
|
||||
set => _cookies = value;
|
||||
}
|
||||
|
||||
internal bool HeadersSent { get; set; }
|
||||
|
||||
void IDisposable.Dispose() => Close(true);
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCookie(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
}
|
||||
|
||||
if (_cookies != null)
|
||||
{
|
||||
if (_cookies.Any(c => cookie.Name == c.Name && cookie.Domain == c.Domain && cookie.Path == c.Path))
|
||||
{
|
||||
throw new ArgumentException("The cookie already exists.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_cookies = new CookieList();
|
||||
}
|
||||
|
||||
_cookies.Add(cookie);
|
||||
}
|
||||
|
||||
internal MemoryStream SendHeaders(bool closing)
|
||||
{
|
||||
if (_contentType != null)
|
||||
{
|
||||
var contentTypeValue = _contentType.IndexOf("charset=", StringComparison.Ordinal) == -1 && ContentEncoding is not null
|
||||
? $"{_contentType}; charset={ContentEncoding.WebName}"
|
||||
: _contentType;
|
||||
|
||||
Headers.Add(HttpHeaderNames.ContentType, contentTypeValue);
|
||||
}
|
||||
|
||||
if (Headers[HttpHeaderNames.Server] == null)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.Server, WebServer.Signature);
|
||||
}
|
||||
|
||||
if (Headers[HttpHeaderNames.Date] == null)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.Date, HttpDate.Format(DateTime.UtcNow));
|
||||
}
|
||||
|
||||
// HTTP did not support chunked transfer encoding before version 1.1;
|
||||
// besides, there's no point in setting transfer encoding at all without a request body.
|
||||
if (closing || ProtocolVersion < HttpVersion.Version11)
|
||||
{
|
||||
_chunked = false;
|
||||
}
|
||||
|
||||
// Was content length set to a valid value, AND chunked encoding not set?
|
||||
// Note that this does not mean that a response body _will_ be sent
|
||||
// as this could be the response to a HEAD request.
|
||||
var haveContentLength = !_chunked
|
||||
&& Headers.ContainsKey(HttpHeaderNames.ContentLength)
|
||||
&& long.TryParse(Headers[HttpHeaderNames.ContentLength], NumberStyles.None, CultureInfo.InvariantCulture, out var contentLength)
|
||||
&& contentLength >= 0L;
|
||||
|
||||
if (!haveContentLength)
|
||||
{
|
||||
// Content length could have been set to an invalid value (e.g. "-1")
|
||||
// so we must either force it to 0, or remove the header completely.
|
||||
if (closing)
|
||||
{
|
||||
// Content length was not explicitly set to a valid value,
|
||||
// and there is no request body.
|
||||
Headers[HttpHeaderNames.ContentLength] = "0";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Content length was not explicitly set to a valid value,
|
||||
// and we're going to send a request body.
|
||||
// - Remove possibly invalid Content-Length header
|
||||
// - Enable chunked transfer encoding for HTTP 1.1
|
||||
Headers.Remove(HttpHeaderNames.ContentLength);
|
||||
if (ProtocolVersion >= HttpVersion.Version11)
|
||||
{
|
||||
_chunked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_chunked)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.TransferEncoding, "chunked");
|
||||
}
|
||||
|
||||
//// Apache forces closing the connection for these status codes:
|
||||
//// HttpStatusCode.BadRequest 400
|
||||
//// HttpStatusCode.RequestTimeout 408
|
||||
//// HttpStatusCode.LengthRequired 411
|
||||
//// HttpStatusCode.RequestEntityTooLarge 413
|
||||
//// HttpStatusCode.RequestUriTooLong 414
|
||||
//// HttpStatusCode.InternalServerError 500
|
||||
//// HttpStatusCode.ServiceUnavailable 503
|
||||
var reuses = _connection.Reuses;
|
||||
var keepAlive = _statusCode switch {
|
||||
400 => false,
|
||||
408 => false,
|
||||
411 => false,
|
||||
413 => false,
|
||||
414 => false,
|
||||
500 => false,
|
||||
503 => false,
|
||||
_ => _keepAlive && reuses < 100
|
||||
};
|
||||
|
||||
_keepAlive = keepAlive;
|
||||
if (keepAlive)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.Connection, "keep-alive");
|
||||
if (ProtocolVersion >= HttpVersion.Version11)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.KeepAlive, $"timeout=15,max={100 - reuses}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.Connection, "close");
|
||||
}
|
||||
|
||||
return WriteHeaders();
|
||||
}
|
||||
|
||||
private static void AppendSetCookieHeader(StringBuilder sb, Cookie cookie)
|
||||
{
|
||||
if (cookie.Name.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = sb.Append("Set-Cookie: ");
|
||||
|
||||
if (cookie.Version > 0)
|
||||
{
|
||||
_ = sb.Append("Version=").Append(cookie.Version).Append("; ");
|
||||
}
|
||||
|
||||
_ = sb
|
||||
.Append(cookie.Name)
|
||||
.Append('=')
|
||||
.Append(cookie.Value);
|
||||
|
||||
if (cookie.Expires != DateTime.MinValue)
|
||||
{
|
||||
_ = sb
|
||||
.Append("; Expires=")
|
||||
.Append(HttpDate.Format(cookie.Expires));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cookie.Path))
|
||||
{
|
||||
_ = sb.Append("; Path=").Append(QuotedString(cookie, cookie.Path));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cookie.Domain))
|
||||
{
|
||||
_ = sb.Append("; Domain=").Append(QuotedString(cookie, cookie.Domain));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cookie.Port))
|
||||
{
|
||||
_ = sb.Append("; Port=").Append(cookie.Port);
|
||||
}
|
||||
|
||||
if (cookie.Secure)
|
||||
{
|
||||
_ = sb.Append("; Secure");
|
||||
}
|
||||
|
||||
if (cookie.HttpOnly)
|
||||
{
|
||||
_ = sb.Append("; HttpOnly");
|
||||
}
|
||||
|
||||
_ = sb.Append("\r\n");
|
||||
}
|
||||
|
||||
private static string QuotedString(Cookie cookie, string value)
|
||||
=> cookie.Version == 0 || value.IsToken() ? value : "\"" + value.Replace("\"", "\\\"") + "\"";
|
||||
|
||||
private void Close(bool force)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
_connection.Close(force);
|
||||
}
|
||||
|
||||
private string GetHeaderData()
|
||||
{
|
||||
var sb = new StringBuilder()
|
||||
.Append("HTTP/")
|
||||
.Append(ProtocolVersion)
|
||||
.Append(' ')
|
||||
.Append(_statusCode)
|
||||
.Append(' ')
|
||||
.Append(StatusDescription)
|
||||
.Append("\r\n");
|
||||
|
||||
foreach (var key in Headers.AllKeys.Where(x => x != "Set-Cookie"))
|
||||
{
|
||||
_ = sb
|
||||
.Append(key)
|
||||
.Append(": ")
|
||||
.Append(Headers[key])
|
||||
.Append("\r\n");
|
||||
}
|
||||
|
||||
if (_cookies != null)
|
||||
{
|
||||
foreach (var cookie in _cookies)
|
||||
{
|
||||
AppendSetCookieHeader(sb, cookie);
|
||||
}
|
||||
}
|
||||
|
||||
if (Headers.ContainsKey(HttpHeaderNames.SetCookie))
|
||||
{
|
||||
foreach (var cookie in CookieList.Parse(Headers[HttpHeaderNames.SetCookie]))
|
||||
{
|
||||
AppendSetCookieHeader(sb, cookie);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.Append("\r\n").ToString();
|
||||
}
|
||||
|
||||
private MemoryStream WriteHeaders()
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
var data = WebServer.DefaultEncoding.GetBytes(GetHeaderData());
|
||||
var preamble = WebServer.DefaultEncoding.GetPreamble();
|
||||
stream.Write(preamble, 0, preamble.Length);
|
||||
stream.Write(data, 0, data.Length);
|
||||
|
||||
_outputStream ??= _connection.GetResponseStream();
|
||||
|
||||
// Assumes that the ms was at position 0
|
||||
stream.Position = preamble.Length;
|
||||
HeadersSent = true;
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
private void EnsureCanChangeHeaders()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(_id);
|
||||
}
|
||||
|
||||
if (HeadersSent)
|
||||
{
|
||||
throw new InvalidOperationException("Header values cannot be changed after headers are sent.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerResponseHelper.cs
vendored
Normal file
55
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerResponseHelper.cs
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal static class HttpListenerResponseHelper
|
||||
{
|
||||
internal static string GetStatusDescription(int code) => code switch {
|
||||
100 => "Continue",
|
||||
101 => "Switching Protocols",
|
||||
102 => "Processing",
|
||||
200 => "OK",
|
||||
201 => "Created",
|
||||
202 => "Accepted",
|
||||
203 => "Non-Authoritative Information",
|
||||
204 => "No Content",
|
||||
205 => "Reset Content",
|
||||
206 => "Partial Content",
|
||||
207 => "Multi-Status",
|
||||
300 => "Multiple Choices",
|
||||
301 => "Moved Permanently",
|
||||
302 => "Found",
|
||||
303 => "See Other",
|
||||
304 => "Not Modified",
|
||||
305 => "Use Proxy",
|
||||
307 => "Temporary Redirect",
|
||||
400 => "Bad Request",
|
||||
401 => "Unauthorized",
|
||||
402 => "Payment Required",
|
||||
403 => "Forbidden",
|
||||
404 => "Not Found",
|
||||
405 => "Method Not Allowed",
|
||||
406 => "Not Acceptable",
|
||||
407 => "Proxy Authentication Required",
|
||||
408 => "Request Timeout",
|
||||
409 => "Conflict",
|
||||
410 => "Gone",
|
||||
411 => "Length Required",
|
||||
412 => "Precondition Failed",
|
||||
413 => "Request Entity Too Large",
|
||||
414 => "Request-Uri Too Long",
|
||||
415 => "Unsupported Media Type",
|
||||
416 => "Requested Range Not Satisfiable",
|
||||
417 => "Expectation Failed",
|
||||
422 => "Unprocessable Entity",
|
||||
423 => "Locked",
|
||||
424 => "Failed Dependency",
|
||||
500 => "Internal Server Error",
|
||||
501 => "Not Implemented",
|
||||
502 => "Bad Gateway",
|
||||
503 => "Service Unavailable",
|
||||
504 => "Gateway Timeout",
|
||||
505 => "Http Version Not Supported",
|
||||
507 => "Insufficient Storage",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
35
Vendor/EmbedIO-3.5.2/Net/Internal/ListenerPrefix.cs
vendored
Normal file
35
Vendor/EmbedIO-3.5.2/Net/Internal/ListenerPrefix.cs
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal sealed class ListenerPrefix
|
||||
{
|
||||
public ListenerPrefix(string uri)
|
||||
{
|
||||
var parsedUri = ListenerUri.Parse(uri);
|
||||
Secure = parsedUri.Secure;
|
||||
Host = parsedUri.Host;
|
||||
Port = parsedUri.Port;
|
||||
Path = parsedUri.Path;
|
||||
}
|
||||
|
||||
public HttpListener? Listener { get; set; }
|
||||
|
||||
public bool Secure { get; }
|
||||
|
||||
public string Host { get; }
|
||||
|
||||
public int Port { get; }
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public static void CheckUri(string uri)
|
||||
{
|
||||
_ = ListenerUri.Parse(uri);
|
||||
}
|
||||
|
||||
public bool IsValid() => Path.IndexOf('%') == -1 && Path.IndexOf("//", StringComparison.Ordinal) == -1;
|
||||
|
||||
public override string ToString() => $"{Host}:{Port} ({(Secure ? "Secure" : "Insecure")}";
|
||||
}
|
||||
}
|
||||
91
Vendor/EmbedIO-3.5.2/Net/Internal/ListenerUri.cs
vendored
Normal file
91
Vendor/EmbedIO-3.5.2/Net/Internal/ListenerUri.cs
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal class ListenerUri
|
||||
{
|
||||
private ListenerUri(bool secure,
|
||||
string host,
|
||||
int port,
|
||||
string path)
|
||||
{
|
||||
Secure = secure;
|
||||
Host = host;
|
||||
Port = port;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public bool Secure { get; private set; }
|
||||
|
||||
public string Host { get; private set; }
|
||||
|
||||
public int Port { get; private set; }
|
||||
|
||||
public string Path { get; private set; }
|
||||
|
||||
public static ListenerUri Parse(string uri)
|
||||
{
|
||||
bool secure;
|
||||
int port;
|
||||
int parsingPosition;
|
||||
if (uri.StartsWith("http://"))
|
||||
{
|
||||
secure = false;
|
||||
port = 80;
|
||||
parsingPosition = "http://".Length;
|
||||
}
|
||||
else if (uri.StartsWith("https://"))
|
||||
{
|
||||
secure = true;
|
||||
port = 443;
|
||||
parsingPosition = "https://".Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Only 'http' and 'https' schemes are supported.");
|
||||
}
|
||||
|
||||
var startOfPath = uri.IndexOf('/', parsingPosition);
|
||||
if (startOfPath == -1)
|
||||
{
|
||||
throw new ArgumentException("Path should end in '/'.");
|
||||
}
|
||||
|
||||
var hostWithPort = uri.Substring(parsingPosition, startOfPath - parsingPosition);
|
||||
|
||||
var startOfPortWithColon = hostWithPort.LastIndexOf(':');
|
||||
if (startOfPortWithColon > -1)
|
||||
{
|
||||
startOfPortWithColon += parsingPosition;
|
||||
}
|
||||
|
||||
var endOfIpV6 = hostWithPort.LastIndexOf(']');
|
||||
if (endOfIpV6 > -1)
|
||||
{
|
||||
endOfIpV6 += parsingPosition;
|
||||
}
|
||||
|
||||
if (endOfIpV6 > startOfPortWithColon)
|
||||
{
|
||||
startOfPortWithColon = -1;
|
||||
}
|
||||
|
||||
if (startOfPortWithColon != -1 && startOfPortWithColon < startOfPath)
|
||||
{
|
||||
if (!int.TryParse(uri.Substring(startOfPortWithColon + 1, startOfPath - startOfPortWithColon - 1), out port) || port <= 0 || port >= 65535)
|
||||
{
|
||||
throw new ArgumentException("Invalid port.");
|
||||
}
|
||||
}
|
||||
|
||||
var host = uri.Substring(parsingPosition, (startOfPortWithColon == -1 ? startOfPath : startOfPortWithColon) - parsingPosition);
|
||||
var path = uri.Substring(startOfPath);
|
||||
if (!path.EndsWith("/"))
|
||||
{
|
||||
throw new ArgumentException("Path should end in '/'.");
|
||||
}
|
||||
|
||||
return new ListenerUri(secure, host, port, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Vendor/EmbedIO-3.5.2/Net/Internal/NetExtensions.cs
vendored
Normal file
44
Vendor/EmbedIO-3.5.2/Net/Internal/NetExtensions.cs
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents some System.NET custom extensions.
|
||||
/// </summary>
|
||||
internal static class NetExtensions
|
||||
{
|
||||
internal static byte[] ToByteArray(this ushort value, Endianness order)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
if (!order.IsHostOrder())
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArray(this ulong value, Endianness order)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
if (!order.IsHostOrder())
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
internal static byte[] ToHostOrder(this byte[] source, Endianness sourceOrder)
|
||||
=> source.Length < 1 ? source
|
||||
: sourceOrder.IsHostOrder() ? source
|
||||
: source.Reverse().ToArray();
|
||||
|
||||
// true: !(true ^ true) or !(false ^ false)
|
||||
// false: !(true ^ false) or !(false ^ true)
|
||||
private static bool IsHostOrder(this Endianness order)
|
||||
=> !(BitConverter.IsLittleEndian ^ (order == Endianness.Little));
|
||||
}
|
||||
}
|
||||
143
Vendor/EmbedIO-3.5.2/Net/Internal/RequestStream.cs
vendored
Normal file
143
Vendor/EmbedIO-3.5.2/Net/Internal/RequestStream.cs
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal class RequestStream : Stream
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly byte[] _buffer;
|
||||
private int _offset;
|
||||
private int _length;
|
||||
private long _remainingBody;
|
||||
|
||||
internal RequestStream(Stream stream, byte[] buffer, int offset, int length, long contentLength = -1)
|
||||
{
|
||||
_stream = stream;
|
||||
_buffer = buffer;
|
||||
_offset = offset;
|
||||
_length = length;
|
||||
_remainingBody = contentLength;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read([In, Out] byte[] buffer, int offset, int count)
|
||||
{
|
||||
// Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0
|
||||
var nread = FillFromBuffer(buffer, offset, count);
|
||||
|
||||
if (nread == -1)
|
||||
{
|
||||
// No more bytes available (Content-Length)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nread > 0)
|
||||
{
|
||||
return nread;
|
||||
}
|
||||
|
||||
nread = _stream.Read(buffer, offset, count);
|
||||
|
||||
if (nread > 0 && _remainingBody > 0)
|
||||
{
|
||||
_remainingBody -= nread;
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
|
||||
// Returns 0 if we can keep reading from the base stream,
|
||||
// > 0 if we read something from the buffer.
|
||||
// -1 if we had a content length set and we finished reading that many bytes.
|
||||
private int FillFromBuffer(byte[] buffer, int off, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (off < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(off), "< 0");
|
||||
}
|
||||
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count), "< 0");
|
||||
}
|
||||
|
||||
var len = buffer.Length;
|
||||
|
||||
if (off > len)
|
||||
{
|
||||
throw new ArgumentException("destination offset is beyond array size");
|
||||
}
|
||||
|
||||
if (off > len - count)
|
||||
{
|
||||
throw new ArgumentException("Reading would overrun buffer");
|
||||
}
|
||||
|
||||
if (_remainingBody == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var size = Math.Min(_length, count);
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
size = (int) Math.Min(size, _remainingBody);
|
||||
}
|
||||
|
||||
if (_offset > _buffer.Length - size)
|
||||
{
|
||||
size = Math.Min(size, _buffer.Length - _offset);
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(_buffer, _offset, buffer, off, size);
|
||||
_offset += size;
|
||||
_length -= size;
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
_remainingBody -= size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
190
Vendor/EmbedIO-3.5.2/Net/Internal/ResponseStream.cs
vendored
Normal file
190
Vendor/EmbedIO-3.5.2/Net/Internal/ResponseStream.cs
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal class ResponseStream : Stream
|
||||
{
|
||||
private static readonly byte[] CrLf = { 13, 10 };
|
||||
private readonly object _headersSyncRoot = new ();
|
||||
|
||||
private readonly Stream _stream;
|
||||
private readonly HttpListenerResponse _response;
|
||||
private readonly bool _ignoreErrors;
|
||||
private bool _disposed;
|
||||
private bool _trailerSent;
|
||||
|
||||
internal ResponseStream(Stream stream, HttpListenerResponse response, bool ignoreErrors)
|
||||
{
|
||||
_response = response;
|
||||
_ignoreErrors = ignoreErrors;
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRead => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanSeek => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanWrite => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(ResponseStream));
|
||||
}
|
||||
|
||||
byte[] bytes;
|
||||
var ms = GetHeaders(false);
|
||||
var chunked = _response.SendChunked;
|
||||
|
||||
if (ms != null)
|
||||
{
|
||||
var start = ms.Position; // After the possible preamble for the encoding
|
||||
ms.Position = ms.Length;
|
||||
if (chunked)
|
||||
{
|
||||
bytes = GetChunkSizeBytes(count, false);
|
||||
ms.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
var newCount = Math.Min(count, 16384 - (int)ms.Position + (int)start);
|
||||
ms.Write(buffer, offset, newCount);
|
||||
count -= newCount;
|
||||
offset += newCount;
|
||||
InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start));
|
||||
ms.SetLength(0);
|
||||
ms.Capacity = 0; // 'dispose' the buffer in ms.
|
||||
}
|
||||
else if (chunked)
|
||||
{
|
||||
bytes = GetChunkSizeBytes(count, false);
|
||||
InternalWrite(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
InternalWrite(buffer, offset, count);
|
||||
}
|
||||
|
||||
if (chunked)
|
||||
{
|
||||
InternalWrite(CrLf, 0, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Read([In, Out] byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
internal void InternalWrite(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_ignoreErrors)
|
||||
{
|
||||
try
|
||||
{
|
||||
_stream.Write(buffer, offset, count);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_stream.Write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var ms = GetHeaders(true);
|
||||
var chunked = _response.SendChunked;
|
||||
|
||||
if (_stream.CanWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes;
|
||||
if (ms != null)
|
||||
{
|
||||
var start = ms.Position;
|
||||
if (chunked && !_trailerSent)
|
||||
{
|
||||
bytes = GetChunkSizeBytes(0, true);
|
||||
ms.Position = ms.Length;
|
||||
ms.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start));
|
||||
_trailerSent = true;
|
||||
}
|
||||
else if (chunked && !_trailerSent)
|
||||
{
|
||||
bytes = GetChunkSizeBytes(0, true);
|
||||
InternalWrite(bytes, 0, bytes.Length);
|
||||
_trailerSent = true;
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Ignore error due to connection reset by peer
|
||||
}
|
||||
}
|
||||
|
||||
_response.Close();
|
||||
}
|
||||
|
||||
private static byte[] GetChunkSizeBytes(int size, bool final) => WebServer.DefaultEncoding.GetBytes($"{size:x}\r\n{(final ? "\r\n" : string.Empty)}");
|
||||
|
||||
private MemoryStream? GetHeaders(bool closing)
|
||||
{
|
||||
lock (_headersSyncRoot)
|
||||
{
|
||||
return _response.HeadersSent ? null : _response.SendHeaders(closing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Vendor/EmbedIO-3.5.2/Net/Internal/StringExtensions.cs
vendored
Normal file
77
Vendor/EmbedIO-3.5.2/Net/Internal/StringExtensions.cs
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal static class StringExtensions
|
||||
{
|
||||
private const string TokenSpecialChars = "()<>@,;:\\\"/[]?={} \t";
|
||||
|
||||
internal static bool IsToken(this string @this)
|
||||
=> @this.All(c => c >= 0x20 && c < 0x7f && TokenSpecialChars.IndexOf(c) < 0);
|
||||
|
||||
internal static IEnumerable<string> SplitHeaderValue(this string @this, bool useCookieSeparators)
|
||||
{
|
||||
var len = @this.Length;
|
||||
|
||||
var buff = new StringBuilder(32);
|
||||
var escaped = false;
|
||||
var quoted = false;
|
||||
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var c = @this[i];
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
if (escaped)
|
||||
{
|
||||
escaped = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
quoted = !quoted;
|
||||
}
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
if (i < len - 1 && @this[i + 1] == '"')
|
||||
{
|
||||
escaped = true;
|
||||
}
|
||||
}
|
||||
else if (c == ',' || (useCookieSeparators && c == ';'))
|
||||
{
|
||||
if (!quoted)
|
||||
{
|
||||
yield return buff.ToString();
|
||||
buff.Length = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
_ = buff.Append(c);
|
||||
}
|
||||
|
||||
if (buff.Length > 0)
|
||||
{
|
||||
yield return buff.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Unquote(this string str)
|
||||
{
|
||||
var start = str.IndexOf('\"');
|
||||
var end = str.LastIndexOf('\"');
|
||||
|
||||
if (start >= 0 && end >= 0)
|
||||
{
|
||||
str = str.Substring(start + 1, end - 1);
|
||||
}
|
||||
|
||||
return str.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Vendor/EmbedIO-3.5.2/Net/Internal/SystemCookieCollection.cs
vendored
Normal file
56
Vendor/EmbedIO-3.5.2/Net/Internal/SystemCookieCollection.cs
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wrapper for <c>System.Net.CookieCollection</c>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ICookieCollection" />
|
||||
internal sealed class SystemCookieCollection : ICookieCollection
|
||||
{
|
||||
private readonly CookieCollection _collection;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemCookieCollection"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The cookie collection.</param>
|
||||
public SystemCookieCollection(CookieCollection collection)
|
||||
{
|
||||
_collection = collection;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _collection.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSynchronized => _collection.IsSynchronized;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object SyncRoot => _collection.SyncRoot;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Cookie? this[string name] => _collection[name];
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator<Cookie> IEnumerable<Cookie>.GetEnumerator() => _collection.OfType<Cookie>().GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator GetEnumerator() => _collection.GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(Array array, int index) => _collection.CopyTo(array, index);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(Cookie[] array, int index) => _collection.CopyTo(array, index);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(Cookie cookie) => _collection.Add(cookie);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(Cookie cookie) => _collection.OfType<Cookie>().Contains(cookie);
|
||||
}
|
||||
}
|
||||
125
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpContext.cs
vendored
Normal file
125
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpContext.cs
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Authentication;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.Sessions;
|
||||
using EmbedIO.Utilities;
|
||||
using EmbedIO.WebSockets;
|
||||
using EmbedIO.WebSockets.Internal;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal sealed class SystemHttpContext : IHttpContextImpl
|
||||
{
|
||||
private readonly System.Net.HttpListenerContext _context;
|
||||
|
||||
private readonly TimeKeeper _ageKeeper = new ();
|
||||
|
||||
private readonly Stack<Action<IHttpContext>> _closeCallbacks = new ();
|
||||
|
||||
private bool _closed;
|
||||
|
||||
public SystemHttpContext(System.Net.HttpListenerContext context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
Request = new SystemHttpRequest(_context);
|
||||
User = _context.User ?? Auth.NoUser;
|
||||
Response = new SystemHttpResponse(_context);
|
||||
Id = UniqueIdGenerator.GetNext();
|
||||
LocalEndPoint = Request.LocalEndPoint;
|
||||
RemoteEndPoint = Request.RemoteEndPoint;
|
||||
Route = RouteMatch.None;
|
||||
Session = SessionProxy.None;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
public long Age => _ageKeeper.ElapsedTime;
|
||||
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
public IHttpRequest Request { get; }
|
||||
|
||||
public RouteMatch Route { get; set; }
|
||||
|
||||
public string RequestedPath => Route.SubPath ?? string.Empty; // It will never be empty, because modules are matched via base routes - this is just to silence a warning.
|
||||
|
||||
public IHttpResponse Response { get; }
|
||||
|
||||
public IPrincipal User { get; set; }
|
||||
|
||||
public ISessionProxy Session { get; set; }
|
||||
|
||||
public bool SupportCompressedRequests { get; set; }
|
||||
|
||||
public IDictionary<object, object> Items { get; } = new Dictionary<object, object>();
|
||||
|
||||
public bool IsHandled { get; private set; }
|
||||
|
||||
public MimeTypeProviderStack MimeTypeProviders { get; } = new MimeTypeProviderStack();
|
||||
|
||||
public void SetHandled() => IsHandled = true;
|
||||
|
||||
public void OnClose(Action<IHttpContext> callback)
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
throw new InvalidOperationException("HTTP context has already been closed.");
|
||||
}
|
||||
|
||||
_closeCallbacks.Push(Validate.NotNull(nameof(callback), callback));
|
||||
}
|
||||
|
||||
public async Task<IWebSocketContext> AcceptWebSocketAsync(
|
||||
IEnumerable<string> requestedProtocols,
|
||||
string acceptedProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var context = await _context.AcceptWebSocketAsync(
|
||||
acceptedProtocol.NullIfEmpty(), // Empty string would throw; use null to signify "no subprotocol" here.
|
||||
receiveBufferSize,
|
||||
keepAliveInterval)
|
||||
.ConfigureAwait(false);
|
||||
return new WebSocketContext(this, context.SecWebSocketVersion, requestedProtocols, acceptedProtocol, new SystemWebSocket(context.WebSocket), cancellationToken);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_closed = true;
|
||||
|
||||
// Always close the response stream no matter what.
|
||||
Response.Close();
|
||||
|
||||
foreach (var callback in _closeCallbacks)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Log("HTTP context", "[Id] Exception thrown by a HTTP context close callback.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMimeType(string extension)
|
||||
=> MimeTypeProviders.GetMimeType(extension);
|
||||
|
||||
public bool TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
=> MimeTypeProviders.TryDetermineCompression(mimeType, out preferCompression);
|
||||
}
|
||||
}
|
||||
71
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpListener.cs
vendored
Normal file
71
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpListener.cs
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wrapper for Microsoft HTTP Listener.
|
||||
/// </summary>
|
||||
internal class SystemHttpListener : IHttpListener
|
||||
{
|
||||
private readonly System.Net.HttpListener _httpListener;
|
||||
|
||||
public SystemHttpListener(System.Net.HttpListener httpListener)
|
||||
{
|
||||
_httpListener = httpListener;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IgnoreWriteExceptions
|
||||
{
|
||||
get => _httpListener.IgnoreWriteExceptions;
|
||||
set => _httpListener.IgnoreWriteExceptions = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<string> Prefixes => _httpListener.Prefixes.ToList();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsListening => _httpListener.IsListening;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name { get; } = "Microsoft HTTP Listener";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start() => _httpListener.Start();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Stop() => _httpListener.Stop();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddPrefix(string urlPrefix) => _httpListener.Prefixes.Add(urlPrefix);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IHttpContextImpl> GetContextAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// System.Net.HttpListener.GetContextAsync may throw ObjectDisposedException
|
||||
// when stopping a WebServer. This has been observed on Mono 5.20.1.19
|
||||
// on Raspberry Pi, but the fact remains that the method does not take
|
||||
// a CancellationToken as parameter, and WebServerBase<>.RunAsync counts on it.
|
||||
System.Net.HttpListenerContext context;
|
||||
try
|
||||
{
|
||||
context = await _httpListener.GetContextAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new OperationCanceledException(
|
||||
"Probable cancellation detected by catching an exception in System.Net.HttpListener.GetContextAsync",
|
||||
e,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
return new SystemHttpContext(context);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose() => ((IDisposable)_httpListener)?.Dispose();
|
||||
}
|
||||
}
|
||||
121
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpRequest.cs
vendored
Normal file
121
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpRequest.cs
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wrapper for HttpListenerContext.Request.
|
||||
/// </summary>
|
||||
/// <seealso cref="EmbedIO.IHttpRequest" />
|
||||
public class SystemHttpRequest : IHttpRequest
|
||||
{
|
||||
private readonly System.Net.HttpListenerRequest _request;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemHttpRequest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
public SystemHttpRequest(System.Net.HttpListenerContext context)
|
||||
{
|
||||
_request = context.Request;
|
||||
_ = Enum.TryParse<HttpVerbs>(_request.HttpMethod.Trim(), true, out var verb);
|
||||
HttpVerb = verb;
|
||||
Cookies = new SystemCookieCollection(_request.Cookies);
|
||||
LocalEndPoint = _request.LocalEndPoint!;
|
||||
RemoteEndPoint = _request.RemoteEndPoint!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NameValueCollection Headers => _request.Headers;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version ProtocolVersion => _request.ProtocolVersion;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool KeepAlive => _request.KeepAlive;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICookieCollection Cookies { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string RawUrl => _request.RawUrl;
|
||||
|
||||
/// <inheritdoc />
|
||||
public NameValueCollection QueryString => _request.QueryString;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string HttpMethod => _request.HttpMethod;
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpVerbs HttpVerb { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri Url => _request.Url;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasEntityBody => _request.HasEntityBody;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream InputStream => _request.InputStream;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Encoding ContentEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_request.HasEntityBody || _request.ContentType == null)
|
||||
{
|
||||
return WebServer.DefaultEncoding;
|
||||
}
|
||||
|
||||
var charSet = HeaderUtility.GetCharset(ContentType);
|
||||
if (string.IsNullOrEmpty(charSet))
|
||||
{
|
||||
return WebServer.DefaultEncoding;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding(charSet);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return WebServer.DefaultEncoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSecureConnection => _request.IsSecureConnection;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLocal => _request.IsLocal;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UserAgent => _request.UserAgent;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsWebSocketRequest => _request.IsWebSocketRequest;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentType => _request.ContentType;
|
||||
|
||||
/// <inheritdoc />
|
||||
public long ContentLength64 => _request.ContentLength64;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAuthenticated => _request.IsAuthenticated;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri? UrlReferrer => _request.UrlReferrer;
|
||||
}
|
||||
}
|
||||
97
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpResponse.cs
vendored
Normal file
97
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpResponse.cs
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wrapper for HttpListenerContext.Response.
|
||||
/// </summary>
|
||||
/// <seealso cref="IHttpResponse" />
|
||||
public class SystemHttpResponse : IHttpResponse
|
||||
{
|
||||
private readonly System.Net.HttpListenerResponse _response;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemHttpResponse"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
public SystemHttpResponse(System.Net.HttpListenerContext context)
|
||||
{
|
||||
_response = context.Response;
|
||||
Cookies = new SystemCookieCollection(_response.Cookies);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public WebHeaderCollection Headers => _response.Headers;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int StatusCode
|
||||
{
|
||||
get => _response.StatusCode;
|
||||
set => _response.StatusCode = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public long ContentLength64
|
||||
{
|
||||
get => _response.ContentLength64;
|
||||
set => _response.ContentLength64 = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentType
|
||||
{
|
||||
get => _response.ContentType;
|
||||
set => _response.ContentType = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OutputStream => _response.OutputStream;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICookieCollection Cookies { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Encoding? ContentEncoding
|
||||
{
|
||||
get => _response.ContentEncoding;
|
||||
set => _response.ContentEncoding = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool KeepAlive
|
||||
{
|
||||
get => _response.KeepAlive;
|
||||
set => _response.KeepAlive = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SendChunked
|
||||
{
|
||||
get => _response.SendChunked;
|
||||
set => _response.SendChunked = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version ProtocolVersion
|
||||
{
|
||||
get => _response.ProtocolVersion;
|
||||
set => _response.ProtocolVersion = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string StatusDescription
|
||||
{
|
||||
get => _response.StatusDescription;
|
||||
set => _response.StatusDescription = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCookie(Cookie cookie) => _response.SetCookie(cookie);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Close() => _response.OutputStream?.Dispose();
|
||||
}
|
||||
}
|
||||
53
Vendor/EmbedIO-3.5.2/Net/Internal/WebSocketHandshakeResponse.cs
vendored
Normal file
53
Vendor/EmbedIO-3.5.2/Net/Internal/WebSocketHandshakeResponse.cs
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal class WebSocketHandshakeResponse
|
||||
{
|
||||
private const int HandshakeStatusCode = (int)HttpStatusCode.SwitchingProtocols;
|
||||
|
||||
internal WebSocketHandshakeResponse(IHttpContext context)
|
||||
{
|
||||
ProtocolVersion = HttpVersion.Version11;
|
||||
Headers = context.Response.Headers;
|
||||
Headers.Clear(); // Use only headers mentioned in RFC6455 - scrap all the rest.
|
||||
StatusCode = HandshakeStatusCode;
|
||||
Reason = HttpListenerResponseHelper.GetStatusDescription(HandshakeStatusCode);
|
||||
|
||||
Headers[HttpHeaderNames.Upgrade] = "websocket";
|
||||
Headers[HttpHeaderNames.Connection] = "Upgrade";
|
||||
|
||||
foreach (var cookie in context.Request.Cookies)
|
||||
{
|
||||
Headers.Add("Set-Cookie", cookie.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public string Reason { get; }
|
||||
|
||||
public int StatusCode { get; }
|
||||
|
||||
public NameValueCollection Headers { get; }
|
||||
|
||||
public Version ProtocolVersion { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var output = new StringBuilder(64)
|
||||
.AppendFormat(CultureInfo.InvariantCulture, "HTTP/{0} {1} {2}\r\n", ProtocolVersion, StatusCode, Reason);
|
||||
|
||||
foreach (var key in Headers.AllKeys)
|
||||
{
|
||||
_ = output.AppendFormat(CultureInfo.InvariantCulture, "{0}: {1}\r\n", key, Headers[key]);
|
||||
}
|
||||
|
||||
_ = output.Append("\r\n");
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user