Merge pull request #1010 from cvium/kestrel_poc
Remove System.Net and port to Kestrelpull/1058/head
commit
10a0d6bdba
@ -0,0 +1,39 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using WebSocketManager = Emby.Server.Implementations.WebSockets.WebSocketManager;
|
||||
|
||||
namespace Emby.Server.Implementations.Middleware
|
||||
{
|
||||
public class WebSocketMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<WebSocketMiddleware> _logger;
|
||||
private readonly WebSocketManager _webSocketManager;
|
||||
|
||||
public WebSocketMiddleware(RequestDelegate next, ILogger<WebSocketMiddleware> logger, WebSocketManager webSocketManager)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
_webSocketManager = webSocketManager;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
_logger.LogInformation("Handling request: " + httpContext.Request.Path);
|
||||
|
||||
if (httpContext.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
|
||||
if (webSocketContext != null)
|
||||
{
|
||||
await _webSocketManager.OnWebSocketConnected(webSocketContext);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _next.Invoke(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System.IO;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace Jellyfin.Server.SocketSharp
|
||||
namespace Emby.Server.Implementations.SocketSharp
|
||||
{
|
||||
public class HttpFile : IHttpFile
|
||||
{
|
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Emby.Server.Implementations.Net;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Emby.Server.Implementations.SocketSharp
|
||||
{
|
||||
public class WebSocketSharpListener : IHttpListener
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||
private CancellationToken _disposeCancellationToken;
|
||||
|
||||
public WebSocketSharpListener(
|
||||
ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
_disposeCancellationToken = _disposeCancellationTokenSource.Token;
|
||||
}
|
||||
|
||||
public Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
|
||||
public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
|
||||
|
||||
public Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
|
||||
|
||||
private static void LogRequest(ILogger logger, HttpRequest request)
|
||||
{
|
||||
var url = request.GetDisplayUrl();
|
||||
|
||||
logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, request.Headers[HeaderNames.UserAgent].ToString());
|
||||
}
|
||||
|
||||
public async Task ProcessWebSocketRequest(HttpContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogRequest(_logger, ctx.Request);
|
||||
var endpoint = ctx.Connection.RemoteIpAddress.ToString();
|
||||
var url = ctx.Request.GetDisplayUrl();
|
||||
|
||||
var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
|
||||
var socket = new SharpWebSocket(webSocketContext, _logger);
|
||||
|
||||
WebSocketConnected(new WebSocketConnectEventArgs
|
||||
{
|
||||
Url = url,
|
||||
QueryString = ctx.Request.Query,
|
||||
WebSocket = socket,
|
||||
Endpoint = endpoint
|
||||
});
|
||||
|
||||
WebSocketReceiveResult result;
|
||||
var message = new List<byte>();
|
||||
|
||||
do
|
||||
{
|
||||
var buffer = WebSocket.CreateServerBuffer(4096);
|
||||
result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken);
|
||||
message.AddRange(buffer.Array.Take(result.Count));
|
||||
|
||||
if (result.EndOfMessage)
|
||||
{
|
||||
socket.OnReceiveBytes(message.ToArray());
|
||||
message.Clear();
|
||||
}
|
||||
} while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close);
|
||||
|
||||
|
||||
if (webSocketContext.State == WebSocketState.Open)
|
||||
{
|
||||
await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
|
||||
result.CloseStatusDescription, _disposeCancellationToken);
|
||||
}
|
||||
|
||||
socket.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "AcceptWebSocketAsync error");
|
||||
ctx.Response.StatusCode = 500;
|
||||
}
|
||||
}
|
||||
|
||||
public Task Stop()
|
||||
{
|
||||
_disposeCancellationTokenSource.Cancel();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources and disposes of the managed resources used.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources and disposes of the managed resources used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">Whether or not the managed resources should be disposed</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Stop().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using IRequest = MediaBrowser.Model.Services.IRequest;
|
||||
|
||||
namespace Emby.Server.Implementations.SocketSharp
|
||||
{
|
||||
public class WebSocketSharpResponse : IResponse
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly HttpResponse _response;
|
||||
|
||||
public WebSocketSharpResponse(ILogger logger, HttpResponse response, IRequest request)
|
||||
{
|
||||
_logger = logger;
|
||||
this._response = response;
|
||||
Items = new Dictionary<string, object>();
|
||||
Request = request;
|
||||
}
|
||||
|
||||
public IRequest Request { get; private set; }
|
||||
|
||||
public Dictionary<string, object> Items { get; private set; }
|
||||
|
||||
public object OriginalResponse => _response;
|
||||
|
||||
public int StatusCode
|
||||
{
|
||||
get => this._response.StatusCode;
|
||||
set => this._response.StatusCode = value;
|
||||
}
|
||||
|
||||
public string StatusDescription { get; set; }
|
||||
|
||||
public string ContentType
|
||||
{
|
||||
get => _response.ContentType;
|
||||
set => _response.ContentType = value;
|
||||
}
|
||||
|
||||
public IHeaderDictionary Headers => _response.Headers;
|
||||
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ContentType = value;
|
||||
return;
|
||||
}
|
||||
|
||||
_response.Headers.Add(name, value);
|
||||
}
|
||||
|
||||
public string GetHeader(string name)
|
||||
{
|
||||
return _response.Headers[name];
|
||||
}
|
||||
|
||||
public void Redirect(string url)
|
||||
{
|
||||
_response.Redirect(url);
|
||||
}
|
||||
|
||||
public Stream OutputStream => _response.Body;
|
||||
|
||||
public bool IsClosed { get; set; }
|
||||
|
||||
public bool SendChunked { get; set; }
|
||||
|
||||
const int StreamCopyToBufferSize = 81920;
|
||||
public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken)
|
||||
{
|
||||
var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
//if (count <= 0)
|
||||
//{
|
||||
// allowAsync = true;
|
||||
//}
|
||||
|
||||
var fileOpenOptions = FileOpenOptions.SequentialScan;
|
||||
|
||||
if (allowAsync)
|
||||
{
|
||||
fileOpenOptions |= FileOpenOptions.Asynchronous;
|
||||
}
|
||||
|
||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
|
||||
using (var fs = fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
|
||||
{
|
||||
if (offset > 0)
|
||||
{
|
||||
fs.Position = offset;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
await streamHelper.CopyToAsync(fs, OutputStream, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await fs.CopyToAsync(OutputStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
||||
namespace Emby.Server.Implementations.WebSockets
|
||||
{
|
||||
public interface IWebSocketHandler
|
||||
{
|
||||
Task ProcessMessage(WebSocketMessage<object> message, TaskCompletionSource<bool> taskCompletionSource);
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using UtfUnknown;
|
||||
|
||||
namespace Emby.Server.Implementations.WebSockets
|
||||
{
|
||||
public class WebSocketManager
|
||||
{
|
||||
private readonly IWebSocketHandler[] _webSocketHandlers;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly ILogger<WebSocketManager> _logger;
|
||||
private const int BufferSize = 4096;
|
||||
|
||||
public WebSocketManager(IWebSocketHandler[] webSocketHandlers, IJsonSerializer jsonSerializer, ILogger<WebSocketManager> logger)
|
||||
{
|
||||
_webSocketHandlers = webSocketHandlers;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task OnWebSocketConnected(WebSocket webSocket)
|
||||
{
|
||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
var cancellationToken = new CancellationTokenSource().Token;
|
||||
WebSocketReceiveResult result;
|
||||
var message = new List<byte>();
|
||||
|
||||
// Keep listening for incoming messages, otherwise the socket closes automatically
|
||||
do
|
||||
{
|
||||
var buffer = WebSocket.CreateServerBuffer(BufferSize);
|
||||
result = await webSocket.ReceiveAsync(buffer, cancellationToken);
|
||||
message.AddRange(buffer.Array.Take(result.Count));
|
||||
|
||||
if (result.EndOfMessage)
|
||||
{
|
||||
await ProcessMessage(message.ToArray(), taskCompletionSource);
|
||||
message.Clear();
|
||||
}
|
||||
} while (!taskCompletionSource.Task.IsCompleted &&
|
||||
webSocket.State == WebSocketState.Open &&
|
||||
result.MessageType != WebSocketMessageType.Close);
|
||||
|
||||
if (webSocket.State == WebSocketState.Open)
|
||||
{
|
||||
await webSocket.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
|
||||
result.CloseStatusDescription, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource<bool> taskCompletionSource)
|
||||
{
|
||||
var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName;
|
||||
var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)
|
||||
? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length)
|
||||
: Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length);
|
||||
|
||||
// All messages are expected to be valid JSON objects
|
||||
if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogDebug("Received web socket message that is not a json structure: {Message}", message);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var info = _jsonSerializer.DeserializeFromString<WebSocketMessage<object>>(message);
|
||||
|
||||
_logger.LogDebug("Websocket message received: {0}", info.MessageType);
|
||||
|
||||
var tasks = _webSocketHandlers.Select(handler => Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.ProcessMessage(info, taskCompletionSource).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{HandlerType} failed processing WebSocket message {MessageType}",
|
||||
handler.GetType().Name, info.MessageType ?? string.Empty);
|
||||
}
|
||||
}));
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing web socket message");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
|
||||
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
|
||||
using IRequest = MediaBrowser.Model.Services.IRequest;
|
||||
|
||||
namespace Jellyfin.Server.SocketSharp
|
||||
{
|
||||
public class WebSocketSharpResponse : IHttpResponse
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly HttpListenerResponse _response;
|
||||
|
||||
public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request)
|
||||
{
|
||||
_logger = logger;
|
||||
this._response = response;
|
||||
Items = new Dictionary<string, object>();
|
||||
Request = request;
|
||||
}
|
||||
|
||||
public IRequest Request { get; private set; }
|
||||
|
||||
public Dictionary<string, object> Items { get; private set; }
|
||||
|
||||
public object OriginalResponse => _response;
|
||||
|
||||
public int StatusCode
|
||||
{
|
||||
get => this._response.StatusCode;
|
||||
set => this._response.StatusCode = value;
|
||||
}
|
||||
|
||||
public string StatusDescription
|
||||
{
|
||||
get => this._response.StatusDescription;
|
||||
set => this._response.StatusDescription = value;
|
||||
}
|
||||
|
||||
public string ContentType
|
||||
{
|
||||
get => _response.ContentType;
|
||||
set => _response.ContentType = value;
|
||||
}
|
||||
|
||||
public QueryParamCollection Headers => _response.Headers;
|
||||
|
||||
private static string AsHeaderValue(Cookie cookie)
|
||||
{
|
||||
DateTime defaultExpires = DateTime.MinValue;
|
||||
|
||||
var path = cookie.Expires == defaultExpires
|
||||
? "/"
|
||||
: cookie.Path ?? "/";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"{cookie.Name}={cookie.Value};path={path}");
|
||||
|
||||
if (cookie.Expires != defaultExpires)
|
||||
{
|
||||
sb.Append($";expires={cookie.Expires:R}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cookie.Domain))
|
||||
{
|
||||
sb.Append($";domain={cookie.Domain}");
|
||||
}
|
||||
|
||||
if (cookie.Secure)
|
||||
{
|
||||
sb.Append(";Secure");
|
||||
}
|
||||
|
||||
if (cookie.HttpOnly)
|
||||
{
|
||||
sb.Append(";HttpOnly");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ContentType = value;
|
||||
return;
|
||||
}
|
||||
|
||||
_response.AddHeader(name, value);
|
||||
}
|
||||
|
||||
public string GetHeader(string name)
|
||||
{
|
||||
return _response.Headers[name];
|
||||
}
|
||||
|
||||
public void Redirect(string url)
|
||||
{
|
||||
_response.Redirect(url);
|
||||
}
|
||||
|
||||
public Stream OutputStream => _response.OutputStream;
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!this.IsClosed)
|
||||
{
|
||||
this.IsClosed = true;
|
||||
|
||||
try
|
||||
{
|
||||
var response = this._response;
|
||||
|
||||
var outputStream = response.OutputStream;
|
||||
|
||||
// This is needed with compression
|
||||
outputStream.Flush();
|
||||
outputStream.Dispose();
|
||||
|
||||
response.Close();
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in HttpListenerResponseWrapper");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsClosed
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public void SetContentLength(long contentLength)
|
||||
{
|
||||
// you can happily set the Content-Length header in Asp.Net
|
||||
// but HttpListener will complain if you do - you have to set ContentLength64 on the response.
|
||||
// workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
|
||||
_response.ContentLength64 = contentLength;
|
||||
}
|
||||
|
||||
public void SetCookie(Cookie cookie)
|
||||
{
|
||||
var cookieStr = AsHeaderValue(cookie);
|
||||
_response.Headers.Add("Set-Cookie", cookieStr);
|
||||
}
|
||||
|
||||
public bool SendChunked
|
||||
{
|
||||
get => _response.SendChunked;
|
||||
set => _response.SendChunked = value;
|
||||
}
|
||||
|
||||
public bool KeepAlive { get; set; }
|
||||
|
||||
public void ClearCookies()
|
||||
{
|
||||
}
|
||||
|
||||
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||
{
|
||||
return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Common.Extensions
|
||||
{
|
||||
// The MS CollectionExtensions are only available in netcoreapp
|
||||
public static class CollectionExtensions
|
||||
{
|
||||
public static TValue GetValueOrDefault<TKey, TValue> (this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
|
||||
{
|
||||
dictionary.TryGetValue(key, out var ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace MediaBrowser.Controller.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Class WebSocketConnectEventArgs
|
||||
/// </summary>
|
||||
public class WebSocketConnectingEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the URL.
|
||||
/// </summary>
|
||||
/// <value>The URL.</value>
|
||||
public string Url { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the endpoint.
|
||||
/// </summary>
|
||||
/// <value>The endpoint.</value>
|
||||
public string Endpoint { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the query string.
|
||||
/// </summary>
|
||||
/// <value>The query string.</value>
|
||||
public QueryParamCollection QueryString { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [allow connection].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [allow connection]; otherwise, <c>false</c>.</value>
|
||||
public bool AllowConnection { get; set; }
|
||||
|
||||
public WebSocketConnectingEventArgs()
|
||||
{
|
||||
QueryString = new QueryParamCollection();
|
||||
AllowConnection = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,691 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MediaBrowser.Model.Services
|
||||
{
|
||||
public static class MyHttpUtility
|
||||
{
|
||||
// Must be sorted
|
||||
static readonly long[] entities = new long[] {
|
||||
(long)'A' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
|
||||
(long)'A' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'A' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'A' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'A' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24,
|
||||
(long)'A' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24,
|
||||
(long)'A' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
|
||||
(long)'A' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'B' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32,
|
||||
(long)'C' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16,
|
||||
(long)'C' << 56 | (long)'h' << 48 | (long)'i' << 40,
|
||||
(long)'D' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16,
|
||||
(long)'D' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24,
|
||||
(long)'E' << 56 | (long)'T' << 48 | (long)'H' << 40,
|
||||
(long)'E' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'E' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'E' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'E' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8,
|
||||
(long)'E' << 56 | (long)'t' << 48 | (long)'a' << 40,
|
||||
(long)'E' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'G' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24,
|
||||
(long)'I' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'I' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'I' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'I' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32,
|
||||
(long)'I' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'K' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24,
|
||||
(long)'L' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16,
|
||||
(long)'M' << 56 | (long)'u' << 48,
|
||||
(long)'N' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
|
||||
(long)'N' << 56 | (long)'u' << 48,
|
||||
(long)'O' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
|
||||
(long)'O' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'O' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'O' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'O' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24,
|
||||
(long)'O' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8,
|
||||
(long)'O' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16,
|
||||
(long)'O' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
|
||||
(long)'O' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'P' << 56 | (long)'h' << 48 | (long)'i' << 40,
|
||||
(long)'P' << 56 | (long)'i' << 48,
|
||||
(long)'P' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24,
|
||||
(long)'P' << 56 | (long)'s' << 48 | (long)'i' << 40,
|
||||
(long)'R' << 56 | (long)'h' << 48 | (long)'o' << 40,
|
||||
(long)'S' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16,
|
||||
(long)'S' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24,
|
||||
(long)'T' << 56 | (long)'H' << 48 | (long)'O' << 40 | (long)'R' << 32 | (long)'N' << 24,
|
||||
(long)'T' << 56 | (long)'a' << 48 | (long)'u' << 40,
|
||||
(long)'T' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24,
|
||||
(long)'U' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'U' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'U' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'U' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8,
|
||||
(long)'U' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'X' << 56 | (long)'i' << 48,
|
||||
(long)'Y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'Y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'Z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32,
|
||||
(long)'a' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'a' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'a' << 56 | (long)'c' << 48 | (long)'u' << 40 | (long)'t' << 32 | (long)'e' << 24,
|
||||
(long)'a' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
|
||||
(long)'a' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'a' << 56 | (long)'l' << 48 | (long)'e' << 40 | (long)'f' << 32 | (long)'s' << 24 | (long)'y' << 16 | (long)'m' << 8,
|
||||
(long)'a' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24,
|
||||
(long)'a' << 56 | (long)'m' << 48 | (long)'p' << 40,
|
||||
(long)'a' << 56 | (long)'n' << 48 | (long)'d' << 40,
|
||||
(long)'a' << 56 | (long)'n' << 48 | (long)'g' << 40,
|
||||
(long)'a' << 56 | (long)'p' << 48 | (long)'o' << 40 | (long)'s' << 32,
|
||||
(long)'a' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24,
|
||||
(long)'a' << 56 | (long)'s' << 48 | (long)'y' << 40 | (long)'m' << 32 | (long)'p' << 24,
|
||||
(long)'a' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
|
||||
(long)'a' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'b' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
|
||||
(long)'b' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32,
|
||||
(long)'b' << 56 | (long)'r' << 48 | (long)'v' << 40 | (long)'b' << 32 | (long)'a' << 24 | (long)'r' << 16,
|
||||
(long)'b' << 56 | (long)'u' << 48 | (long)'l' << 40 | (long)'l' << 32,
|
||||
(long)'c' << 56 | (long)'a' << 48 | (long)'p' << 40,
|
||||
(long)'c' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16,
|
||||
(long)'c' << 56 | (long)'e' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'l' << 24,
|
||||
(long)'c' << 56 | (long)'e' << 48 | (long)'n' << 40 | (long)'t' << 32,
|
||||
(long)'c' << 56 | (long)'h' << 48 | (long)'i' << 40,
|
||||
(long)'c' << 56 | (long)'i' << 48 | (long)'r' << 40 | (long)'c' << 32,
|
||||
(long)'c' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'b' << 32 | (long)'s' << 24,
|
||||
(long)'c' << 56 | (long)'o' << 48 | (long)'n' << 40 | (long)'g' << 32,
|
||||
(long)'c' << 56 | (long)'o' << 48 | (long)'p' << 40 | (long)'y' << 32,
|
||||
(long)'c' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'r' << 24,
|
||||
(long)'c' << 56 | (long)'u' << 48 | (long)'p' << 40,
|
||||
(long)'c' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'n' << 16,
|
||||
(long)'d' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'d' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16,
|
||||
(long)'d' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'d' << 56 | (long)'e' << 48 | (long)'g' << 40,
|
||||
(long)'d' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24,
|
||||
(long)'d' << 56 | (long)'i' << 48 | (long)'a' << 40 | (long)'m' << 32 | (long)'s' << 24,
|
||||
(long)'d' << 56 | (long)'i' << 48 | (long)'v' << 40 | (long)'i' << 32 | (long)'d' << 24 | (long)'e' << 16,
|
||||
(long)'e' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'e' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'e' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'e' << 56 | (long)'m' << 48 | (long)'p' << 40 | (long)'t' << 32 | (long)'y' << 24,
|
||||
(long)'e' << 56 | (long)'m' << 48 | (long)'s' << 40 | (long)'p' << 32,
|
||||
(long)'e' << 56 | (long)'n' << 48 | (long)'s' << 40 | (long)'p' << 32,
|
||||
(long)'e' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8,
|
||||
(long)'e' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'i' << 32 | (long)'v' << 24,
|
||||
(long)'e' << 56 | (long)'t' << 48 | (long)'a' << 40,
|
||||
(long)'e' << 56 | (long)'t' << 48 | (long)'h' << 40,
|
||||
(long)'e' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'e' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'o' << 32,
|
||||
(long)'e' << 56 | (long)'x' << 48 | (long)'i' << 40 | (long)'s' << 32 | (long)'t' << 24,
|
||||
(long)'f' << 56 | (long)'n' << 48 | (long)'o' << 40 | (long)'f' << 32,
|
||||
(long)'f' << 56 | (long)'o' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'l' << 24 | (long)'l' << 16,
|
||||
(long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'2' << 16,
|
||||
(long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'4' << 16,
|
||||
(long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'3' << 24 | (long)'4' << 16,
|
||||
(long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'l' << 24,
|
||||
(long)'g' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24,
|
||||
(long)'g' << 56 | (long)'e' << 48,
|
||||
(long)'g' << 56 | (long)'t' << 48,
|
||||
(long)'h' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'h' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'h' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'t' << 24 | (long)'s' << 16,
|
||||
(long)'h' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'l' << 32 | (long)'i' << 24 | (long)'p' << 16,
|
||||
(long)'i' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'i' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'i' << 56 | (long)'e' << 48 | (long)'x' << 40 | (long)'c' << 32 | (long)'l' << 24,
|
||||
(long)'i' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'i' << 56 | (long)'m' << 48 | (long)'a' << 40 | (long)'g' << 32 | (long)'e' << 24,
|
||||
(long)'i' << 56 | (long)'n' << 48 | (long)'f' << 40 | (long)'i' << 32 | (long)'n' << 24,
|
||||
(long)'i' << 56 | (long)'n' << 48 | (long)'t' << 40,
|
||||
(long)'i' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32,
|
||||
(long)'i' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'e' << 32 | (long)'s' << 24 | (long)'t' << 16,
|
||||
(long)'i' << 56 | (long)'s' << 48 | (long)'i' << 40 | (long)'n' << 32,
|
||||
(long)'i' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'k' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24,
|
||||
(long)'l' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'l' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16,
|
||||
(long)'l' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32,
|
||||
(long)'l' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
|
||||
(long)'l' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'l' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24,
|
||||
(long)'l' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
|
||||
(long)'l' << 56 | (long)'e' << 48,
|
||||
(long)'l' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16,
|
||||
(long)'l' << 56 | (long)'o' << 48 | (long)'w' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'t' << 16,
|
||||
(long)'l' << 56 | (long)'o' << 48 | (long)'z' << 40,
|
||||
(long)'l' << 56 | (long)'r' << 48 | (long)'m' << 40,
|
||||
(long)'l' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16,
|
||||
(long)'l' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
|
||||
(long)'l' << 56 | (long)'t' << 48,
|
||||
(long)'m' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'r' << 32,
|
||||
(long)'m' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24,
|
||||
(long)'m' << 56 | (long)'i' << 48 | (long)'c' << 40 | (long)'r' << 32 | (long)'o' << 24,
|
||||
(long)'m' << 56 | (long)'i' << 48 | (long)'d' << 40 | (long)'d' << 32 | (long)'o' << 24 | (long)'t' << 16,
|
||||
(long)'m' << 56 | (long)'i' << 48 | (long)'n' << 40 | (long)'u' << 32 | (long)'s' << 24,
|
||||
(long)'m' << 56 | (long)'u' << 48,
|
||||
(long)'n' << 56 | (long)'a' << 48 | (long)'b' << 40 | (long)'l' << 32 | (long)'a' << 24,
|
||||
(long)'n' << 56 | (long)'b' << 48 | (long)'s' << 40 | (long)'p' << 32,
|
||||
(long)'n' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24,
|
||||
(long)'n' << 56 | (long)'e' << 48,
|
||||
(long)'n' << 56 | (long)'i' << 48,
|
||||
(long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40,
|
||||
(long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'i' << 32 | (long)'n' << 24,
|
||||
(long)'n' << 56 | (long)'s' << 48 | (long)'u' << 40 | (long)'b' << 32,
|
||||
(long)'n' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
|
||||
(long)'n' << 56 | (long)'u' << 48,
|
||||
(long)'o' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'o' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'o' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
|
||||
(long)'o' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'o' << 56 | (long)'l' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'e' << 24,
|
||||
(long)'o' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24,
|
||||
(long)'o' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8,
|
||||
(long)'o' << 56 | (long)'p' << 48 | (long)'l' << 40 | (long)'u' << 32 | (long)'s' << 24,
|
||||
(long)'o' << 56 | (long)'r' << 48,
|
||||
(long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'f' << 32,
|
||||
(long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'m' << 32,
|
||||
(long)'o' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16,
|
||||
(long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
|
||||
(long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24 | (long)'s' << 16,
|
||||
(long)'o' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'a' << 32,
|
||||
(long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'t' << 32,
|
||||
(long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'m' << 32 | (long)'i' << 24 | (long)'l' << 16,
|
||||
(long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'p' << 32,
|
||||
(long)'p' << 56 | (long)'h' << 48 | (long)'i' << 40,
|
||||
(long)'p' << 56 | (long)'i' << 48,
|
||||
(long)'p' << 56 | (long)'i' << 48 | (long)'v' << 40,
|
||||
(long)'p' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'s' << 32 | (long)'m' << 24 | (long)'n' << 16,
|
||||
(long)'p' << 56 | (long)'o' << 48 | (long)'u' << 40 | (long)'n' << 32 | (long)'d' << 24,
|
||||
(long)'p' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24,
|
||||
(long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'d' << 32,
|
||||
(long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'p' << 32,
|
||||
(long)'p' << 56 | (long)'s' << 48 | (long)'i' << 40,
|
||||
(long)'q' << 56 | (long)'u' << 48 | (long)'o' << 40 | (long)'t' << 32,
|
||||
(long)'r' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'r' << 56 | (long)'a' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'c' << 24,
|
||||
(long)'r' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32,
|
||||
(long)'r' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
|
||||
(long)'r' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'r' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24,
|
||||
(long)'r' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
|
||||
(long)'r' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'l' << 32,
|
||||
(long)'r' << 56 | (long)'e' << 48 | (long)'g' << 40,
|
||||
(long)'r' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16,
|
||||
(long)'r' << 56 | (long)'h' << 48 | (long)'o' << 40,
|
||||
(long)'r' << 56 | (long)'l' << 48 | (long)'m' << 40,
|
||||
(long)'r' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16,
|
||||
(long)'r' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
|
||||
(long)'s' << 56 | (long)'b' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
|
||||
(long)'s' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16,
|
||||
(long)'s' << 56 | (long)'d' << 48 | (long)'o' << 40 | (long)'t' << 32,
|
||||
(long)'s' << 56 | (long)'e' << 48 | (long)'c' << 40 | (long)'t' << 32,
|
||||
(long)'s' << 56 | (long)'h' << 48 | (long)'y' << 40,
|
||||
(long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24,
|
||||
(long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24 | (long)'f' << 16,
|
||||
(long)'s' << 56 | (long)'i' << 48 | (long)'m' << 40,
|
||||
(long)'s' << 56 | (long)'p' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24 | (long)'s' << 16,
|
||||
(long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40,
|
||||
(long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40 | (long)'e' << 32,
|
||||
(long)'s' << 56 | (long)'u' << 48 | (long)'m' << 40,
|
||||
(long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40,
|
||||
(long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'1' << 32,
|
||||
(long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'2' << 32,
|
||||
(long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'3' << 32,
|
||||
(long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'e' << 32,
|
||||
(long)'s' << 56 | (long)'z' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
|
||||
(long)'t' << 56 | (long)'a' << 48 | (long)'u' << 40,
|
||||
(long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'4' << 16,
|
||||
(long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24,
|
||||
(long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24 | (long)'s' << 16 | (long)'y' << 8 | (long)'m' << 0,
|
||||
(long)'t' << 56 | (long)'h' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'s' << 24 | (long)'p' << 16,
|
||||
(long)'t' << 56 | (long)'h' << 48 | (long)'o' << 40 | (long)'r' << 32 | (long)'n' << 24,
|
||||
(long)'t' << 56 | (long)'i' << 48 | (long)'l' << 40 | (long)'d' << 32 | (long)'e' << 24,
|
||||
(long)'t' << 56 | (long)'i' << 48 | (long)'m' << 40 | (long)'e' << 32 | (long)'s' << 24,
|
||||
(long)'t' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24,
|
||||
(long)'u' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'u' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'u' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
|
||||
(long)'u' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
|
||||
(long)'u' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
|
||||
(long)'u' << 56 | (long)'m' << 48 | (long)'l' << 40,
|
||||
(long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'h' << 24,
|
||||
(long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8,
|
||||
(long)'u' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'w' << 56 | (long)'e' << 48 | (long)'i' << 40 | (long)'e' << 32 | (long)'r' << 24 | (long)'p' << 16,
|
||||
(long)'x' << 56 | (long)'i' << 48,
|
||||
(long)'y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
|
||||
(long)'y' << 56 | (long)'e' << 48 | (long)'n' << 40,
|
||||
(long)'y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
|
||||
(long)'z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32,
|
||||
(long)'z' << 56 | (long)'w' << 48 | (long)'j' << 40,
|
||||
(long)'z' << 56 | (long)'w' << 48 | (long)'n' << 40 | (long)'j' << 32
|
||||
};
|
||||
|
||||
static readonly char[] entities_values = new char[] {
|
||||
'\u00C6', '\u00C1', '\u00C2', '\u00C0', '\u0391', '\u00C5', '\u00C3', '\u00C4', '\u0392', '\u00C7', '\u03A7',
|
||||
'\u2021', '\u0394', '\u00D0', '\u00C9', '\u00CA', '\u00C8', '\u0395', '\u0397', '\u00CB', '\u0393', '\u00CD',
|
||||
'\u00CE', '\u00CC', '\u0399', '\u00CF', '\u039A', '\u039B', '\u039C', '\u00D1', '\u039D', '\u0152', '\u00D3',
|
||||
'\u00D4', '\u00D2', '\u03A9', '\u039F', '\u00D8', '\u00D5', '\u00D6', '\u03A6', '\u03A0', '\u2033', '\u03A8',
|
||||
'\u03A1', '\u0160', '\u03A3', '\u00DE', '\u03A4', '\u0398', '\u00DA', '\u00DB', '\u00D9', '\u03A5', '\u00DC',
|
||||
'\u039E', '\u00DD', '\u0178', '\u0396', '\u00E1', '\u00E2', '\u00B4', '\u00E6', '\u00E0', '\u2135', '\u03B1',
|
||||
'\u0026', '\u2227', '\u2220', '\u0027', '\u00E5', '\u2248', '\u00E3', '\u00E4', '\u201E', '\u03B2', '\u00A6',
|
||||
'\u2022', '\u2229', '\u00E7', '\u00B8', '\u00A2', '\u03C7', '\u02C6', '\u2663', '\u2245', '\u00A9', '\u21B5',
|
||||
'\u222A', '\u00A4', '\u21D3', '\u2020', '\u2193', '\u00B0', '\u03B4', '\u2666', '\u00F7', '\u00E9', '\u00EA',
|
||||
'\u00E8', '\u2205', '\u2003', '\u2002', '\u03B5', '\u2261', '\u03B7', '\u00F0', '\u00EB', '\u20AC', '\u2203',
|
||||
'\u0192', '\u2200', '\u00BD', '\u00BC', '\u00BE', '\u2044', '\u03B3', '\u2265', '\u003E', '\u21D4', '\u2194',
|
||||
'\u2665', '\u2026', '\u00ED', '\u00EE', '\u00A1', '\u00EC', '\u2111', '\u221E', '\u222B', '\u03B9', '\u00BF',
|
||||
'\u2208', '\u00EF', '\u03BA', '\u21D0', '\u03BB', '\u2329', '\u00AB', '\u2190', '\u2308', '\u201C', '\u2264',
|
||||
'\u230A', '\u2217', '\u25CA', '\u200E', '\u2039', '\u2018', '\u003C', '\u00AF', '\u2014', '\u00B5', '\u00B7',
|
||||
'\u2212', '\u03BC', '\u2207', '\u00A0', '\u2013', '\u2260', '\u220B', '\u00AC', '\u2209', '\u2284', '\u00F1',
|
||||
'\u03BD', '\u00F3', '\u00F4', '\u0153', '\u00F2', '\u203E', '\u03C9', '\u03BF', '\u2295', '\u2228', '\u00AA',
|
||||
'\u00BA', '\u00F8', '\u00F5', '\u2297', '\u00F6', '\u00B6', '\u2202', '\u2030', '\u22A5', '\u03C6', '\u03C0',
|
||||
'\u03D6', '\u00B1', '\u00A3', '\u2032', '\u220F', '\u221D', '\u03C8', '\u0022', '\u21D2', '\u221A', '\u232A',
|
||||
'\u00BB', '\u2192', '\u2309', '\u201D', '\u211C', '\u00AE', '\u230B', '\u03C1', '\u200F', '\u203A', '\u2019',
|
||||
'\u201A', '\u0161', '\u22C5', '\u00A7', '\u00AD', '\u03C3', '\u03C2', '\u223C', '\u2660', '\u2282', '\u2286',
|
||||
'\u2211', '\u2283', '\u00B9', '\u00B2', '\u00B3', '\u2287', '\u00DF', '\u03C4', '\u2234', '\u03B8', '\u03D1',
|
||||
'\u2009', '\u00FE', '\u02DC', '\u00D7', '\u2122', '\u21D1', '\u00FA', '\u2191', '\u00FB', '\u00F9', '\u00A8',
|
||||
'\u03D2', '\u03C5', '\u00FC', '\u2118', '\u03BE', '\u00FD', '\u00A5', '\u00FF', '\u03B6', '\u200D', '\u200C'
|
||||
};
|
||||
|
||||
#region Methods
|
||||
|
||||
static void WriteCharBytes(IList buf, char ch, Encoding e)
|
||||
{
|
||||
if (ch > 255)
|
||||
{
|
||||
foreach (byte b in e.GetBytes(new char[] { ch }))
|
||||
buf.Add(b);
|
||||
}
|
||||
else
|
||||
buf.Add((byte)ch);
|
||||
}
|
||||
|
||||
public static string UrlDecode(string s, Encoding e)
|
||||
{
|
||||
if (null == s)
|
||||
return null;
|
||||
|
||||
if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1)
|
||||
return s;
|
||||
|
||||
if (e == null)
|
||||
e = Encoding.UTF8;
|
||||
|
||||
long len = s.Length;
|
||||
var bytes = new List<byte>();
|
||||
int xchar;
|
||||
char ch;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
ch = s[i];
|
||||
if (ch == '%' && i + 2 < len && s[i + 1] != '%')
|
||||
{
|
||||
if (s[i + 1] == 'u' && i + 5 < len)
|
||||
{
|
||||
// unicode hex sequence
|
||||
xchar = GetChar(s, i + 2, 4);
|
||||
if (xchar != -1)
|
||||
{
|
||||
WriteCharBytes(bytes, (char)xchar, e);
|
||||
i += 5;
|
||||
}
|
||||
else
|
||||
WriteCharBytes(bytes, '%', e);
|
||||
}
|
||||
else if ((xchar = GetChar(s, i + 1, 2)) != -1)
|
||||
{
|
||||
WriteCharBytes(bytes, (char)xchar, e);
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteCharBytes(bytes, '%', e);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '+')
|
||||
WriteCharBytes(bytes, ' ', e);
|
||||
else
|
||||
WriteCharBytes(bytes, ch, e);
|
||||
}
|
||||
|
||||
byte[] buf = bytes.ToArray();
|
||||
bytes = null;
|
||||
return e.GetString(buf, 0, buf.Length);
|
||||
|
||||
}
|
||||
|
||||
static int GetInt(byte b)
|
||||
{
|
||||
char c = (char)b;
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int GetChar(string str, int offset, int length)
|
||||
{
|
||||
int val = 0;
|
||||
int end = length + offset;
|
||||
for (int i = offset; i < end; i++)
|
||||
{
|
||||
char c = str[i];
|
||||
if (c > 127)
|
||||
return -1;
|
||||
|
||||
int current = GetInt((byte)c);
|
||||
if (current == -1)
|
||||
return -1;
|
||||
val = (val << 4) + current;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static bool TryConvertKeyToEntity(string key, out char value)
|
||||
{
|
||||
var token = CalculateKeyValue(key);
|
||||
if (token == 0)
|
||||
{
|
||||
value = '\0';
|
||||
return false;
|
||||
}
|
||||
|
||||
var idx = Array.BinarySearch(entities, token);
|
||||
if (idx < 0)
|
||||
{
|
||||
value = '\0';
|
||||
return false;
|
||||
}
|
||||
|
||||
value = entities_values[idx];
|
||||
return true;
|
||||
}
|
||||
|
||||
static long CalculateKeyValue(string s)
|
||||
{
|
||||
if (s.Length > 8)
|
||||
return 0;
|
||||
|
||||
long key = 0;
|
||||
for (int i = 0; i < s.Length; ++i)
|
||||
{
|
||||
long ch = s[i];
|
||||
if (ch > 'z' || ch < '0')
|
||||
return 0;
|
||||
|
||||
key |= ch << ((7 - i) * 8);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes an HTML-encoded string and returns the decoded string.
|
||||
/// </summary>
|
||||
/// <param name="s">The HTML string to decode. </param>
|
||||
/// <returns>The decoded text.</returns>
|
||||
public static string HtmlDecode(string s)
|
||||
{
|
||||
if (s == null)
|
||||
throw new ArgumentNullException(nameof(s));
|
||||
|
||||
if (s.IndexOf('&') == -1)
|
||||
return s;
|
||||
|
||||
var entity = new StringBuilder();
|
||||
var output = new StringBuilder();
|
||||
int len = s.Length;
|
||||
// 0 -> nothing,
|
||||
// 1 -> right after '&'
|
||||
// 2 -> between '&' and ';' but no '#'
|
||||
// 3 -> '#' found after '&' and getting numbers
|
||||
int state = 0;
|
||||
int number = 0;
|
||||
int digit_start = 0;
|
||||
bool hex_number = false;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
char c = s[i];
|
||||
if (state == 0)
|
||||
{
|
||||
if (c == '&')
|
||||
{
|
||||
entity.Append(c);
|
||||
state = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Append(c);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '&')
|
||||
{
|
||||
state = 1;
|
||||
if (digit_start > 0)
|
||||
{
|
||||
entity.Append(s, digit_start, i - digit_start);
|
||||
digit_start = 0;
|
||||
}
|
||||
|
||||
output.Append(entity.ToString());
|
||||
entity.Length = 0;
|
||||
entity.Append('&');
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case 1:
|
||||
if (c == ';')
|
||||
{
|
||||
state = 0;
|
||||
output.Append(entity.ToString());
|
||||
output.Append(c);
|
||||
entity.Length = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
number = 0;
|
||||
hex_number = false;
|
||||
if (c != '#')
|
||||
{
|
||||
state = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = 3;
|
||||
}
|
||||
entity.Append(c);
|
||||
|
||||
break;
|
||||
case 2:
|
||||
entity.Append(c);
|
||||
if (c == ';')
|
||||
{
|
||||
string key = entity.ToString();
|
||||
state = 0;
|
||||
entity.Length = 0;
|
||||
|
||||
if (key.Length > 1)
|
||||
{
|
||||
var skey = key.Substring(1, key.Length - 2);
|
||||
if (TryConvertKeyToEntity(skey, out c))
|
||||
{
|
||||
output.Append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
output.Append(key);
|
||||
}
|
||||
|
||||
break;
|
||||
case 3:
|
||||
if (c == ';')
|
||||
{
|
||||
if (number < 0x10000)
|
||||
{
|
||||
output.Append((char)number);
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Append((char)(0xd800 + ((number - 0x10000) >> 10)));
|
||||
output.Append((char)(0xdc00 + ((number - 0x10000) & 0x3ff)));
|
||||
}
|
||||
state = 0;
|
||||
entity.Length = 0;
|
||||
digit_start = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == 'x' || c == 'X' && !hex_number)
|
||||
{
|
||||
digit_start = i;
|
||||
hex_number = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (char.IsDigit(c))
|
||||
{
|
||||
if (digit_start == 0)
|
||||
digit_start = i;
|
||||
|
||||
number = number * (hex_number ? 16 : 10) + ((int)c - '0');
|
||||
break;
|
||||
}
|
||||
|
||||
if (hex_number)
|
||||
{
|
||||
if (c >= 'a' && c <= 'f')
|
||||
{
|
||||
number = number * 16 + 10 + ((int)c - 'a');
|
||||
break;
|
||||
}
|
||||
if (c >= 'A' && c <= 'F')
|
||||
{
|
||||
number = number * 16 + 10 + ((int)c - 'A');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
state = 2;
|
||||
if (digit_start > 0)
|
||||
{
|
||||
entity.Append(s, digit_start, i - digit_start);
|
||||
digit_start = 0;
|
||||
}
|
||||
|
||||
entity.Append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.Length > 0)
|
||||
{
|
||||
output.Append(entity);
|
||||
}
|
||||
else if (digit_start > 0)
|
||||
{
|
||||
output.Append(s, digit_start, s.Length - digit_start);
|
||||
}
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
public static QueryParamCollection ParseQueryString(string query)
|
||||
{
|
||||
return ParseQueryString(query, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public static QueryParamCollection ParseQueryString(string query, Encoding encoding)
|
||||
{
|
||||
if (query == null)
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
if (encoding == null)
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
if (query.Length == 0 || (query.Length == 1 && query[0] == '?'))
|
||||
return new QueryParamCollection();
|
||||
if (query[0] == '?')
|
||||
query = query.Substring(1);
|
||||
|
||||
var result = new QueryParamCollection();
|
||||
ParseQueryString(query, encoding, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static void ParseQueryString(string query, Encoding encoding, QueryParamCollection result)
|
||||
{
|
||||
if (query.Length == 0)
|
||||
return;
|
||||
|
||||
string decoded = HtmlDecode(query);
|
||||
int decodedLength = decoded.Length;
|
||||
int namePos = 0;
|
||||
bool first = true;
|
||||
while (namePos <= decodedLength)
|
||||
{
|
||||
int valuePos = -1, valueEnd = -1;
|
||||
for (int q = namePos; q < decodedLength; q++)
|
||||
{
|
||||
if (valuePos == -1 && decoded[q] == '=')
|
||||
{
|
||||
valuePos = q + 1;
|
||||
}
|
||||
else if (decoded[q] == '&')
|
||||
{
|
||||
valueEnd = q;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
if (decoded[namePos] == '?')
|
||||
namePos++;
|
||||
}
|
||||
|
||||
string name, value;
|
||||
if (valuePos == -1)
|
||||
{
|
||||
name = null;
|
||||
valuePos = namePos;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding);
|
||||
}
|
||||
if (valueEnd < 0)
|
||||
{
|
||||
namePos = -1;
|
||||
valueEnd = decoded.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
namePos = valueEnd + 1;
|
||||
}
|
||||
value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding);
|
||||
|
||||
result.Add(name, value);
|
||||
if (namePos == -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion // Methods
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System.Net;
|
||||
|
||||
namespace MediaBrowser.Model.Services
|
||||
{
|
||||
public interface IHttpResponse : IResponse
|
||||
{
|
||||
//ICookies Cookies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new Set-Cookie instruction to Response
|
||||
/// </summary>
|
||||
/// <param name="cookie"></param>
|
||||
void SetCookie(Cookie cookie);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all pending Set-Cookie instructions
|
||||
/// </summary>
|
||||
void ClearCookies();
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the values that indicate whether the byte order is a Little-endian or Big-endian.
|
||||
/// </summary>
|
||||
public enum ByteOrder : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates a Little-endian.
|
||||
/// </summary>
|
||||
Little,
|
||||
/// <summary>
|
||||
/// Indicates a Big-endian.
|
||||
/// </summary>
|
||||
Big
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the event data associated with a <see cref="WebSocket.OnClose"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A <see cref="WebSocket.OnClose"/> event occurs when the WebSocket connection has been closed.
|
||||
/// If you would like to get the reason for the close, you should access the <see cref="Code"/> or
|
||||
/// <see cref="Reason"/> property.
|
||||
/// </remarks>
|
||||
public class CloseEventArgs : EventArgs
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private bool _clean;
|
||||
private ushort _code;
|
||||
private string _reason;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
internal CloseEventArgs(PayloadData payload)
|
||||
{
|
||||
var data = payload.ApplicationData;
|
||||
var len = data.Length;
|
||||
_code = len > 1
|
||||
? data.SubArray(0, 2).ToUInt16(ByteOrder.Big)
|
||||
: (ushort)CloseStatusCode.NoStatusCode;
|
||||
|
||||
_reason = len > 2
|
||||
? GetUtf8String(data.SubArray(2, len - 2))
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private static string GetUtf8String(byte[] bytes)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status code for the close.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="ushort"/> that represents the status code for the close if any.
|
||||
/// </value>
|
||||
public ushort Code => _code;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason for the close.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="string"/> that represents the reason for the close if any.
|
||||
/// </value>
|
||||
public string Reason => _reason;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the WebSocket connection has been closed cleanly.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the WebSocket connection has been closed cleanly; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool WasClean
|
||||
{
|
||||
get => _clean;
|
||||
|
||||
internal set => _clean = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the values of the status code for the WebSocket connection close.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The values of the status code are defined in
|
||||
/// <see href="http://tools.ietf.org/html/rfc6455#section-7.4">Section 7.4</see>
|
||||
/// of RFC 6455.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// "Reserved value" must not be set as a status code in a close control frame
|
||||
/// by an endpoint. It's designated for use in applications expecting a status
|
||||
/// code to indicate that the connection was closed due to the system grounds.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public enum CloseStatusCode : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1000.
|
||||
/// Indicates a normal close.
|
||||
/// </summary>
|
||||
Normal = 1000,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1001.
|
||||
/// Indicates that an endpoint is going away.
|
||||
/// </summary>
|
||||
Away = 1001,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1002.
|
||||
/// Indicates that an endpoint is terminating the connection due to a protocol error.
|
||||
/// </summary>
|
||||
ProtocolError = 1002,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1003.
|
||||
/// Indicates that an endpoint is terminating the connection because it has received
|
||||
/// an unacceptable type message.
|
||||
/// </summary>
|
||||
IncorrectData = 1003,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1004.
|
||||
/// Still undefined. Reserved value.
|
||||
/// </summary>
|
||||
Undefined = 1004,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1005.
|
||||
/// Indicates that no status code was actually present. Reserved value.
|
||||
/// </summary>
|
||||
NoStatusCode = 1005,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1006.
|
||||
/// Indicates that the connection was closed abnormally. Reserved value.
|
||||
/// </summary>
|
||||
Abnormal = 1006,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1007.
|
||||
/// Indicates that an endpoint is terminating the connection because it has received
|
||||
/// a message that contains a data that isn't consistent with the type of the message.
|
||||
/// </summary>
|
||||
InconsistentData = 1007,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1008.
|
||||
/// Indicates that an endpoint is terminating the connection because it has received
|
||||
/// a message that violates its policy.
|
||||
/// </summary>
|
||||
PolicyViolation = 1008,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1009.
|
||||
/// Indicates that an endpoint is terminating the connection because it has received
|
||||
/// a message that is too big to process.
|
||||
/// </summary>
|
||||
TooBig = 1009,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1010.
|
||||
/// Indicates that the client is terminating the connection because it has expected
|
||||
/// the server to negotiate one or more extension, but the server didn't return them
|
||||
/// in the handshake response.
|
||||
/// </summary>
|
||||
IgnoreExtension = 1010,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1011.
|
||||
/// Indicates that the server is terminating the connection because it has encountered
|
||||
/// an unexpected condition that prevented it from fulfilling the request.
|
||||
/// </summary>
|
||||
ServerError = 1011,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1015.
|
||||
/// Indicates that the connection was closed due to a failure to perform a TLS handshake.
|
||||
/// Reserved value.
|
||||
/// </summary>
|
||||
TlsHandshakeFailure = 1015
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the values of the compression method used to compress the message on the WebSocket
|
||||
/// connection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values of the compression method are defined in
|
||||
/// <see href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-09">Compression
|
||||
/// Extensions for WebSocket</see>.
|
||||
/// </remarks>
|
||||
public enum CompressionMethod : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates non compression.
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Indicates using DEFLATE.
|
||||
/// </summary>
|
||||
Deflate
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the event data associated with a <see cref="WebSocket.OnError"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A <see cref="WebSocket.OnError"/> event occurs when the <see cref="WebSocket"/> gets an error.
|
||||
/// If you would like to get the error message, you should access the <see cref="Message"/>
|
||||
/// property.
|
||||
/// </remarks>
|
||||
public class ErrorEventArgs : EventArgs
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private string _message;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
internal ErrorEventArgs(string message)
|
||||
{
|
||||
_message = message;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error message.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="string"/> that represents the error message.
|
||||
/// </value>
|
||||
public string Message => _message;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,947 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Services;
|
||||
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
|
||||
using WebSocketState = System.Net.WebSockets.WebSocketState;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a set of static methods for the websocket-sharp.
|
||||
/// </summary>
|
||||
public static class Ext
|
||||
{
|
||||
#region Private Const Fields
|
||||
|
||||
private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static MemoryStream compress(this Stream stream)
|
||||
{
|
||||
var output = new MemoryStream();
|
||||
if (stream.Length == 0)
|
||||
return output;
|
||||
|
||||
stream.Position = 0;
|
||||
using (var ds = new DeflateStream(output, CompressionMode.Compress, true))
|
||||
{
|
||||
stream.CopyTo(ds);
|
||||
//ds.Close(); // "BFINAL" set to 1.
|
||||
output.Position = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] decompress(this byte[] value)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
return value;
|
||||
|
||||
using (var input = new MemoryStream(value))
|
||||
{
|
||||
return input.decompressToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static MemoryStream decompress(this Stream stream)
|
||||
{
|
||||
var output = new MemoryStream();
|
||||
if (stream.Length == 0)
|
||||
return output;
|
||||
|
||||
stream.Position = 0;
|
||||
using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true))
|
||||
{
|
||||
ds.CopyTo(output, true);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] decompressToArray(this Stream stream)
|
||||
{
|
||||
using (var decomp = stream.decompress())
|
||||
{
|
||||
return decomp.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<byte[]> ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length)
|
||||
{
|
||||
var len = await stream.ReadAsync(buffer, offset, length).ConfigureAwait(false);
|
||||
if (len < 1)
|
||||
return buffer.SubArray(0, offset);
|
||||
|
||||
var tmp = 0;
|
||||
while (len < length)
|
||||
{
|
||||
tmp = await stream.ReadAsync(buffer, offset + len, length - len).ConfigureAwait(false);
|
||||
if (tmp < 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
len += tmp;
|
||||
}
|
||||
|
||||
return len < length
|
||||
? buffer.SubArray(0, offset + len)
|
||||
: buffer;
|
||||
}
|
||||
|
||||
private static async Task<bool> ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length, Stream dest)
|
||||
{
|
||||
var bytes = await stream.ReadBytesAsync(buffer, offset, length).ConfigureAwait(false);
|
||||
var len = bytes.Length;
|
||||
dest.Write(bytes, 0, len);
|
||||
|
||||
return len == offset + length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
internal static async Task<byte[]> AppendAsync(this ushort code, string reason)
|
||||
{
|
||||
using (var buffer = new MemoryStream())
|
||||
{
|
||||
var tmp = code.ToByteArrayInternally(ByteOrder.Big);
|
||||
await buffer.WriteAsync(tmp, 0, 2).ConfigureAwait(false);
|
||||
if (reason != null && reason.Length > 0)
|
||||
{
|
||||
tmp = Encoding.UTF8.GetBytes(reason);
|
||||
await buffer.WriteAsync(tmp, 0, tmp.Length).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal static string CheckIfClosable(this WebSocketState state)
|
||||
{
|
||||
return state == WebSocketState.CloseSent
|
||||
? "While closing the WebSocket connection."
|
||||
: state == WebSocketState.Closed
|
||||
? "The WebSocket connection has already been closed."
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static string CheckIfOpen(this WebSocketState state)
|
||||
{
|
||||
return state == WebSocketState.Connecting
|
||||
? "A WebSocket connection isn't established."
|
||||
: state == WebSocketState.CloseSent
|
||||
? "While closing the WebSocket connection."
|
||||
: state == WebSocketState.Closed
|
||||
? "The WebSocket connection has already been closed."
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static string CheckIfValidControlData(this byte[] data, string paramName)
|
||||
{
|
||||
return data.Length > 125
|
||||
? string.Format("'{0}' length must be less.", paramName)
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static Stream Compress(this Stream stream, CompressionMethod method)
|
||||
{
|
||||
return method == CompressionMethod.Deflate
|
||||
? stream.compress()
|
||||
: stream;
|
||||
}
|
||||
|
||||
internal static bool Contains<T>(this IEnumerable<T> source, Func<T, bool> condition)
|
||||
{
|
||||
foreach (T elm in source)
|
||||
if (condition(elm))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void CopyTo(this Stream src, Stream dest, bool setDefaultPosition)
|
||||
{
|
||||
var readLen = 0;
|
||||
var bufferLen = 256;
|
||||
var buffer = new byte[bufferLen];
|
||||
while ((readLen = src.Read(buffer, 0, bufferLen)) > 0)
|
||||
{
|
||||
dest.Write(buffer, 0, readLen);
|
||||
}
|
||||
|
||||
if (setDefaultPosition)
|
||||
dest.Position = 0;
|
||||
}
|
||||
|
||||
internal static byte[] Decompress(this byte[] value, CompressionMethod method)
|
||||
{
|
||||
return method == CompressionMethod.Deflate
|
||||
? value.decompress()
|
||||
: value;
|
||||
}
|
||||
|
||||
internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method)
|
||||
{
|
||||
return method == CompressionMethod.Deflate
|
||||
? stream.decompressToArray()
|
||||
: stream.ToByteArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="int"/> equals the specified <see cref="char"/>,
|
||||
/// and invokes the specified Action<int> delegate at the same time.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="value"/> equals <paramref name="c"/>;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// An <see cref="int"/> to compare.
|
||||
/// </param>
|
||||
/// <param name="c">
|
||||
/// A <see cref="char"/> to compare.
|
||||
/// </param>
|
||||
/// <param name="action">
|
||||
/// An Action<int> delegate that references the method(s) called at
|
||||
/// the same time as comparing. An <see cref="int"/> parameter to pass to
|
||||
/// the method(s) is <paramref name="value"/>.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="value"/> isn't between 0 and 255.
|
||||
/// </exception>
|
||||
internal static bool EqualsWith(this int value, char c, Action<int> action)
|
||||
{
|
||||
if (value < 0 || value > 255)
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
|
||||
action(value);
|
||||
return value == c - 0;
|
||||
}
|
||||
|
||||
internal static string GetMessage(this CloseStatusCode code)
|
||||
{
|
||||
return code == CloseStatusCode.ProtocolError
|
||||
? "A WebSocket protocol error has occurred."
|
||||
: code == CloseStatusCode.IncorrectData
|
||||
? "An incorrect data has been received."
|
||||
: code == CloseStatusCode.Abnormal
|
||||
? "An exception has occurred."
|
||||
: code == CloseStatusCode.InconsistentData
|
||||
? "An inconsistent data has been received."
|
||||
: code == CloseStatusCode.PolicyViolation
|
||||
? "A policy violation has occurred."
|
||||
: code == CloseStatusCode.TooBig
|
||||
? "A too big data has been received."
|
||||
: code == CloseStatusCode.IgnoreExtension
|
||||
? "WebSocket client did not receive expected extension(s)."
|
||||
: code == CloseStatusCode.ServerError
|
||||
? "WebSocket server got an internal error."
|
||||
: code == CloseStatusCode.TlsHandshakeFailure
|
||||
? "An error has occurred while handshaking."
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
internal static string GetNameInternal(this string nameAndValue, string separator)
|
||||
{
|
||||
var i = nameAndValue.IndexOf(separator);
|
||||
return i > 0
|
||||
? nameAndValue.Substring(0, i).Trim()
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static string GetValueInternal(this string nameAndValue, string separator)
|
||||
{
|
||||
var i = nameAndValue.IndexOf(separator);
|
||||
return i >= 0 && i < nameAndValue.Length - 1
|
||||
? nameAndValue.Substring(i + 1).Trim()
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static bool IsCompressionExtension(this string value, CompressionMethod method)
|
||||
{
|
||||
return value.StartsWith(method.ToExtensionString());
|
||||
}
|
||||
|
||||
internal static bool IsPortNumber(this int value)
|
||||
{
|
||||
return value > 0 && value < 65536;
|
||||
}
|
||||
|
||||
internal static bool IsReserved(this ushort code)
|
||||
{
|
||||
return code == (ushort)CloseStatusCode.Undefined ||
|
||||
code == (ushort)CloseStatusCode.NoStatusCode ||
|
||||
code == (ushort)CloseStatusCode.Abnormal ||
|
||||
code == (ushort)CloseStatusCode.TlsHandshakeFailure;
|
||||
}
|
||||
|
||||
internal static bool IsReserved(this CloseStatusCode code)
|
||||
{
|
||||
return code == CloseStatusCode.Undefined ||
|
||||
code == CloseStatusCode.NoStatusCode ||
|
||||
code == CloseStatusCode.Abnormal ||
|
||||
code == CloseStatusCode.TlsHandshakeFailure;
|
||||
}
|
||||
|
||||
internal static bool IsText(this string value)
|
||||
{
|
||||
var len = value.Length;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
char c = value[i];
|
||||
if (c < 0x20 && !"\r\n\t".Contains(c))
|
||||
return false;
|
||||
|
||||
if (c == 0x7f)
|
||||
return false;
|
||||
|
||||
if (c == '\n' && ++i < len)
|
||||
{
|
||||
c = value[i];
|
||||
if (!" \t".Contains(c))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool IsToken(this string value)
|
||||
{
|
||||
foreach (char c in value)
|
||||
if (c < 0x20 || c >= 0x7f || _tspecials.Contains(c))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static string Quote(this string value)
|
||||
{
|
||||
return value.IsToken()
|
||||
? value
|
||||
: string.Format("\"{0}\"", value.Replace("\"", "\\\""));
|
||||
}
|
||||
|
||||
internal static Task<byte[]> ReadBytesAsync(this Stream stream, int length)
|
||||
=> stream.ReadBytesAsync(new byte[length], 0, length);
|
||||
|
||||
internal static async Task<byte[]> ReadBytesAsync(this Stream stream, long length, int bufferLength)
|
||||
{
|
||||
using (var result = new MemoryStream())
|
||||
{
|
||||
var count = length / bufferLength;
|
||||
var rem = (int)(length % bufferLength);
|
||||
|
||||
var buffer = new byte[bufferLength];
|
||||
var end = false;
|
||||
for (long i = 0; i < count; i++)
|
||||
{
|
||||
if (!await stream.ReadBytesAsync(buffer, 0, bufferLength, result).ConfigureAwait(false))
|
||||
{
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!end && rem > 0)
|
||||
{
|
||||
await stream.ReadBytesAsync(new byte[rem], 0, rem, result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal static string RemovePrefix(this string value, params string[] prefixes)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var prefix in prefixes)
|
||||
{
|
||||
if (value.StartsWith(prefix))
|
||||
{
|
||||
i = prefix.Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i > 0
|
||||
? value.Substring(i)
|
||||
: value;
|
||||
}
|
||||
|
||||
internal static T[] Reverse<T>(this T[] array)
|
||||
{
|
||||
var len = array.Length;
|
||||
T[] reverse = new T[len];
|
||||
|
||||
var end = len - 1;
|
||||
for (var i = 0; i <= end; i++)
|
||||
reverse[i] = array[end - i];
|
||||
|
||||
return reverse;
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> SplitHeaderValue(
|
||||
this string value, params char[] separator)
|
||||
{
|
||||
var len = value.Length;
|
||||
var separators = new string(separator);
|
||||
|
||||
var buffer = new StringBuilder(32);
|
||||
var quoted = false;
|
||||
var escaped = false;
|
||||
|
||||
char c;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
c = value[i];
|
||||
if (c == '"')
|
||||
{
|
||||
if (escaped)
|
||||
escaped = !escaped;
|
||||
else
|
||||
quoted = !quoted;
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
if (i < len - 1 && value[i + 1] == '"')
|
||||
escaped = true;
|
||||
}
|
||||
else if (separators.Contains(c))
|
||||
{
|
||||
if (!quoted)
|
||||
{
|
||||
yield return buffer.ToString();
|
||||
buffer.Length = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
buffer.Append(c);
|
||||
}
|
||||
|
||||
if (buffer.Length > 0)
|
||||
yield return buffer.ToString();
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArray(this Stream stream)
|
||||
{
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
stream.Position = 0;
|
||||
stream.CopyTo(output);
|
||||
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArrayInternally(this ushort value, ByteOrder order)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
if (!order.IsHostOrder())
|
||||
Array.Reverse(bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArrayInternally(this ulong value, ByteOrder order)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
if (!order.IsHostOrder())
|
||||
Array.Reverse(bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
internal static string ToExtensionString(
|
||||
this CompressionMethod method, params string[] parameters)
|
||||
{
|
||||
if (method == CompressionMethod.None)
|
||||
return string.Empty;
|
||||
|
||||
var m = string.Format("permessage-{0}", method.ToString().ToLowerInvariant());
|
||||
if (parameters == null || parameters.Length == 0)
|
||||
return m;
|
||||
|
||||
return string.Format("{0}; {1}", m, parameters.ToString("; "));
|
||||
}
|
||||
|
||||
internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder)
|
||||
{
|
||||
src.ToHostOrder(srcOrder);
|
||||
return BitConverter.ToUInt16(src, 0);
|
||||
}
|
||||
|
||||
internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder)
|
||||
{
|
||||
src.ToHostOrder(srcOrder);
|
||||
return BitConverter.ToUInt64(src, 0);
|
||||
}
|
||||
|
||||
internal static string TrimEndSlash(this string value)
|
||||
{
|
||||
value = value.TrimEnd('/');
|
||||
return value.Length > 0
|
||||
? value
|
||||
: "/";
|
||||
}
|
||||
|
||||
internal static string Unquote(this string value)
|
||||
{
|
||||
var start = value.IndexOf('\"');
|
||||
var end = value.LastIndexOf('\"');
|
||||
if (start < end)
|
||||
value = value.Substring(start + 1, end - start - 1).Replace("\\\"", "\"");
|
||||
|
||||
return value.Trim();
|
||||
}
|
||||
|
||||
internal static void WriteBytes(this Stream stream, byte[] value)
|
||||
{
|
||||
using (var src = new MemoryStream(value))
|
||||
{
|
||||
src.CopyTo(stream);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="string"/> contains any of characters
|
||||
/// in the specified array of <see cref="char"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> to test.
|
||||
/// </param>
|
||||
/// <param name="chars">
|
||||
/// An array of <see cref="char"/> that contains characters to find.
|
||||
/// </param>
|
||||
public static bool Contains(this string value, params char[] chars)
|
||||
{
|
||||
return chars == null || chars.Length == 0
|
||||
? true
|
||||
: value == null || value.Length == 0
|
||||
? false
|
||||
: value.IndexOfAny(chars) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry
|
||||
/// with the specified <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="collection"/> contains the entry
|
||||
/// with <paramref name="name"/>; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="collection">
|
||||
/// A <see cref="QueryParamCollection"/> to test.
|
||||
/// </param>
|
||||
/// <param name="name">
|
||||
/// A <see cref="string"/> that represents the key of the entry to find.
|
||||
/// </param>
|
||||
public static bool Contains(this QueryParamCollection collection, string name)
|
||||
{
|
||||
return collection == null || collection.Count == 0
|
||||
? false
|
||||
: collection[name] != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry
|
||||
/// with the specified both <paramref name="name"/> and <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="collection"/> contains the entry
|
||||
/// with both <paramref name="name"/> and <paramref name="value"/>;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="collection">
|
||||
/// A <see cref="QueryParamCollection"/> to test.
|
||||
/// </param>
|
||||
/// <param name="name">
|
||||
/// A <see cref="string"/> that represents the key of the entry to find.
|
||||
/// </param>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> that represents the value of the entry to find.
|
||||
/// </param>
|
||||
public static bool Contains(this QueryParamCollection collection, string name, string value)
|
||||
{
|
||||
if (collection == null || collection.Count == 0)
|
||||
return false;
|
||||
|
||||
var values = collection[name];
|
||||
if (values == null)
|
||||
return false;
|
||||
|
||||
foreach (var v in values.Split(','))
|
||||
if (v.Trim().Equals(value, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits the specified <c>EventHandler<TEventArgs></c> delegate
|
||||
/// if it isn't <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">
|
||||
/// An <c>EventHandler<TEventArgs></c> to emit.
|
||||
/// </param>
|
||||
/// <param name="sender">
|
||||
/// An <see cref="object"/> from which emits this <paramref name="eventHandler"/>.
|
||||
/// </param>
|
||||
/// <param name="e">
|
||||
/// A <c>TEventArgs</c> that represents the event data.
|
||||
/// </param>
|
||||
/// <typeparam name="TEventArgs">
|
||||
/// The type of the event data generated by the event.
|
||||
/// </typeparam>
|
||||
public static void Emit<TEventArgs>(
|
||||
this EventHandler<TEventArgs> eventHandler, object sender, TEventArgs e)
|
||||
where TEventArgs : EventArgs
|
||||
{
|
||||
if (eventHandler != null)
|
||||
eventHandler(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the specified HTTP status <paramref name="code"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that represents the description of the HTTP status code.
|
||||
/// </returns>
|
||||
/// <param name="code">
|
||||
/// One of <see cref="HttpStatusCode"/> enum values, indicates the HTTP status codes.
|
||||
/// </param>
|
||||
public static string GetDescription(this HttpStatusCode code)
|
||||
{
|
||||
return ((int)code).GetStatusDescription();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the specified HTTP status <paramref name="code"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that represents the description of the HTTP status code.
|
||||
/// </returns>
|
||||
/// <param name="code">
|
||||
/// An <see cref="int"/> that represents the HTTP status code.
|
||||
/// </param>
|
||||
public static string GetStatusDescription(this int code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocols";
|
||||
case 102: return "Processing";
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 300: return "Multiple Choices";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 307: return "Temporary Redirect";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Timeout";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Request Entity Too Large";
|
||||
case 414: return "Request-Uri Too Long";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Requested Range Not Satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 422: return "Unprocessable Entity";
|
||||
case 423: return "Locked";
|
||||
case 424: return "Failed Dependency";
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Timeout";
|
||||
case 505: return "Http Version Not Supported";
|
||||
case 507: return "Insufficient Storage";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="ByteOrder"/> is host
|
||||
/// (this computer architecture) byte order.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="order"/> is host byte order;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="order">
|
||||
/// One of the <see cref="ByteOrder"/> enum values, to test.
|
||||
/// </param>
|
||||
public static bool IsHostOrder(this ByteOrder order)
|
||||
{
|
||||
// true : !(true ^ true) or !(false ^ false)
|
||||
// false: !(true ^ false) or !(false ^ true)
|
||||
return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="string"/> is a predefined scheme.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="value"/> is a predefined scheme; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> to test.
|
||||
/// </param>
|
||||
public static bool IsPredefinedScheme(this string value)
|
||||
{
|
||||
if (value == null || value.Length < 2)
|
||||
return false;
|
||||
|
||||
var c = value[0];
|
||||
if (c == 'h')
|
||||
return value == "http" || value == "https";
|
||||
|
||||
if (c == 'w')
|
||||
return value == "ws" || value == "wss";
|
||||
|
||||
if (c == 'f')
|
||||
return value == "file" || value == "ftp";
|
||||
|
||||
if (c == 'n')
|
||||
{
|
||||
c = value[1];
|
||||
return c == 'e'
|
||||
? value == "news" || value == "net.pipe" || value == "net.tcp"
|
||||
: value == "nntp";
|
||||
}
|
||||
|
||||
return (c == 'g' && value == "gopher") || (c == 'm' && value == "mailto");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="string"/> is a URI string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="value"/> may be a URI string; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> to test.
|
||||
/// </param>
|
||||
public static bool MaybeUri(this string value)
|
||||
{
|
||||
if (value == null || value.Length == 0)
|
||||
return false;
|
||||
|
||||
var i = value.IndexOf(':');
|
||||
if (i == -1)
|
||||
return false;
|
||||
|
||||
if (i >= 10)
|
||||
return false;
|
||||
|
||||
return value.Substring(0, i).IsPredefinedScheme();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a sub-array from the specified <paramref name="array"/>.
|
||||
/// A sub-array starts at the specified element position.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An array of T that receives a sub-array, or an empty array of T if any problems
|
||||
/// with the parameters.
|
||||
/// </returns>
|
||||
/// <param name="array">
|
||||
/// An array of T that contains the data to retrieve a sub-array.
|
||||
/// </param>
|
||||
/// <param name="startIndex">
|
||||
/// An <see cref="int"/> that contains the zero-based starting position of a sub-array
|
||||
/// in <paramref name="array"/>.
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// An <see cref="int"/> that contains the number of elements to retrieve a sub-array.
|
||||
/// </param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of elements in the <paramref name="array"/>.
|
||||
/// </typeparam>
|
||||
public static T[] SubArray<T>(this T[] array, int startIndex, int length)
|
||||
{
|
||||
if (array == null || array.Length == 0)
|
||||
return new T[0];
|
||||
|
||||
if (startIndex < 0 || length <= 0)
|
||||
return new T[0];
|
||||
|
||||
if (startIndex + length > array.Length)
|
||||
return new T[0];
|
||||
|
||||
if (startIndex == 0 && array.Length == length)
|
||||
return array;
|
||||
|
||||
T[] subArray = new T[length];
|
||||
Array.Copy(array, startIndex, subArray, 0, length);
|
||||
|
||||
return subArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the order of the specified array of <see cref="byte"/> to the host byte order.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An array of <see cref="byte"/> converted from <paramref name="src"/>.
|
||||
/// </returns>
|
||||
/// <param name="src">
|
||||
/// An array of <see cref="byte"/> to convert.
|
||||
/// </param>
|
||||
/// <param name="srcOrder">
|
||||
/// One of the <see cref="ByteOrder"/> enum values, indicates the byte order of
|
||||
/// <paramref name="src"/>.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="src"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
public static void ToHostOrder(this byte[] src, ByteOrder srcOrder)
|
||||
{
|
||||
if (src == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(src));
|
||||
}
|
||||
|
||||
if (src.Length > 1 && !srcOrder.IsHostOrder())
|
||||
{
|
||||
Array.Reverse(src);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified <paramref name="array"/> to a <see cref="string"/> that
|
||||
/// concatenates the each element of <paramref name="array"/> across the specified
|
||||
/// <paramref name="separator"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> converted from <paramref name="array"/>,
|
||||
/// or <see cref="String.Empty"/> if <paramref name="array"/> is empty.
|
||||
/// </returns>
|
||||
/// <param name="array">
|
||||
/// An array of T to convert.
|
||||
/// </param>
|
||||
/// <param name="separator">
|
||||
/// A <see cref="string"/> that represents the separator string.
|
||||
/// </param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of elements in <paramref name="array"/>.
|
||||
/// </typeparam>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="array"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
public static string ToString<T>(this T[] array, string separator)
|
||||
{
|
||||
if (array == null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
|
||||
var len = array.Length;
|
||||
if (len == 0)
|
||||
return string.Empty;
|
||||
|
||||
if (separator == null)
|
||||
separator = string.Empty;
|
||||
|
||||
var buff = new StringBuilder(64);
|
||||
(len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator));
|
||||
|
||||
buff.Append(array[len - 1].ToString());
|
||||
return buff.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified <c>Action<int></c> delegate <paramref name="n"/> times.
|
||||
/// </summary>
|
||||
/// <param name="n">
|
||||
/// An <see cref="int"/> is the number of times to execute.
|
||||
/// </param>
|
||||
/// <param name="action">
|
||||
/// An <c>Action<int></c> delegate that references the method(s) to execute.
|
||||
/// An <see cref="int"/> parameter to pass to the method(s) is the zero-based count of
|
||||
/// iteration.
|
||||
/// </param>
|
||||
public static void Times(this int n, Action<int> action)
|
||||
{
|
||||
if (n > 0 && action != null)
|
||||
for (int i = 0; i < n; i++)
|
||||
action(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified <see cref="string"/> to a <see cref="Uri"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or <see langword="null"/>
|
||||
/// if <paramref name="uriString"/> isn't successfully converted.
|
||||
/// </returns>
|
||||
/// <param name="uriString">
|
||||
/// A <see cref="string"/> to convert.
|
||||
/// </param>
|
||||
public static Uri ToUri(this string uriString)
|
||||
{
|
||||
return Uri.TryCreate(
|
||||
uriString, uriString.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out var res)
|
||||
? res
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL-decodes the specified <see cref="string"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that receives the decoded string, or the <paramref name="value"/>
|
||||
/// if it's <see langword="null"/> or empty.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> to decode.
|
||||
/// </param>
|
||||
public static string UrlDecode(this string value)
|
||||
{
|
||||
return value == null || value.Length == 0
|
||||
? value
|
||||
: WebUtility.UrlDecode(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
internal enum Fin : byte
|
||||
{
|
||||
More = 0x0,
|
||||
Final = 0x1
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
internal abstract class HttpBase
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private QueryParamCollection _headers;
|
||||
private Version _version;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Fields
|
||||
|
||||
protected const string CrLf = "\r\n";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Constructors
|
||||
|
||||
protected HttpBase(Version version, QueryParamCollection headers)
|
||||
{
|
||||
_version = version;
|
||||
_headers = headers;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public QueryParamCollection Headers => _headers;
|
||||
|
||||
public Version ProtocolVersion => _version;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static Encoding getEncoding(string contentType)
|
||||
{
|
||||
if (contentType == null || contentType.Length == 0)
|
||||
return Encoding.UTF8;
|
||||
|
||||
var i = contentType.IndexOf("charset=", StringComparison.Ordinal);
|
||||
if (i == -1)
|
||||
return Encoding.UTF8;
|
||||
|
||||
var charset = contentType.Substring(i + 8);
|
||||
i = charset.IndexOf(';');
|
||||
if (i != -1)
|
||||
charset = charset.Substring(0, i).TrimEnd();
|
||||
|
||||
return Encoding.GetEncoding(charset.Trim('"'));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.Services;
|
||||
using SocketHttpListener.Net;
|
||||
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
|
||||
using HttpVersion = SocketHttpListener.Net.HttpVersion;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
internal class HttpResponse : HttpBase
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private string _code;
|
||||
private string _reason;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Constructors
|
||||
|
||||
private HttpResponse(string code, string reason, Version version, QueryParamCollection headers)
|
||||
: base(version, headers)
|
||||
{
|
||||
_code = code;
|
||||
_reason = reason;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
internal HttpResponse(HttpStatusCode code)
|
||||
: this(code, code.GetDescription())
|
||||
{
|
||||
}
|
||||
|
||||
internal HttpResponse(HttpStatusCode code, string reason)
|
||||
: this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection())
|
||||
{
|
||||
Headers["Server"] = "websocket-sharp/1.0";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public CookieCollection Cookies => GetCookies(Headers, true);
|
||||
|
||||
private static CookieCollection GetCookies(QueryParamCollection headers, bool response)
|
||||
{
|
||||
var name = response ? "Set-Cookie" : "Cookie";
|
||||
return headers == null || !headers.Contains(name)
|
||||
? new CookieCollection()
|
||||
: CookieHelper.Parse(headers[name], response);
|
||||
}
|
||||
|
||||
public bool IsProxyAuthenticationRequired => _code == "407";
|
||||
|
||||
public bool IsUnauthorized => _code == "401";
|
||||
|
||||
public bool IsWebSocketResponse
|
||||
{
|
||||
get
|
||||
{
|
||||
var headers = Headers;
|
||||
return ProtocolVersion > HttpVersion.Version10 &&
|
||||
_code == "101" &&
|
||||
headers.Contains("Upgrade", "websocket") &&
|
||||
headers.Contains("Connection", "Upgrade");
|
||||
}
|
||||
}
|
||||
|
||||
public string Reason => _reason;
|
||||
|
||||
public string StatusCode => _code;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
internal static HttpResponse CreateCloseResponse(HttpStatusCode code)
|
||||
{
|
||||
var res = new HttpResponse(code);
|
||||
res.Headers["Connection"] = "close";
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void SetCookies(CookieCollection cookies)
|
||||
{
|
||||
if (cookies == null || cookies.Count == 0)
|
||||
return;
|
||||
|
||||
var headers = Headers;
|
||||
var sorted = cookies.OfType<Cookie>().OrderBy(i => i.Name).ToList();
|
||||
|
||||
foreach (var cookie in sorted)
|
||||
headers.Add("Set-Cookie", cookie.ToString());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var output = new StringBuilder(64);
|
||||
output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf);
|
||||
|
||||
var headers = Headers;
|
||||
foreach (var key in headers.Keys)
|
||||
output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf);
|
||||
|
||||
output.Append(CrLf);
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
internal enum Mask : byte
|
||||
{
|
||||
Unmask = 0x0,
|
||||
Mask = 0x1
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the event data associated with a <see cref="WebSocket.OnMessage"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A <see cref="WebSocket.OnMessage"/> event occurs when the <see cref="WebSocket"/> receives
|
||||
/// a text or binary data frame.
|
||||
/// If you want to get the received data, you access the <see cref="Data"/> or
|
||||
/// <see cref="RawData"/> property.
|
||||
/// </remarks>
|
||||
public class MessageEventArgs : EventArgs
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private string _data;
|
||||
private Opcode _opcode;
|
||||
private byte[] _rawData;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
internal MessageEventArgs(Opcode opcode, byte[] data)
|
||||
{
|
||||
_opcode = opcode;
|
||||
_rawData = data;
|
||||
_data = convertToString(opcode, data);
|
||||
}
|
||||
|
||||
internal MessageEventArgs(Opcode opcode, PayloadData payload)
|
||||
{
|
||||
_opcode = opcode;
|
||||
_rawData = payload.ApplicationData;
|
||||
_data = convertToString(opcode, _rawData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the received data as a <see cref="string"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="string"/> that contains the received data.
|
||||
/// </value>
|
||||
public string Data => _data;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the received data as an array of <see cref="byte"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// An array of <see cref="byte"/> that contains the received data.
|
||||
/// </value>
|
||||
public byte[] RawData => _rawData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the received data.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// One of the <see cref="Opcode"/> values, indicates the type of the received data.
|
||||
/// </value>
|
||||
public Opcode Type => _opcode;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static string convertToString(Opcode opcode, byte[] data)
|
||||
{
|
||||
return data.Length == 0
|
||||
? string.Empty
|
||||
: opcode == Opcode.Text
|
||||
? Encoding.UTF8.GetString(data, 0, data.Length)
|
||||
: opcode.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
using System.Net;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public delegate AuthenticationSchemes AuthenticationSchemeSelector(HttpListenerRequest httpRequest);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal class AuthenticationTypes
|
||||
{
|
||||
internal const string NTLM = "NTLM";
|
||||
internal const string Negotiate = "Negotiate";
|
||||
internal const string Basic = "Basic";
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal enum BoundaryType
|
||||
{
|
||||
ContentLength = 0, // Content-Length: XXX
|
||||
Chunked = 1, // Transfer-Encoding: chunked
|
||||
Multipart = 3,
|
||||
None = 4,
|
||||
Invalid = 5,
|
||||
}
|
||||
}
|
@ -1,385 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
//
|
||||
// System.Net.ResponseStream
|
||||
//
|
||||
// Author:
|
||||
// Gonzalo Paniagua Javier (gonzalo@novell.com)
|
||||
//
|
||||
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
internal sealed class ChunkStream
|
||||
{
|
||||
private enum State
|
||||
{
|
||||
None,
|
||||
PartialSize,
|
||||
Body,
|
||||
BodyFinished,
|
||||
Trailer
|
||||
}
|
||||
|
||||
private class Chunk
|
||||
{
|
||||
public byte[] Bytes;
|
||||
public int Offset;
|
||||
|
||||
public Chunk(byte[] chunk)
|
||||
{
|
||||
Bytes = chunk;
|
||||
}
|
||||
|
||||
public int Read(byte[] buffer, int offset, int size)
|
||||
{
|
||||
int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size;
|
||||
Buffer.BlockCopy(Bytes, Offset, buffer, offset, nread);
|
||||
Offset += nread;
|
||||
return nread;
|
||||
}
|
||||
}
|
||||
|
||||
internal WebHeaderCollection _headers;
|
||||
private int _chunkSize;
|
||||
private int _chunkRead;
|
||||
private int _totalWritten;
|
||||
private State _state;
|
||||
private StringBuilder _saved;
|
||||
private bool _sawCR;
|
||||
private bool _gotit;
|
||||
private int _trailerState;
|
||||
private List<Chunk> _chunks;
|
||||
|
||||
public ChunkStream(WebHeaderCollection headers)
|
||||
{
|
||||
_headers = headers;
|
||||
_saved = new StringBuilder();
|
||||
_chunks = new List<Chunk>();
|
||||
_chunkSize = -1;
|
||||
_totalWritten = 0;
|
||||
}
|
||||
|
||||
public void ResetBuffer()
|
||||
{
|
||||
_chunkSize = -1;
|
||||
_chunkRead = 0;
|
||||
_totalWritten = 0;
|
||||
_chunks.Clear();
|
||||
}
|
||||
|
||||
public int Read(byte[] buffer, int offset, int size)
|
||||
{
|
||||
return ReadFromChunks(buffer, offset, size);
|
||||
}
|
||||
|
||||
private int ReadFromChunks(byte[] buffer, int offset, int size)
|
||||
{
|
||||
int count = _chunks.Count;
|
||||
int nread = 0;
|
||||
|
||||
var chunksForRemoving = new List<Chunk>(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var chunk = _chunks[i];
|
||||
|
||||
if (chunk.Offset == chunk.Bytes.Length)
|
||||
{
|
||||
chunksForRemoving.Add(chunk);
|
||||
continue;
|
||||
}
|
||||
|
||||
nread += chunk.Read(buffer, offset + nread, size - nread);
|
||||
if (nread == size)
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var chunk in chunksForRemoving)
|
||||
_chunks.Remove(chunk);
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
public void Write(byte[] buffer, int offset, int size)
|
||||
{
|
||||
// Note, the logic here only works when offset is 0 here.
|
||||
// Otherwise, it would treat "size" as the end offset instead of an actual byte count from offset.
|
||||
|
||||
if (offset < size)
|
||||
InternalWrite(buffer, ref offset, size);
|
||||
}
|
||||
|
||||
private void InternalWrite(byte[] buffer, ref int offset, int size)
|
||||
{
|
||||
if (_state == State.None || _state == State.PartialSize)
|
||||
{
|
||||
_state = GetChunkSize(buffer, ref offset, size);
|
||||
if (_state == State.PartialSize)
|
||||
return;
|
||||
|
||||
_saved.Length = 0;
|
||||
_sawCR = false;
|
||||
_gotit = false;
|
||||
}
|
||||
|
||||
if (_state == State.Body && offset < size)
|
||||
{
|
||||
_state = ReadBody(buffer, ref offset, size);
|
||||
if (_state == State.Body)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_state == State.BodyFinished && offset < size)
|
||||
{
|
||||
_state = ReadCRLF(buffer, ref offset, size);
|
||||
if (_state == State.BodyFinished)
|
||||
return;
|
||||
|
||||
_sawCR = false;
|
||||
}
|
||||
|
||||
if (_state == State.Trailer && offset < size)
|
||||
{
|
||||
_state = ReadTrailer(buffer, ref offset, size);
|
||||
if (_state == State.Trailer)
|
||||
return;
|
||||
|
||||
_saved.Length = 0;
|
||||
_sawCR = false;
|
||||
_gotit = false;
|
||||
}
|
||||
|
||||
if (offset < size)
|
||||
InternalWrite(buffer, ref offset, size);
|
||||
}
|
||||
|
||||
public bool WantMore => (_chunkRead != _chunkSize || _chunkSize != 0 || _state != State.None);
|
||||
|
||||
public bool DataAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = _chunks.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var ch = _chunks[i];
|
||||
if (ch == null || ch.Bytes == null)
|
||||
continue;
|
||||
if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length)
|
||||
return (_state != State.Body);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int TotalDataSize => _totalWritten;
|
||||
|
||||
public int ChunkLeft => _chunkSize - _chunkRead;
|
||||
|
||||
private State ReadBody(byte[] buffer, ref int offset, int size)
|
||||
{
|
||||
if (_chunkSize == 0)
|
||||
return State.BodyFinished;
|
||||
|
||||
int diff = size - offset;
|
||||
if (diff + _chunkRead > _chunkSize)
|
||||
diff = _chunkSize - _chunkRead;
|
||||
|
||||
byte[] chunk = new byte[diff];
|
||||
Buffer.BlockCopy(buffer, offset, chunk, 0, diff);
|
||||
_chunks.Add(new Chunk(chunk));
|
||||
offset += diff;
|
||||
_chunkRead += diff;
|
||||
_totalWritten += diff;
|
||||
|
||||
return (_chunkRead == _chunkSize) ? State.BodyFinished : State.Body;
|
||||
}
|
||||
|
||||
private State GetChunkSize(byte[] buffer, ref int offset, int size)
|
||||
{
|
||||
_chunkRead = 0;
|
||||
_chunkSize = 0;
|
||||
char c = '\0';
|
||||
while (offset < size)
|
||||
{
|
||||
c = (char)buffer[offset++];
|
||||
if (c == '\r')
|
||||
{
|
||||
if (_sawCR)
|
||||
ThrowProtocolViolation("2 CR found");
|
||||
|
||||
_sawCR = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_sawCR && c == '\n')
|
||||
break;
|
||||
|
||||
if (c == ' ')
|
||||
_gotit = true;
|
||||
|
||||
if (!_gotit)
|
||||
_saved.Append(c);
|
||||
|
||||
if (_saved.Length > 20)
|
||||
ThrowProtocolViolation("chunk size too long.");
|
||||
}
|
||||
|
||||
if (!_sawCR || c != '\n')
|
||||
{
|
||||
if (offset < size)
|
||||
ThrowProtocolViolation("Missing \\n");
|
||||
|
||||
try
|
||||
{
|
||||
if (_saved.Length > 0)
|
||||
{
|
||||
_chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ThrowProtocolViolation("Cannot parse chunk size.");
|
||||
}
|
||||
|
||||
return State.PartialSize;
|
||||
}
|
||||
|
||||
_chunkRead = 0;
|
||||
try
|
||||
{
|
||||
_chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ThrowProtocolViolation("Cannot parse chunk size.");
|
||||
}
|
||||
|
||||
if (_chunkSize == 0)
|
||||
{
|
||||
_trailerState = 2;
|
||||
return State.Trailer;
|
||||
}
|
||||
|
||||
return State.Body;
|
||||
}
|
||||
|
||||
private static string RemoveChunkExtension(string input)
|
||||
{
|
||||
int idx = input.IndexOf(';');
|
||||
if (idx == -1)
|
||||
return input;
|
||||
return input.Substring(0, idx);
|
||||
}
|
||||
|
||||
private State ReadCRLF(byte[] buffer, ref int offset, int size)
|
||||
{
|
||||
if (!_sawCR)
|
||||
{
|
||||
if ((char)buffer[offset++] != '\r')
|
||||
ThrowProtocolViolation("Expecting \\r");
|
||||
|
||||
_sawCR = true;
|
||||
if (offset == size)
|
||||
return State.BodyFinished;
|
||||
}
|
||||
|
||||
if (_sawCR && (char)buffer[offset++] != '\n')
|
||||
ThrowProtocolViolation("Expecting \\n");
|
||||
|
||||
return State.None;
|
||||
}
|
||||
|
||||
private State ReadTrailer(byte[] buffer, ref int offset, int size)
|
||||
{
|
||||
char c = '\0';
|
||||
|
||||
// short path
|
||||
if (_trailerState == 2 && (char)buffer[offset] == '\r' && _saved.Length == 0)
|
||||
{
|
||||
offset++;
|
||||
if (offset < size && (char)buffer[offset] == '\n')
|
||||
{
|
||||
offset++;
|
||||
return State.None;
|
||||
}
|
||||
offset--;
|
||||
}
|
||||
|
||||
int st = _trailerState;
|
||||
string stString = "\r\n\r";
|
||||
while (offset < size && st < 4)
|
||||
{
|
||||
c = (char)buffer[offset++];
|
||||
if ((st == 0 || st == 2) && c == '\r')
|
||||
{
|
||||
st++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((st == 1 || st == 3) && c == '\n')
|
||||
{
|
||||
st++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (st > 0)
|
||||
{
|
||||
_saved.Append(stString.Substring(0, _saved.Length == 0 ? st - 2 : st));
|
||||
st = 0;
|
||||
if (_saved.Length > 4196)
|
||||
ThrowProtocolViolation("Error reading trailer (too long).");
|
||||
}
|
||||
}
|
||||
|
||||
if (st < 4)
|
||||
{
|
||||
_trailerState = st;
|
||||
if (offset < size)
|
||||
ThrowProtocolViolation("Error reading trailer.");
|
||||
|
||||
return State.Trailer;
|
||||
}
|
||||
|
||||
var reader = new StringReader(_saved.ToString());
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null && line != "")
|
||||
_headers.Add(line);
|
||||
|
||||
return State.None;
|
||||
}
|
||||
|
||||
private static void ThrowProtocolViolation(string message)
|
||||
{
|
||||
var we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null);
|
||||
throw we;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public static class CookieHelper
|
||||
{
|
||||
internal static CookieCollection Parse(string value, bool response)
|
||||
{
|
||||
return response
|
||||
? parseResponse(value)
|
||||
: null;
|
||||
}
|
||||
|
||||
private static string[] splitCookieHeaderValue(string value)
|
||||
{
|
||||
return new List<string>(value.SplitHeaderValue(',', ';')).ToArray();
|
||||
}
|
||||
|
||||
private static CookieCollection parseResponse(string value)
|
||||
{
|
||||
var cookies = new CookieCollection();
|
||||
|
||||
Cookie cookie = null;
|
||||
var pairs = splitCookieHeaderValue(value);
|
||||
for (int i = 0; i < pairs.Length; i++)
|
||||
{
|
||||
var pair = pairs[i].Trim();
|
||||
if (pair.Length == 0)
|
||||
continue;
|
||||
|
||||
if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Version = int.Parse(pair.GetValueInternal("=").Trim('"'));
|
||||
}
|
||||
else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var buffer = new StringBuilder(pair.GetValueInternal("="), 32);
|
||||
if (i < pairs.Length - 1)
|
||||
buffer.AppendFormat(", {0}", pairs[++i].Trim());
|
||||
|
||||
if (!DateTime.TryParseExact(
|
||||
buffer.ToString(),
|
||||
new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" },
|
||||
new CultureInfo("en-US"),
|
||||
DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal,
|
||||
out var expires))
|
||||
expires = DateTime.Now;
|
||||
|
||||
if (cookie != null && cookie.Expires == DateTime.MinValue)
|
||||
cookie.Expires = expires.ToLocalTime();
|
||||
}
|
||||
else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var max = int.Parse(pair.GetValueInternal("=").Trim('"'));
|
||||
var expires = DateTime.Now.AddSeconds((double)max);
|
||||
if (cookie != null)
|
||||
cookie.Expires = expires;
|
||||
}
|
||||
else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Path = pair.GetValueInternal("=");
|
||||
}
|
||||
else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Domain = pair.GetValueInternal("=");
|
||||
}
|
||||
else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var port = pair.Equals("port", StringComparison.OrdinalIgnoreCase)
|
||||
? "\"\""
|
||||
: pair.GetValueInternal("=");
|
||||
|
||||
if (cookie != null)
|
||||
cookie.Port = port;
|
||||
}
|
||||
else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Comment = pair.GetValueInternal("=").UrlDecode();
|
||||
}
|
||||
else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.CommentUri = pair.GetValueInternal("=").Trim('"').ToUri();
|
||||
}
|
||||
else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Discard = true;
|
||||
}
|
||||
else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Secure = true;
|
||||
}
|
||||
else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.HttpOnly = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
string name;
|
||||
string 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(' ');
|
||||
}
|
||||
|
||||
cookie = new Cookie(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal enum EntitySendFormat
|
||||
{
|
||||
ContentLength = 0, // Content-Length: XXX
|
||||
Chunked = 1, // Transfer-Encoding: chunked
|
||||
}
|
||||
}
|
@ -1,528 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
sealed class HttpConnection
|
||||
{
|
||||
private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead);
|
||||
const int BufferSize = 8192;
|
||||
Socket _socket;
|
||||
Stream _stream;
|
||||
HttpEndPointListener _epl;
|
||||
MemoryStream _memoryStream;
|
||||
byte[] _buffer;
|
||||
HttpListenerContext _context;
|
||||
StringBuilder _currentLine;
|
||||
ListenerPrefix _prefix;
|
||||
HttpRequestStream _requestStream;
|
||||
HttpResponseStream _responseStream;
|
||||
bool _chunked;
|
||||
int _reuses;
|
||||
bool _contextBound;
|
||||
bool secure;
|
||||
IPEndPoint local_ep;
|
||||
HttpListener _lastListener;
|
||||
X509Certificate cert;
|
||||
SslStream ssl_stream;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ICryptoProvider _cryptoProvider;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
|
||||
public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure,
|
||||
X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem,
|
||||
IEnvironmentInfo environment)
|
||||
{
|
||||
_logger = logger;
|
||||
this._socket = socket;
|
||||
this._epl = epl;
|
||||
this.secure = secure;
|
||||
this.cert = cert;
|
||||
_cryptoProvider = cryptoProvider;
|
||||
_streamHelper = streamHelper;
|
||||
_fileSystem = fileSystem;
|
||||
_environment = environment;
|
||||
|
||||
if (secure == false)
|
||||
{
|
||||
_stream = new SocketStream(_socket, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ssl_stream = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) =>
|
||||
{
|
||||
if (c == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//var c2 = c as X509Certificate2;
|
||||
//if (c2 == null)
|
||||
//{
|
||||
// c2 = new X509Certificate2(c.GetRawCertData());
|
||||
//}
|
||||
|
||||
//_clientCert = c2;
|
||||
//_clientCertErrors = new int[] { (int)e };
|
||||
return true;
|
||||
});
|
||||
|
||||
_stream = ssl_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream Stream => _stream;
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
if (ssl_stream != null)
|
||||
{
|
||||
var enableAsync = true;
|
||||
if (enableAsync)
|
||||
{
|
||||
await ssl_stream.AuthenticateAsServerAsync(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false);
|
||||
}
|
||||
}
|
||||
|
||||
InitInternal();
|
||||
}
|
||||
|
||||
private void InitInternal()
|
||||
{
|
||||
_contextBound = false;
|
||||
_requestStream = null;
|
||||
_responseStream = null;
|
||||
_prefix = null;
|
||||
_chunked = false;
|
||||
_memoryStream = new MemoryStream();
|
||||
_position = 0;
|
||||
_inputState = InputState.RequestLine;
|
||||
_lineState = LineState.None;
|
||||
_context = new HttpListenerContext(this);
|
||||
}
|
||||
|
||||
public bool IsClosed => (_socket == null);
|
||||
|
||||
public int Reuses => _reuses;
|
||||
|
||||
public IPEndPoint LocalEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
if (local_ep != null)
|
||||
return local_ep;
|
||||
|
||||
local_ep = (IPEndPoint)_socket.LocalEndPoint;
|
||||
return local_ep;
|
||||
}
|
||||
}
|
||||
|
||||
public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint;
|
||||
|
||||
public bool IsSecure => secure;
|
||||
|
||||
public ListenerPrefix Prefix
|
||||
{
|
||||
get => _prefix;
|
||||
set => _prefix = value;
|
||||
}
|
||||
|
||||
private void OnTimeout(object unused)
|
||||
{
|
||||
//_logger.LogInformation("HttpConnection timer fired");
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
}
|
||||
|
||||
public void BeginReadRequest()
|
||||
{
|
||||
if (_buffer == null)
|
||||
_buffer = new byte[BufferSize];
|
||||
try
|
||||
{
|
||||
_stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
|
||||
}
|
||||
catch
|
||||
{
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
public HttpRequestStream GetRequestStream(bool chunked, long contentlength)
|
||||
{
|
||||
if (_requestStream == null)
|
||||
{
|
||||
byte[] buffer = _memoryStream.GetBuffer();
|
||||
int length = (int)_memoryStream.Length;
|
||||
_memoryStream = null;
|
||||
if (chunked)
|
||||
{
|
||||
_chunked = true;
|
||||
//_context.Response.SendChunked = true;
|
||||
_requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position);
|
||||
}
|
||||
else
|
||||
{
|
||||
_requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength);
|
||||
}
|
||||
}
|
||||
return _requestStream;
|
||||
}
|
||||
|
||||
public HttpResponseStream GetResponseStream(bool isExpect100Continue = false)
|
||||
{
|
||||
// TODO: can we get this _stream before reading the input?
|
||||
if (_responseStream == null)
|
||||
{
|
||||
var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure;
|
||||
|
||||
_responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger);
|
||||
}
|
||||
return _responseStream;
|
||||
}
|
||||
|
||||
private static void OnRead(IAsyncResult ares)
|
||||
{
|
||||
var cnc = (HttpConnection)ares.AsyncState;
|
||||
cnc.OnReadInternal(ares);
|
||||
}
|
||||
|
||||
private void OnReadInternal(IAsyncResult ares)
|
||||
{
|
||||
int nread = -1;
|
||||
try
|
||||
{
|
||||
nread = _stream.EndRead(ares);
|
||||
_memoryStream.Write(_buffer, 0, nread);
|
||||
if (_memoryStream.Length > 32768)
|
||||
{
|
||||
SendError("Bad Request", 400);
|
||||
Close(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (_memoryStream != null && _memoryStream.Length > 0)
|
||||
SendError();
|
||||
if (_socket != null)
|
||||
{
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (nread == 0)
|
||||
{
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProcessInput(_memoryStream))
|
||||
{
|
||||
if (!_context.HaveError)
|
||||
_context.Request.FinishInitialization();
|
||||
|
||||
if (_context.HaveError)
|
||||
{
|
||||
SendError();
|
||||
Close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_epl.BindContext(_context))
|
||||
{
|
||||
const int NotFoundErrorCode = 404;
|
||||
SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode);
|
||||
Close(true);
|
||||
return;
|
||||
}
|
||||
HttpListener listener = _epl.Listener;
|
||||
if (_lastListener != listener)
|
||||
{
|
||||
RemoveConnection();
|
||||
listener.AddConnection(this);
|
||||
_lastListener = listener;
|
||||
}
|
||||
|
||||
_contextBound = true;
|
||||
listener.RegisterContext(_context);
|
||||
return;
|
||||
}
|
||||
_stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
|
||||
}
|
||||
|
||||
private void RemoveConnection()
|
||||
{
|
||||
if (_lastListener == null)
|
||||
_epl.RemoveConnection(this);
|
||||
else
|
||||
_lastListener.RemoveConnection(this);
|
||||
}
|
||||
|
||||
private enum InputState
|
||||
{
|
||||
RequestLine,
|
||||
Headers
|
||||
}
|
||||
|
||||
private enum LineState
|
||||
{
|
||||
None,
|
||||
CR,
|
||||
LF
|
||||
}
|
||||
|
||||
InputState _inputState = InputState.RequestLine;
|
||||
LineState _lineState = LineState.None;
|
||||
int _position;
|
||||
|
||||
// true -> done processing
|
||||
// false -> need more input
|
||||
private bool ProcessInput(MemoryStream ms)
|
||||
{
|
||||
byte[] buffer = ms.GetBuffer();
|
||||
int len = (int)ms.Length;
|
||||
int used = 0;
|
||||
string line;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (_context.HaveError)
|
||||
return true;
|
||||
|
||||
if (_position >= len)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
line = ReadLine(buffer, _position, len - _position, ref used);
|
||||
_position += used;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_context.ErrorMessage = "Bad request";
|
||||
_context.ErrorStatus = 400;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line == null)
|
||||
break;
|
||||
|
||||
if (line == "")
|
||||
{
|
||||
if (_inputState == InputState.RequestLine)
|
||||
continue;
|
||||
_currentLine = null;
|
||||
ms = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_inputState == InputState.RequestLine)
|
||||
{
|
||||
_context.Request.SetRequestLine(line);
|
||||
_inputState = InputState.Headers;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.Request.AddHeader(line);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_context.ErrorMessage = e.Message;
|
||||
_context.ErrorStatus = 400;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (used == len)
|
||||
{
|
||||
ms.SetLength(0);
|
||||
_position = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ReadLine(byte[] buffer, int offset, int len, ref int used)
|
||||
{
|
||||
if (_currentLine == null)
|
||||
_currentLine = new StringBuilder(128);
|
||||
int last = offset + len;
|
||||
used = 0;
|
||||
for (int i = offset; i < last && _lineState != LineState.LF; i++)
|
||||
{
|
||||
used++;
|
||||
byte b = buffer[i];
|
||||
if (b == 13)
|
||||
{
|
||||
_lineState = LineState.CR;
|
||||
}
|
||||
else if (b == 10)
|
||||
{
|
||||
_lineState = LineState.LF;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentLine.Append((char)b);
|
||||
}
|
||||
}
|
||||
|
||||
string result = null;
|
||||
if (_lineState == LineState.LF)
|
||||
{
|
||||
_lineState = LineState.None;
|
||||
result = _currentLine.ToString();
|
||||
_currentLine.Length = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SendError(string msg, int status)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpListenerResponse response = _context.Response;
|
||||
response.StatusCode = status;
|
||||
response.ContentType = "text/html";
|
||||
string description = HttpStatusDescription.Get(status);
|
||||
string str;
|
||||
if (msg != null)
|
||||
str = string.Format("<h1>{0} ({1})</h1>", description, msg);
|
||||
else
|
||||
str = string.Format("<h1>{0}</h1>", description);
|
||||
|
||||
byte[] error = Encoding.UTF8.GetBytes(str);
|
||||
response.Close(error, false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// response was already closed
|
||||
}
|
||||
}
|
||||
|
||||
public void SendError()
|
||||
{
|
||||
SendError(_context.ErrorMessage, _context.ErrorStatus);
|
||||
}
|
||||
|
||||
private void Unbind()
|
||||
{
|
||||
if (_contextBound)
|
||||
{
|
||||
_epl.UnbindContext(_context);
|
||||
_contextBound = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
|
||||
private void CloseSocket()
|
||||
{
|
||||
if (_socket == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_socket.Close();
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_socket = null;
|
||||
}
|
||||
|
||||
RemoveConnection();
|
||||
}
|
||||
|
||||
internal void Close(bool force)
|
||||
{
|
||||
if (_socket != null)
|
||||
{
|
||||
Stream st = GetResponseStream();
|
||||
if (st != null)
|
||||
st.Close();
|
||||
|
||||
_responseStream = null;
|
||||
}
|
||||
|
||||
if (_socket != null)
|
||||
{
|
||||
force |= !_context.Request.KeepAlive;
|
||||
if (!force)
|
||||
force = (string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!force && _context.Request.FlushInput())
|
||||
{
|
||||
if (_chunked && _context.Response.ForceCloseChunked == false)
|
||||
{
|
||||
// Don't close. Keep working.
|
||||
_reuses++;
|
||||
Unbind();
|
||||
InitInternal();
|
||||
BeginReadRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
_reuses++;
|
||||
Unbind();
|
||||
InitInternal();
|
||||
BeginReadRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
Socket s = _socket;
|
||||
_socket = null;
|
||||
try
|
||||
{
|
||||
if (s != null)
|
||||
s.Shutdown(SocketShutdown.Both);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (s != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
s.Close();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
Unbind();
|
||||
RemoveConnection();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,526 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal sealed class HttpEndPointListener
|
||||
{
|
||||
private HttpListener _listener;
|
||||
private IPEndPoint _endpoint;
|
||||
private Socket _socket;
|
||||
private Dictionary<ListenerPrefix, HttpListener> _prefixes;
|
||||
private List<ListenerPrefix> _unhandledPrefixes; // host = '*'
|
||||
private List<ListenerPrefix> _allPrefixes; // host = '+'
|
||||
private X509Certificate _cert;
|
||||
private bool _secure;
|
||||
private Dictionary<HttpConnection, HttpConnection> _unregisteredConnections;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private bool _closed;
|
||||
private bool _enableDualMode;
|
||||
private readonly ICryptoProvider _cryptoProvider;
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
|
||||
public HttpEndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert,
|
||||
ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IStreamHelper streamHelper,
|
||||
IFileSystem fileSystem, IEnvironmentInfo environment)
|
||||
{
|
||||
this._listener = listener;
|
||||
_logger = logger;
|
||||
_cryptoProvider = cryptoProvider;
|
||||
_socketFactory = socketFactory;
|
||||
_streamHelper = streamHelper;
|
||||
_fileSystem = fileSystem;
|
||||
_environment = environment;
|
||||
|
||||
this._secure = secure;
|
||||
this._cert = cert;
|
||||
|
||||
_enableDualMode = addr.Equals(IPAddress.IPv6Any);
|
||||
_endpoint = new IPEndPoint(addr, port);
|
||||
|
||||
_prefixes = new Dictionary<ListenerPrefix, HttpListener>();
|
||||
_unregisteredConnections = new Dictionary<HttpConnection, HttpConnection>();
|
||||
|
||||
CreateSocket();
|
||||
}
|
||||
|
||||
internal HttpListener Listener => _listener;
|
||||
|
||||
private void CreateSocket()
|
||||
{
|
||||
try
|
||||
{
|
||||
_socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode);
|
||||
}
|
||||
catch (SocketCreateException ex)
|
||||
{
|
||||
if (_enableDualMode && _endpoint.Address.Equals(IPAddress.IPv6Any) &&
|
||||
(string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) ||
|
||||
// mono 4.8.1 and lower on bsd is throwing this
|
||||
string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase) ||
|
||||
// mono 5.2 on bsd is throwing this
|
||||
string.Equals(ex.ErrorCode, "OperationNotSupported", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
_endpoint = new IPEndPoint(IPAddress.Any, _endpoint.Port);
|
||||
_enableDualMode = false;
|
||||
_socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// This is not supported on all operating systems (qnap)
|
||||
}
|
||||
|
||||
_socket.Bind(_endpoint);
|
||||
|
||||
// This is the number TcpListener uses.
|
||||
_socket.Listen(2147483647);
|
||||
|
||||
Accept();
|
||||
|
||||
_closed = false;
|
||||
}
|
||||
|
||||
private void Accept()
|
||||
{
|
||||
var acceptEventArg = new SocketAsyncEventArgs();
|
||||
acceptEventArg.UserToken = this;
|
||||
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnAccept);
|
||||
|
||||
Accept(acceptEventArg);
|
||||
}
|
||||
|
||||
private static void TryCloseAndDispose(Socket socket)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (socket)
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryClose(Socket socket)
|
||||
{
|
||||
try
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void Accept(SocketAsyncEventArgs acceptEventArg)
|
||||
{
|
||||
// acceptSocket must be cleared since the context object is being reused
|
||||
acceptEventArg.AcceptSocket = null;
|
||||
|
||||
try
|
||||
{
|
||||
bool willRaiseEvent = _socket.AcceptAsync(acceptEventArg);
|
||||
|
||||
if (!willRaiseEvent)
|
||||
{
|
||||
ProcessAccept(acceptEventArg);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// TODO Investigate or properly fix.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var epl = (HttpEndPointListener)acceptEventArg.UserToken;
|
||||
|
||||
epl._logger.LogError(ex, "Error in socket.AcceptAsync");
|
||||
}
|
||||
}
|
||||
|
||||
// This method is the callback method associated with Socket.AcceptAsync
|
||||
// operations and is invoked when an accept operation is complete
|
||||
//
|
||||
private static void OnAccept(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
ProcessAccept(e);
|
||||
}
|
||||
|
||||
private static async void ProcessAccept(SocketAsyncEventArgs args)
|
||||
{
|
||||
var epl = (HttpEndPointListener)args.UserToken;
|
||||
|
||||
if (epl._closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx
|
||||
// Under certain conditions ConnectionReset can occur
|
||||
// Need to attept to re-accept
|
||||
var socketError = args.SocketError;
|
||||
var accepted = args.AcceptSocket;
|
||||
|
||||
epl.Accept(args);
|
||||
|
||||
if (socketError == SocketError.ConnectionReset)
|
||||
{
|
||||
epl._logger.LogError("SocketError.ConnectionReset reported. Attempting to re-accept.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (accepted == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (epl._secure && epl._cert == null)
|
||||
{
|
||||
TryClose(accepted);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var remoteEndPointString = accepted.RemoteEndPoint == null ? string.Empty : accepted.RemoteEndPoint.ToString();
|
||||
var localEndPointString = accepted.LocalEndPoint == null ? string.Empty : accepted.LocalEndPoint.ToString();
|
||||
//_logger.LogInformation("HttpEndPointListener Accepting connection from {0} to {1} secure connection requested: {2}", remoteEndPointString, localEndPointString, _secure);
|
||||
|
||||
var conn = new HttpConnection(epl._logger, accepted, epl, epl._secure, epl._cert, epl._cryptoProvider, epl._streamHelper, epl._fileSystem, epl._environment);
|
||||
|
||||
await conn.Init().ConfigureAwait(false);
|
||||
|
||||
//_logger.LogDebug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId);
|
||||
lock (epl._unregisteredConnections)
|
||||
{
|
||||
epl._unregisteredConnections[conn] = conn;
|
||||
}
|
||||
conn.BeginReadRequest();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
epl._logger.LogError(ex, "Error in ProcessAccept");
|
||||
|
||||
TryClose(accepted);
|
||||
epl.Accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private Socket CreateSocket(AddressFamily addressFamily, bool dualMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
|
||||
|
||||
if (dualMode)
|
||||
{
|
||||
socket.DualMode = true;
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
if (dualMode)
|
||||
{
|
||||
// Mono for BSD incorrectly throws ArgumentException instead of SocketException
|
||||
throw new SocketCreateException("AddressFamilyNotSupported", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveConnection(HttpConnection conn)
|
||||
{
|
||||
lock (_unregisteredConnections)
|
||||
{
|
||||
_unregisteredConnections.Remove(conn);
|
||||
}
|
||||
}
|
||||
|
||||
public bool BindContext(HttpListenerContext context)
|
||||
{
|
||||
var req = context.Request;
|
||||
HttpListener listener = SearchListener(req.Url, out var prefix);
|
||||
if (listener == null)
|
||||
return false;
|
||||
|
||||
context.Connection.Prefix = prefix;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UnbindContext(HttpListenerContext context)
|
||||
{
|
||||
if (context == null || context.Request == null)
|
||||
return;
|
||||
|
||||
_listener.UnregisterContext(context);
|
||||
}
|
||||
|
||||
private HttpListener SearchListener(Uri uri, out ListenerPrefix prefix)
|
||||
{
|
||||
prefix = null;
|
||||
if (uri == null)
|
||||
return null;
|
||||
|
||||
string host = uri.Host;
|
||||
int port = uri.Port;
|
||||
string path = WebUtility.UrlDecode(uri.AbsolutePath);
|
||||
string pathSlash = path[path.Length - 1] == '/' ? path : path + "/";
|
||||
|
||||
HttpListener bestMatch = null;
|
||||
int bestLength = -1;
|
||||
|
||||
if (host != null && host != "")
|
||||
{
|
||||
Dictionary<ListenerPrefix, HttpListener> localPrefixes = _prefixes;
|
||||
foreach (var p in localPrefixes.Keys)
|
||||
{
|
||||
string ppath = p.Path;
|
||||
if (ppath.Length < bestLength)
|
||||
continue;
|
||||
|
||||
if (p.Host != host || p.Port != port)
|
||||
continue;
|
||||
|
||||
if (path.StartsWith(ppath) || pathSlash.StartsWith(ppath))
|
||||
{
|
||||
bestLength = ppath.Length;
|
||||
bestMatch = localPrefixes[p];
|
||||
prefix = p;
|
||||
}
|
||||
}
|
||||
if (bestLength != -1)
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
List<ListenerPrefix> list = _unhandledPrefixes;
|
||||
bestMatch = MatchFromList(host, path, list, out prefix);
|
||||
|
||||
if (path != pathSlash && bestMatch == null)
|
||||
bestMatch = MatchFromList(host, pathSlash, list, out prefix);
|
||||
|
||||
if (bestMatch != null)
|
||||
return bestMatch;
|
||||
|
||||
list = _allPrefixes;
|
||||
bestMatch = MatchFromList(host, path, list, out prefix);
|
||||
|
||||
if (path != pathSlash && bestMatch == null)
|
||||
bestMatch = MatchFromList(host, pathSlash, list, out prefix);
|
||||
|
||||
if (bestMatch != null)
|
||||
return bestMatch;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private HttpListener MatchFromList(string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)
|
||||
{
|
||||
prefix = null;
|
||||
if (list == null)
|
||||
return null;
|
||||
|
||||
HttpListener bestMatch = null;
|
||||
int bestLength = -1;
|
||||
|
||||
foreach (ListenerPrefix p in list)
|
||||
{
|
||||
string ppath = p.Path;
|
||||
if (ppath.Length < bestLength)
|
||||
continue;
|
||||
|
||||
if (path.StartsWith(ppath))
|
||||
{
|
||||
bestLength = ppath.Length;
|
||||
bestMatch = p._listener;
|
||||
prefix = p;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
private void AddSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)
|
||||
{
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
foreach (ListenerPrefix p in list)
|
||||
{
|
||||
if (p.Path == prefix.Path)
|
||||
throw new Exception("net_listener_already");
|
||||
}
|
||||
list.Add(prefix);
|
||||
}
|
||||
|
||||
private bool RemoveSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)
|
||||
{
|
||||
if (list == null)
|
||||
return false;
|
||||
|
||||
int c = list.Count;
|
||||
for (int i = 0; i < c; i++)
|
||||
{
|
||||
ListenerPrefix p = list[i];
|
||||
if (p.Path == prefix.Path)
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CheckIfRemove()
|
||||
{
|
||||
if (_prefixes.Count > 0)
|
||||
return;
|
||||
|
||||
List<ListenerPrefix> list = _unhandledPrefixes;
|
||||
if (list != null && list.Count > 0)
|
||||
return;
|
||||
|
||||
list = _allPrefixes;
|
||||
if (list != null && list.Count > 0)
|
||||
return;
|
||||
|
||||
HttpEndPointManager.RemoveEndPoint(this, _endpoint);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_closed = true;
|
||||
_socket.Close();
|
||||
lock (_unregisteredConnections)
|
||||
{
|
||||
// Clone the list because RemoveConnection can be called from Close
|
||||
var connections = new List<HttpConnection>(_unregisteredConnections.Keys);
|
||||
|
||||
foreach (HttpConnection c in connections)
|
||||
c.Close(true);
|
||||
_unregisteredConnections.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPrefix(ListenerPrefix prefix, HttpListener listener)
|
||||
{
|
||||
List<ListenerPrefix> current;
|
||||
List<ListenerPrefix> future;
|
||||
if (prefix.Host == "*")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _unhandledPrefixes;
|
||||
future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
||||
prefix._listener = listener;
|
||||
AddSpecial(future, prefix);
|
||||
} while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix.Host == "+")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _allPrefixes;
|
||||
future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
||||
prefix._listener = listener;
|
||||
AddSpecial(future, prefix);
|
||||
} while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
|
||||
do
|
||||
{
|
||||
prefs = _prefixes;
|
||||
if (prefs.ContainsKey(prefix))
|
||||
{
|
||||
throw new Exception("net_listener_already");
|
||||
}
|
||||
p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
|
||||
p2[prefix] = listener;
|
||||
} while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
|
||||
}
|
||||
|
||||
public void RemovePrefix(ListenerPrefix prefix, HttpListener listener)
|
||||
{
|
||||
List<ListenerPrefix> current;
|
||||
List<ListenerPrefix> future;
|
||||
if (prefix.Host == "*")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _unhandledPrefixes;
|
||||
future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
||||
if (!RemoveSpecial(future, prefix))
|
||||
break; // Prefix not found
|
||||
} while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);
|
||||
|
||||
CheckIfRemove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix.Host == "+")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _allPrefixes;
|
||||
future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
||||
if (!RemoveSpecial(future, prefix))
|
||||
break; // Prefix not found
|
||||
} while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);
|
||||
CheckIfRemove();
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
|
||||
do
|
||||
{
|
||||
prefs = _prefixes;
|
||||
if (!prefs.ContainsKey(prefix))
|
||||
break;
|
||||
|
||||
p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
|
||||
p2.Remove(prefix);
|
||||
} while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
|
||||
CheckIfRemove();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal sealed class HttpEndPointManager
|
||||
{
|
||||
private static Dictionary<IPAddress, Dictionary<int, HttpEndPointListener>> s_ipEndPoints = new Dictionary<IPAddress, Dictionary<int, HttpEndPointListener>>();
|
||||
|
||||
private HttpEndPointManager()
|
||||
{
|
||||
}
|
||||
|
||||
public static void AddListener(ILogger logger, HttpListener listener)
|
||||
{
|
||||
var added = new List<string>();
|
||||
try
|
||||
{
|
||||
lock ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
foreach (string prefix in listener.Prefixes)
|
||||
{
|
||||
AddPrefixInternal(logger, prefix, listener);
|
||||
added.Add(prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
foreach (string prefix in added)
|
||||
{
|
||||
RemovePrefix(logger, prefix, listener);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddPrefix(ILogger logger, string prefix, HttpListener listener)
|
||||
{
|
||||
lock ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
AddPrefixInternal(logger, prefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddPrefixInternal(ILogger logger, string p, HttpListener listener)
|
||||
{
|
||||
int start = p.IndexOf(':') + 3;
|
||||
int colon = p.IndexOf(':', start);
|
||||
if (colon != -1)
|
||||
{
|
||||
// root can't be -1 here, since we've already checked for ending '/' in ListenerPrefix.
|
||||
int root = p.IndexOf('/', colon, p.Length - colon);
|
||||
string portString = p.Substring(colon + 1, root - colon - 1);
|
||||
|
||||
if (!int.TryParse(portString, out var port) || port <= 0 || port >= 65536)
|
||||
{
|
||||
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_port");
|
||||
}
|
||||
}
|
||||
|
||||
var lp = new ListenerPrefix(p);
|
||||
if (lp.Host != "*" && lp.Host != "+" && Uri.CheckHostName(lp.Host) == UriHostNameType.Unknown)
|
||||
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_listener_host");
|
||||
|
||||
if (lp.Path.IndexOf('%') != -1)
|
||||
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path");
|
||||
|
||||
if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1)
|
||||
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path");
|
||||
|
||||
// listens on all the interfaces if host name cannot be parsed by IPAddress.
|
||||
HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.AddPrefix(lp, listener);
|
||||
}
|
||||
|
||||
private static IPAddress GetIpAnyAddress(HttpListener listener)
|
||||
{
|
||||
return listener.EnableDualMode ? IPAddress.IPv6Any : IPAddress.Any;
|
||||
}
|
||||
|
||||
private static HttpEndPointListener GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure)
|
||||
{
|
||||
IPAddress addr;
|
||||
if (host == "*" || host == "+")
|
||||
{
|
||||
addr = GetIpAnyAddress(listener);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int NotSupportedErrorCode = 50;
|
||||
try
|
||||
{
|
||||
addr = Dns.GetHostAddresses(host)[0];
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Throw same error code as windows, request is not supported.
|
||||
throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported");
|
||||
}
|
||||
|
||||
if (IPAddress.Any.Equals(addr))
|
||||
{
|
||||
// Don't support listening to 0.0.0.0, match windows behavior.
|
||||
throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported");
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<int, HttpEndPointListener> p = null;
|
||||
if (s_ipEndPoints.ContainsKey(addr))
|
||||
{
|
||||
p = s_ipEndPoints[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
p = new Dictionary<int, HttpEndPointListener>();
|
||||
s_ipEndPoints[addr] = p;
|
||||
}
|
||||
|
||||
HttpEndPointListener epl = null;
|
||||
if (p.ContainsKey(port))
|
||||
{
|
||||
epl = p[port];
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
epl = new HttpEndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.SocketFactory, listener.StreamHelper, listener.FileSystem, listener.EnvironmentInfo);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
throw new HttpListenerException(ex.ErrorCode, ex.Message);
|
||||
}
|
||||
p[port] = epl;
|
||||
}
|
||||
|
||||
return epl;
|
||||
}
|
||||
|
||||
public static void RemoveEndPoint(HttpEndPointListener epl, IPEndPoint ep)
|
||||
{
|
||||
lock ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
Dictionary<int, HttpEndPointListener> p = null;
|
||||
p = s_ipEndPoints[ep.Address];
|
||||
p.Remove(ep.Port);
|
||||
if (p.Count == 0)
|
||||
{
|
||||
s_ipEndPoints.Remove(ep.Address);
|
||||
}
|
||||
epl.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveListener(ILogger logger, HttpListener listener)
|
||||
{
|
||||
lock ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
foreach (string prefix in listener.Prefixes)
|
||||
{
|
||||
RemovePrefixInternal(logger, prefix, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener)
|
||||
{
|
||||
lock ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
RemovePrefixInternal(logger, prefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemovePrefixInternal(ILogger logger, string prefix, HttpListener listener)
|
||||
{
|
||||
var lp = new ListenerPrefix(prefix);
|
||||
if (lp.Path.IndexOf('%') != -1)
|
||||
return;
|
||||
|
||||
if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1)
|
||||
return;
|
||||
|
||||
HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.RemovePrefix(lp, listener);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal static partial class HttpKnownHeaderNames
|
||||
{
|
||||
// When adding a new constant, add it to HttpKnownHeaderNames.TryGetHeaderName.cs as well.
|
||||
|
||||
public const string Accept = "Accept";
|
||||
public const string AcceptCharset = "Accept-Charset";
|
||||
public const string AcceptEncoding = "Accept-Encoding";
|
||||
public const string AcceptLanguage = "Accept-Language";
|
||||
public const string AcceptPatch = "Accept-Patch";
|
||||
public const string AcceptRanges = "Accept-Ranges";
|
||||
public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
|
||||
public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
|
||||
public const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
|
||||
public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
|
||||
public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
|
||||
public const string AccessControlMaxAge = "Access-Control-Max-Age";
|
||||
public const string Age = "Age";
|
||||
public const string Allow = "Allow";
|
||||
public const string AltSvc = "Alt-Svc";
|
||||
public const string Authorization = "Authorization";
|
||||
public const string CacheControl = "Cache-Control";
|
||||
public const string Connection = "Connection";
|
||||
public const string ContentDisposition = "Content-Disposition";
|
||||
public const string ContentEncoding = "Content-Encoding";
|
||||
public const string ContentLanguage = "Content-Language";
|
||||
public const string ContentLength = "Content-Length";
|
||||
public const string ContentLocation = "Content-Location";
|
||||
public const string ContentMD5 = "Content-MD5";
|
||||
public const string ContentRange = "Content-Range";
|
||||
public const string ContentSecurityPolicy = "Content-Security-Policy";
|
||||
public const string ContentType = "Content-Type";
|
||||
public const string Cookie = "Cookie";
|
||||
public const string Cookie2 = "Cookie2";
|
||||
public const string Date = "Date";
|
||||
public const string ETag = "ETag";
|
||||
public const string Expect = "Expect";
|
||||
public const string Expires = "Expires";
|
||||
public const string From = "From";
|
||||
public const string Host = "Host";
|
||||
public const string IfMatch = "If-Match";
|
||||
public const string IfModifiedSince = "If-Modified-Since";
|
||||
public const string IfNoneMatch = "If-None-Match";
|
||||
public const string IfRange = "If-Range";
|
||||
public const string IfUnmodifiedSince = "If-Unmodified-Since";
|
||||
public const string KeepAlive = "Keep-Alive";
|
||||
public const string LastModified = "Last-Modified";
|
||||
public const string Link = "Link";
|
||||
public const string Location = "Location";
|
||||
public const string MaxForwards = "Max-Forwards";
|
||||
public const string Origin = "Origin";
|
||||
public const string P3P = "P3P";
|
||||
public const string Pragma = "Pragma";
|
||||
public const string ProxyAuthenticate = "Proxy-Authenticate";
|
||||
public const string ProxyAuthorization = "Proxy-Authorization";
|
||||
public const string ProxyConnection = "Proxy-Connection";
|
||||
public const string PublicKeyPins = "Public-Key-Pins";
|
||||
public const string Range = "Range";
|
||||
public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched.
|
||||
public const string RetryAfter = "Retry-After";
|
||||
public const string SecWebSocketAccept = "Sec-WebSocket-Accept";
|
||||
public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
|
||||
public const string SecWebSocketKey = "Sec-WebSocket-Key";
|
||||
public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
|
||||
public const string SecWebSocketVersion = "Sec-WebSocket-Version";
|
||||
public const string Server = "Server";
|
||||
public const string SetCookie = "Set-Cookie";
|
||||
public const string SetCookie2 = "Set-Cookie2";
|
||||
public const string StrictTransportSecurity = "Strict-Transport-Security";
|
||||
public const string TE = "TE";
|
||||
public const string TSV = "TSV";
|
||||
public const string Trailer = "Trailer";
|
||||
public const string TransferEncoding = "Transfer-Encoding";
|
||||
public const string Upgrade = "Upgrade";
|
||||
public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests";
|
||||
public const string UserAgent = "User-Agent";
|
||||
public const string Vary = "Vary";
|
||||
public const string Via = "Via";
|
||||
public const string WWWAuthenticate = "WWW-Authenticate";
|
||||
public const string Warning = "Warning";
|
||||
public const string XAspNetVersion = "X-AspNet-Version";
|
||||
public const string XContentDuration = "X-Content-Duration";
|
||||
public const string XContentTypeOptions = "X-Content-Type-Options";
|
||||
public const string XFrameOptions = "X-Frame-Options";
|
||||
public const string XMSEdgeRef = "X-MSEdge-Ref";
|
||||
public const string XPoweredBy = "X-Powered-By";
|
||||
public const string XRequestID = "X-Request-ID";
|
||||
public const string XUACompatible = "X-UA-Compatible";
|
||||
}
|
||||
}
|
@ -1,284 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed class HttpListener : IDisposable
|
||||
{
|
||||
internal ICryptoProvider CryptoProvider { get; private set; }
|
||||
internal ISocketFactory SocketFactory { get; private set; }
|
||||
internal IFileSystem FileSystem { get; private set; }
|
||||
internal IStreamHelper StreamHelper { get; private set; }
|
||||
internal IEnvironmentInfo EnvironmentInfo { get; private set; }
|
||||
|
||||
public bool EnableDualMode { get; set; }
|
||||
|
||||
private AuthenticationSchemes auth_schemes;
|
||||
private HttpListenerPrefixCollection prefixes;
|
||||
private AuthenticationSchemeSelector auth_selector;
|
||||
private string realm;
|
||||
private bool unsafe_ntlm_auth;
|
||||
private bool listening;
|
||||
private bool disposed;
|
||||
|
||||
private Dictionary<HttpListenerContext, HttpListenerContext> registry;
|
||||
private Dictionary<HttpConnection, HttpConnection> connections;
|
||||
private ILogger _logger;
|
||||
private X509Certificate _certificate;
|
||||
|
||||
public Action<HttpListenerContext> OnContext { get; set; }
|
||||
|
||||
public HttpListener(
|
||||
ILogger logger,
|
||||
ICryptoProvider cryptoProvider,
|
||||
ISocketFactory socketFactory,
|
||||
IStreamHelper streamHelper,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environmentInfo)
|
||||
{
|
||||
_logger = logger;
|
||||
CryptoProvider = cryptoProvider;
|
||||
SocketFactory = socketFactory;
|
||||
StreamHelper = streamHelper;
|
||||
FileSystem = fileSystem;
|
||||
EnvironmentInfo = environmentInfo;
|
||||
|
||||
prefixes = new HttpListenerPrefixCollection(logger, this);
|
||||
registry = new Dictionary<HttpListenerContext, HttpListenerContext>();
|
||||
connections = new Dictionary<HttpConnection, HttpConnection>();
|
||||
auth_schemes = AuthenticationSchemes.Anonymous;
|
||||
}
|
||||
|
||||
public HttpListener(
|
||||
ILogger logger,
|
||||
X509Certificate certificate,
|
||||
ICryptoProvider cryptoProvider,
|
||||
ISocketFactory socketFactory,
|
||||
IStreamHelper streamHelper,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environmentInfo)
|
||||
: this(logger, cryptoProvider, socketFactory, streamHelper, fileSystem, environmentInfo)
|
||||
{
|
||||
_certificate = certificate;
|
||||
}
|
||||
|
||||
public void LoadCert(X509Certificate cert)
|
||||
{
|
||||
_certificate = cert;
|
||||
}
|
||||
|
||||
// TODO: Digest, NTLM and Negotiate require ControlPrincipal
|
||||
public AuthenticationSchemes AuthenticationSchemes
|
||||
{
|
||||
get => auth_schemes;
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
auth_schemes = value;
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate
|
||||
{
|
||||
get => auth_selector;
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
auth_selector = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsListening => listening;
|
||||
|
||||
public static bool IsSupported => true;
|
||||
|
||||
public HttpListenerPrefixCollection Prefixes
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckDisposed();
|
||||
return prefixes;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use this
|
||||
public string Realm
|
||||
{
|
||||
get => realm;
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
realm = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool UnsafeConnectionNtlmAuthentication
|
||||
{
|
||||
get => unsafe_ntlm_auth;
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
unsafe_ntlm_auth = value;
|
||||
}
|
||||
}
|
||||
|
||||
//internal IMonoSslStream CreateSslStream(Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback)
|
||||
//{
|
||||
// lock (registry)
|
||||
// {
|
||||
// if (tlsProvider == null)
|
||||
// tlsProvider = MonoTlsProviderFactory.GetProviderInternal();
|
||||
// if (tlsSettings == null)
|
||||
// tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings();
|
||||
// if (tlsSettings.RemoteCertificateValidationCallback == null)
|
||||
// tlsSettings.RemoteCertificateValidationCallback = callback;
|
||||
// return tlsProvider.CreateSslStream(innerStream, ownsStream, tlsSettings);
|
||||
// }
|
||||
//}
|
||||
|
||||
internal X509Certificate Certificate => _certificate;
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (!listening)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (!listening)
|
||||
{
|
||||
disposed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Close(true);
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
void Close(bool force)
|
||||
{
|
||||
CheckDisposed();
|
||||
HttpEndPointManager.RemoveListener(_logger, this);
|
||||
Cleanup(force);
|
||||
}
|
||||
|
||||
void Cleanup(bool close_existing)
|
||||
{
|
||||
lock (registry)
|
||||
{
|
||||
if (close_existing)
|
||||
{
|
||||
// Need to copy this since closing will call UnregisterContext
|
||||
ICollection keys = registry.Keys;
|
||||
var all = new HttpListenerContext[keys.Count];
|
||||
keys.CopyTo(all, 0);
|
||||
registry.Clear();
|
||||
for (int i = all.Length - 1; i >= 0; i--)
|
||||
all[i].Connection.Close(true);
|
||||
}
|
||||
|
||||
lock (connections)
|
||||
{
|
||||
ICollection keys = connections.Keys;
|
||||
var conns = new HttpConnection[keys.Count];
|
||||
keys.CopyTo(conns, 0);
|
||||
connections.Clear();
|
||||
for (int i = conns.Length - 1; i >= 0; i--)
|
||||
conns[i].Close(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerContext context)
|
||||
{
|
||||
if (AuthenticationSchemeSelectorDelegate != null)
|
||||
return AuthenticationSchemeSelectorDelegate(context.Request);
|
||||
else
|
||||
return auth_schemes;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
CheckDisposed();
|
||||
if (listening)
|
||||
return;
|
||||
|
||||
HttpEndPointManager.AddListener(_logger, this);
|
||||
listening = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
CheckDisposed();
|
||||
listening = false;
|
||||
Close(false);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
Close(true); //TODO: Should we force here or not?
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
internal void CheckDisposed()
|
||||
{
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
|
||||
internal void RegisterContext(HttpListenerContext context)
|
||||
{
|
||||
if (OnContext != null && IsListening)
|
||||
{
|
||||
OnContext(context);
|
||||
}
|
||||
|
||||
lock (registry)
|
||||
registry[context] = context;
|
||||
}
|
||||
|
||||
internal void UnregisterContext(HttpListenerContext context)
|
||||
{
|
||||
lock (registry)
|
||||
registry.Remove(context);
|
||||
}
|
||||
|
||||
internal void AddConnection(HttpConnection cnc)
|
||||
{
|
||||
lock (connections)
|
||||
{
|
||||
connections[cnc] = cnc;
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveConnection(HttpConnection cnc)
|
||||
{
|
||||
lock (connections)
|
||||
{
|
||||
connections.Remove(cnc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public class HttpListenerBasicIdentity : GenericIdentity
|
||||
{
|
||||
string password;
|
||||
|
||||
public HttpListenerBasicIdentity(string username, string password)
|
||||
: base(username, "Basic")
|
||||
{
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public virtual string Password => password;
|
||||
}
|
||||
|
||||
public class GenericIdentity : IIdentity
|
||||
{
|
||||
private string m_name;
|
||||
private string m_type;
|
||||
|
||||
public GenericIdentity(string name)
|
||||
{
|
||||
if (name == null)
|
||||
throw new System.ArgumentNullException(nameof(name));
|
||||
|
||||
m_name = name;
|
||||
m_type = "";
|
||||
}
|
||||
|
||||
public GenericIdentity(string name, string type)
|
||||
{
|
||||
if (name == null)
|
||||
throw new System.ArgumentNullException(nameof(name));
|
||||
if (type == null)
|
||||
throw new System.ArgumentNullException(nameof(type));
|
||||
|
||||
m_name = name;
|
||||
m_type = type;
|
||||
}
|
||||
|
||||
public virtual string Name => m_name;
|
||||
|
||||
public virtual string AuthenticationType => m_type;
|
||||
|
||||
public virtual bool IsAuthenticated => !m_name.Equals("");
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SocketHttpListener.Net.WebSockets;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed partial class HttpListenerContext
|
||||
{
|
||||
private HttpConnection _connection;
|
||||
|
||||
internal HttpListenerContext(HttpConnection connection)
|
||||
{
|
||||
_connection = connection;
|
||||
_response = new HttpListenerResponse(this);
|
||||
Request = new HttpListenerRequest(this);
|
||||
ErrorStatus = 400;
|
||||
}
|
||||
|
||||
internal int ErrorStatus { get; set; }
|
||||
|
||||
internal string ErrorMessage { get; set; }
|
||||
|
||||
internal bool HaveError => ErrorMessage != null;
|
||||
|
||||
internal HttpConnection Connection => _connection;
|
||||
|
||||
internal void ParseAuthentication(System.Net.AuthenticationSchemes expectedSchemes)
|
||||
{
|
||||
if (expectedSchemes == System.Net.AuthenticationSchemes.Anonymous)
|
||||
return;
|
||||
|
||||
string header = Request.Headers["Authorization"];
|
||||
if (string.IsNullOrEmpty(header))
|
||||
return;
|
||||
|
||||
if (IsBasicHeader(header))
|
||||
{
|
||||
_user = ParseBasicAuthentication(header.Substring(AuthenticationTypes.Basic.Length + 1));
|
||||
}
|
||||
}
|
||||
|
||||
internal IPrincipal ParseBasicAuthentication(string authData) =>
|
||||
TryParseBasicAuth(authData, out HttpStatusCode errorCode, out string username, out string password) ?
|
||||
new GenericPrincipal(new HttpListenerBasicIdentity(username, password), Array.Empty<string>()) :
|
||||
null;
|
||||
|
||||
internal static bool IsBasicHeader(string header) =>
|
||||
header.Length >= 6 &&
|
||||
header[5] == ' ' &&
|
||||
string.Compare(header, 0, AuthenticationTypes.Basic, 0, 5, StringComparison.OrdinalIgnoreCase) == 0;
|
||||
|
||||
internal static bool TryParseBasicAuth(string headerValue, out HttpStatusCode errorCode, out string username, out string password)
|
||||
{
|
||||
errorCode = HttpStatusCode.OK;
|
||||
username = password = null;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(headerValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string authString = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
|
||||
int colonPos = authString.IndexOf(':');
|
||||
if (colonPos < 0)
|
||||
{
|
||||
// username must be at least 1 char
|
||||
errorCode = HttpStatusCode.BadRequest;
|
||||
return false;
|
||||
}
|
||||
|
||||
username = authString.Substring(0, colonPos);
|
||||
password = authString.Substring(colonPos + 1);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorCode = HttpStatusCode.InternalServerError;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval)
|
||||
{
|
||||
return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval);
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment<byte> internalBuffer)
|
||||
{
|
||||
WebSocketValidate.ValidateArraySegment(internalBuffer, nameof(internalBuffer));
|
||||
HttpWebSocket.ValidateOptions(subProtocol, receiveBufferSize, HttpWebSocket.MinSendBufferSize, keepAliveInterval);
|
||||
return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using SocketHttpListener.Net.WebSockets;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed partial class HttpListenerContext
|
||||
{
|
||||
private HttpListenerResponse _response;
|
||||
private IPrincipal _user;
|
||||
|
||||
public HttpListenerRequest Request { get; }
|
||||
|
||||
public IPrincipal User => _user;
|
||||
|
||||
// This can be used to cache the results of HttpListener.AuthenticationSchemeSelectorDelegate.
|
||||
internal AuthenticationSchemes AuthenticationSchemes { get; set; }
|
||||
|
||||
public HttpListenerResponse Response => _response;
|
||||
|
||||
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol)
|
||||
{
|
||||
return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, WebSocket.DefaultKeepAliveInterval);
|
||||
}
|
||||
|
||||
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval)
|
||||
{
|
||||
return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, keepAliveInterval);
|
||||
}
|
||||
}
|
||||
|
||||
public class GenericPrincipal : IPrincipal
|
||||
{
|
||||
private IIdentity m_identity;
|
||||
private string[] m_roles;
|
||||
|
||||
public GenericPrincipal(IIdentity identity, string[] roles)
|
||||
{
|
||||
if (identity == null)
|
||||
throw new ArgumentNullException(nameof(identity));
|
||||
|
||||
m_identity = identity;
|
||||
if (roles != null)
|
||||
{
|
||||
m_roles = new string[roles.Length];
|
||||
for (int i = 0; i < roles.Length; ++i)
|
||||
{
|
||||
m_roles[i] = roles[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_roles = null;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IIdentity Identity => m_identity;
|
||||
|
||||
public virtual bool IsInRole(string role)
|
||||
{
|
||||
if (role == null || m_roles == null)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < m_roles.Length; ++i)
|
||||
{
|
||||
if (m_roles[i] != null && string.Compare(m_roles[i], role, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public class HttpListenerPrefixCollection : ICollection<string>, IEnumerable<string>, IEnumerable
|
||||
{
|
||||
private List<string> _prefixes = new List<string>();
|
||||
private HttpListener _listener;
|
||||
|
||||
private ILogger _logger;
|
||||
|
||||
internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener)
|
||||
{
|
||||
_logger = logger;
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
public int Count => _prefixes.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public bool IsSynchronized => false;
|
||||
|
||||
public void Add(string uriPrefix)
|
||||
{
|
||||
_listener.CheckDisposed();
|
||||
//ListenerPrefix.CheckUri(uriPrefix);
|
||||
if (_prefixes.Contains(uriPrefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_prefixes.Add(uriPrefix);
|
||||
if (_listener.IsListening)
|
||||
{
|
||||
HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<string> uriPrefixes)
|
||||
{
|
||||
_listener.CheckDisposed();
|
||||
|
||||
foreach (var uriPrefix in uriPrefixes)
|
||||
{
|
||||
if (_prefixes.Contains(uriPrefix))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_prefixes.Add(uriPrefix);
|
||||
if (_listener.IsListening)
|
||||
{
|
||||
HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_listener.CheckDisposed();
|
||||
_prefixes.Clear();
|
||||
if (_listener.IsListening)
|
||||
{
|
||||
HttpEndPointManager.RemoveListener(_logger, _listener);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string uriPrefix)
|
||||
{
|
||||
_listener.CheckDisposed();
|
||||
return _prefixes.Contains(uriPrefix);
|
||||
}
|
||||
|
||||
public void CopyTo(string[] array, int offset)
|
||||
{
|
||||
_listener.CheckDisposed();
|
||||
_prefixes.CopyTo(array, offset);
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int offset)
|
||||
{
|
||||
_listener.CheckDisposed();
|
||||
((ICollection)_prefixes).CopyTo(array, offset);
|
||||
}
|
||||
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
{
|
||||
return _prefixes.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _prefixes.GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(string uriPrefix)
|
||||
{
|
||||
_listener.CheckDisposed();
|
||||
if (uriPrefix == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uriPrefix));
|
||||
}
|
||||
|
||||
bool result = _prefixes.Remove(uriPrefix);
|
||||
if (result && _listener.IsListening)
|
||||
{
|
||||
HttpEndPointManager.RemovePrefix(_logger, uriPrefix, _listener);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,325 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed partial class HttpListenerRequest
|
||||
{
|
||||
private long _contentLength;
|
||||
private bool _clSet;
|
||||
private WebHeaderCollection _headers;
|
||||
private string _method;
|
||||
private Stream _inputStream;
|
||||
private HttpListenerContext _context;
|
||||
private bool _isChunked;
|
||||
|
||||
private static byte[] s_100continue = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
|
||||
|
||||
internal HttpListenerRequest(HttpListenerContext context)
|
||||
{
|
||||
_context = context;
|
||||
_headers = new WebHeaderCollection();
|
||||
_version = HttpVersion.Version10;
|
||||
}
|
||||
|
||||
private static readonly char[] s_separators = new char[] { ' ' };
|
||||
|
||||
internal void SetRequestLine(string req)
|
||||
{
|
||||
string[] parts = req.Split(s_separators, 3);
|
||||
if (parts.Length != 3)
|
||||
{
|
||||
_context.ErrorMessage = "Invalid request line (parts).";
|
||||
return;
|
||||
}
|
||||
|
||||
_method = parts[0];
|
||||
foreach (char c in _method)
|
||||
{
|
||||
int ic = (int)c;
|
||||
|
||||
if ((ic >= 'A' && ic <= 'Z') ||
|
||||
(ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' &&
|
||||
c != '<' && c != '>' && c != '@' && c != ',' && c != ';' &&
|
||||
c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' &&
|
||||
c != ']' && c != '?' && c != '=' && c != '{' && c != '}'))
|
||||
continue;
|
||||
|
||||
_context.ErrorMessage = "(Invalid verb)";
|
||||
return;
|
||||
}
|
||||
|
||||
_rawUrl = parts[1];
|
||||
if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/"))
|
||||
{
|
||||
_context.ErrorMessage = "Invalid request line (version).";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_version = new Version(parts[2].Substring(5));
|
||||
}
|
||||
catch
|
||||
{
|
||||
_context.ErrorMessage = "Invalid request line (version).";
|
||||
return;
|
||||
}
|
||||
|
||||
if (_version.Major < 1)
|
||||
{
|
||||
_context.ErrorMessage = "Invalid request line (version).";
|
||||
return;
|
||||
}
|
||||
if (_version.Major > 1)
|
||||
{
|
||||
_context.ErrorStatus = (int)HttpStatusCode.HttpVersionNotSupported;
|
||||
_context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.HttpVersionNotSupported);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool MaybeUri(string s)
|
||||
{
|
||||
int p = s.IndexOf(':');
|
||||
if (p == -1)
|
||||
return false;
|
||||
|
||||
if (p >= 10)
|
||||
return false;
|
||||
|
||||
return IsPredefinedScheme(s.Substring(0, p));
|
||||
}
|
||||
|
||||
private static bool IsPredefinedScheme(string scheme)
|
||||
{
|
||||
if (scheme == null || scheme.Length < 3)
|
||||
return false;
|
||||
|
||||
char c = scheme[0];
|
||||
if (c == 'h')
|
||||
return (scheme == UriScheme.Http || scheme == UriScheme.Https);
|
||||
if (c == 'f')
|
||||
return (scheme == UriScheme.File || scheme == UriScheme.Ftp);
|
||||
|
||||
if (c == 'n')
|
||||
{
|
||||
c = scheme[1];
|
||||
if (c == 'e')
|
||||
return (scheme == UriScheme.News || scheme == UriScheme.NetPipe || scheme == UriScheme.NetTcp);
|
||||
if (scheme == UriScheme.Nntp)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if ((c == 'g' && scheme == UriScheme.Gopher) || (c == 'm' && scheme == UriScheme.Mailto))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void FinishInitialization()
|
||||
{
|
||||
string host = UserHostName;
|
||||
if (_version > HttpVersion.Version10 && (host == null || host.Length == 0))
|
||||
{
|
||||
_context.ErrorMessage = "Invalid host name";
|
||||
return;
|
||||
}
|
||||
|
||||
string path;
|
||||
Uri raw_uri = null;
|
||||
if (MaybeUri(_rawUrl.ToLowerInvariant()) && Uri.TryCreate(_rawUrl, UriKind.Absolute, out raw_uri))
|
||||
path = raw_uri.PathAndQuery;
|
||||
else
|
||||
path = _rawUrl;
|
||||
|
||||
if ((host == null || host.Length == 0))
|
||||
host = UserHostAddress;
|
||||
|
||||
if (raw_uri != null)
|
||||
host = raw_uri.Host;
|
||||
|
||||
int colon = host.IndexOf(']') == -1 ? host.IndexOf(':') : host.LastIndexOf(':');
|
||||
if (colon >= 0)
|
||||
host = host.Substring(0, colon);
|
||||
|
||||
string base_uri = string.Format("{0}://{1}:{2}", RequestScheme, host, LocalEndPoint.Port);
|
||||
|
||||
if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out _requestUri))
|
||||
{
|
||||
_context.ErrorMessage = System.Net.WebUtility.HtmlEncode("Invalid url: " + base_uri + path);
|
||||
return;
|
||||
}
|
||||
|
||||
_requestUri = HttpListenerRequestUriBuilder.GetRequestUri(_rawUrl, _requestUri.Scheme,
|
||||
_requestUri.Authority, _requestUri.LocalPath, _requestUri.Query);
|
||||
|
||||
if (_version >= HttpVersion.Version11)
|
||||
{
|
||||
string t_encoding = Headers[HttpKnownHeaderNames.TransferEncoding];
|
||||
_isChunked = (t_encoding != null && string.Equals(t_encoding, "chunked", StringComparison.OrdinalIgnoreCase));
|
||||
// 'identity' is not valid!
|
||||
if (t_encoding != null && !_isChunked)
|
||||
{
|
||||
_context.Connection.SendError(null, 501);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_isChunked && !_clSet)
|
||||
{
|
||||
if (string.Equals(_method, "POST", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(_method, "PUT", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_context.Connection.SendError(null, 411);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Compare(Headers[HttpKnownHeaderNames.Expect], "100-continue", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
HttpResponseStream output = _context.Connection.GetResponseStream();
|
||||
output.InternalWrite(s_100continue, 0, s_100continue.Length);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Unquote(string str)
|
||||
{
|
||||
int start = str.IndexOf('\"');
|
||||
int end = str.LastIndexOf('\"');
|
||||
if (start >= 0 && end >= 0)
|
||||
str = str.Substring(start + 1, end - 1);
|
||||
return str.Trim();
|
||||
}
|
||||
|
||||
internal void AddHeader(string header)
|
||||
{
|
||||
int colon = header.IndexOf(':');
|
||||
if (colon == -1 || colon == 0)
|
||||
{
|
||||
_context.ErrorMessage = HttpStatusDescription.Get(400);
|
||||
_context.ErrorStatus = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
string name = header.Substring(0, colon).Trim();
|
||||
string val = header.Substring(colon + 1).Trim();
|
||||
if (name.Equals("content-length", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// To match Windows behavior:
|
||||
// Content lengths >= 0 and <= long.MaxValue are accepted as is.
|
||||
// Content lengths > long.MaxValue and <= ulong.MaxValue are treated as 0.
|
||||
// Content lengths < 0 cause the requests to fail.
|
||||
// Other input is a failure, too.
|
||||
long parsedContentLength =
|
||||
ulong.TryParse(val, out ulong parsedUlongContentLength) ? (parsedUlongContentLength <= long.MaxValue ? (long)parsedUlongContentLength : 0) :
|
||||
long.Parse(val);
|
||||
if (parsedContentLength < 0 || (_clSet && parsedContentLength != _contentLength))
|
||||
{
|
||||
_context.ErrorMessage = "Invalid Content-Length.";
|
||||
}
|
||||
else
|
||||
{
|
||||
_contentLength = parsedContentLength;
|
||||
_clSet = true;
|
||||
}
|
||||
}
|
||||
else if (name.Equals("transfer-encoding", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Headers[HttpKnownHeaderNames.TransferEncoding] != null)
|
||||
{
|
||||
_context.ErrorStatus = (int)HttpStatusCode.NotImplemented;
|
||||
_context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.NotImplemented);
|
||||
}
|
||||
}
|
||||
|
||||
if (_context.ErrorMessage == null)
|
||||
{
|
||||
_headers.Set(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
// returns true is the stream could be reused.
|
||||
internal bool FlushInput()
|
||||
{
|
||||
if (!HasEntityBody)
|
||||
return true;
|
||||
|
||||
int length = 2048;
|
||||
if (_contentLength > 0)
|
||||
length = (int)Math.Min(_contentLength, (long)length);
|
||||
|
||||
byte[] bytes = new byte[length];
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
IAsyncResult ares = InputStream.BeginRead(bytes, 0, length, null, null);
|
||||
if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne(1000))
|
||||
return false;
|
||||
if (InputStream.EndRead(ares) <= 0)
|
||||
return true;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
_inputStream = null;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long ContentLength64
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isChunked)
|
||||
_contentLength = -1;
|
||||
|
||||
return _contentLength;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasEntityBody => (_contentLength > 0 || _isChunked);
|
||||
|
||||
public QueryParamCollection Headers => _headers;
|
||||
|
||||
public string HttpMethod => _method;
|
||||
|
||||
public Stream InputStream
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_inputStream == null)
|
||||
{
|
||||
if (_isChunked || _contentLength > 0)
|
||||
_inputStream = _context.Connection.GetRequestStream(_isChunked, _contentLength);
|
||||
else
|
||||
_inputStream = Stream.Null;
|
||||
}
|
||||
|
||||
return _inputStream;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAuthenticated => false;
|
||||
|
||||
public bool IsSecureConnection => _context.Connection.IsSecure;
|
||||
|
||||
public System.Net.IPEndPoint LocalEndPoint => _context.Connection.LocalEndPoint;
|
||||
|
||||
public System.Net.IPEndPoint RemoteEndPoint => _context.Connection.RemoteEndPoint;
|
||||
|
||||
public Guid RequestTraceIdentifier { get; } = Guid.NewGuid();
|
||||
|
||||
public string ServiceName => null;
|
||||
|
||||
private Uri RequestUri => _requestUri;
|
||||
private bool SupportsWebSockets => true;
|
||||
}
|
||||
}
|
@ -1,537 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.Services;
|
||||
using SocketHttpListener.Net.WebSockets;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed partial class HttpListenerRequest
|
||||
{
|
||||
private CookieCollection _cookies;
|
||||
private bool? _keepAlive;
|
||||
private string _rawUrl;
|
||||
private Uri _requestUri;
|
||||
private Version _version;
|
||||
|
||||
public string[] AcceptTypes => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.Accept]);
|
||||
|
||||
public string[] UserLanguages => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.AcceptLanguage]);
|
||||
|
||||
private static CookieCollection ParseCookies(Uri uri, string setCookieHeader)
|
||||
{
|
||||
var cookies = new CookieCollection();
|
||||
return cookies;
|
||||
}
|
||||
|
||||
public CookieCollection Cookies
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cookies == null)
|
||||
{
|
||||
string cookieString = Headers[HttpKnownHeaderNames.Cookie];
|
||||
if (!string.IsNullOrEmpty(cookieString))
|
||||
{
|
||||
_cookies = ParseCookies(RequestUri, cookieString);
|
||||
}
|
||||
if (_cookies == null)
|
||||
{
|
||||
_cookies = new CookieCollection();
|
||||
}
|
||||
}
|
||||
return _cookies;
|
||||
}
|
||||
}
|
||||
|
||||
public Encoding ContentEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP"))
|
||||
{
|
||||
string postDataCharset = Headers["x-up-devcap-post-charset"];
|
||||
if (postDataCharset != null && postDataCharset.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding(postDataCharset);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
if (HasEntityBody)
|
||||
{
|
||||
if (ContentType != null)
|
||||
{
|
||||
string charSet = Helpers.GetCharSetValueFromHeader(ContentType);
|
||||
if (charSet != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding(charSet);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Encoding.UTF8;
|
||||
}
|
||||
}
|
||||
|
||||
public string ContentType => Headers[HttpKnownHeaderNames.ContentType];
|
||||
|
||||
public bool IsLocal => LocalEndPoint.Address.Equals(RemoteEndPoint.Address);
|
||||
|
||||
public bool IsWebSocketRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!SupportsWebSockets)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool foundConnectionUpgradeHeader = false;
|
||||
if (string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Connection]) || string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Upgrade]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (string connection in Headers.GetValues(HttpKnownHeaderNames.Connection))
|
||||
{
|
||||
if (string.Equals(connection, HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foundConnectionUpgradeHeader = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundConnectionUpgradeHeader)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (string upgrade in Headers.GetValues(HttpKnownHeaderNames.Upgrade))
|
||||
{
|
||||
if (string.Equals(upgrade, HttpWebSocket.WebSocketUpgradeToken, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool KeepAlive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_keepAlive.HasValue)
|
||||
{
|
||||
string header = Headers[HttpKnownHeaderNames.ProxyConnection];
|
||||
if (string.IsNullOrEmpty(header))
|
||||
{
|
||||
header = Headers[HttpKnownHeaderNames.Connection];
|
||||
}
|
||||
if (string.IsNullOrEmpty(header))
|
||||
{
|
||||
if (ProtocolVersion >= HttpVersion.Version11)
|
||||
{
|
||||
_keepAlive = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
header = Headers[HttpKnownHeaderNames.KeepAlive];
|
||||
_keepAlive = !string.IsNullOrEmpty(header);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
header = header.ToLowerInvariant();
|
||||
_keepAlive =
|
||||
header.IndexOf("close", StringComparison.OrdinalIgnoreCase) < 0 ||
|
||||
header.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
return _keepAlive.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public QueryParamCollection QueryString
|
||||
{
|
||||
get
|
||||
{
|
||||
var queryString = new QueryParamCollection();
|
||||
Helpers.FillFromString(queryString, Url.Query, true, ContentEncoding);
|
||||
return queryString;
|
||||
}
|
||||
}
|
||||
|
||||
public string RawUrl => _rawUrl;
|
||||
|
||||
private string RequestScheme => IsSecureConnection ? UriScheme.Https : UriScheme.Http;
|
||||
|
||||
public string UserAgent => Headers[HttpKnownHeaderNames.UserAgent];
|
||||
|
||||
public string UserHostAddress => LocalEndPoint.ToString();
|
||||
|
||||
public string UserHostName => Headers[HttpKnownHeaderNames.Host];
|
||||
|
||||
public Uri UrlReferrer
|
||||
{
|
||||
get
|
||||
{
|
||||
string referrer = Headers[HttpKnownHeaderNames.Referer];
|
||||
if (referrer == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
bool success = Uri.TryCreate(referrer, UriKind.RelativeOrAbsolute, out var urlReferrer);
|
||||
return success ? urlReferrer : null;
|
||||
}
|
||||
}
|
||||
|
||||
public Uri Url => RequestUri;
|
||||
|
||||
public Version ProtocolVersion => _version;
|
||||
|
||||
private static class Helpers
|
||||
{
|
||||
//
|
||||
// Get attribute off header value
|
||||
//
|
||||
internal static string GetCharSetValueFromHeader(string headerValue)
|
||||
{
|
||||
const string AttrName = "charset";
|
||||
|
||||
if (headerValue == null)
|
||||
return null;
|
||||
|
||||
int l = headerValue.Length;
|
||||
int k = AttrName.Length;
|
||||
|
||||
// find properly separated attribute name
|
||||
int i = 1; // start searching from 1
|
||||
|
||||
while (i < l)
|
||||
{
|
||||
i = CultureInfo.InvariantCulture.CompareInfo.IndexOf(headerValue, AttrName, i, CompareOptions.IgnoreCase);
|
||||
if (i < 0)
|
||||
break;
|
||||
if (i + k >= l)
|
||||
break;
|
||||
|
||||
char chPrev = headerValue[i - 1];
|
||||
char chNext = headerValue[i + k];
|
||||
if ((chPrev == ';' || chPrev == ',' || char.IsWhiteSpace(chPrev)) && (chNext == '=' || char.IsWhiteSpace(chNext)))
|
||||
break;
|
||||
|
||||
i += k;
|
||||
}
|
||||
|
||||
if (i < 0 || i >= l)
|
||||
return null;
|
||||
|
||||
// skip to '=' and the following whitespace
|
||||
i += k;
|
||||
while (i < l && char.IsWhiteSpace(headerValue[i]))
|
||||
i++;
|
||||
if (i >= l || headerValue[i] != '=')
|
||||
return null;
|
||||
i++;
|
||||
while (i < l && char.IsWhiteSpace(headerValue[i]))
|
||||
i++;
|
||||
if (i >= l)
|
||||
return null;
|
||||
|
||||
// parse the value
|
||||
string attrValue = null;
|
||||
|
||||
int j;
|
||||
|
||||
if (i < l && headerValue[i] == '"')
|
||||
{
|
||||
if (i == l - 1)
|
||||
return null;
|
||||
j = headerValue.IndexOf('"', i + 1);
|
||||
if (j < 0 || j == i + 1)
|
||||
return null;
|
||||
|
||||
attrValue = headerValue.Substring(i + 1, j - i - 1).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (j = i; j < l; j++)
|
||||
{
|
||||
if (headerValue[j] == ';')
|
||||
break;
|
||||
}
|
||||
|
||||
if (j == i)
|
||||
return null;
|
||||
|
||||
attrValue = headerValue.Substring(i, j - i).Trim();
|
||||
}
|
||||
|
||||
return attrValue;
|
||||
}
|
||||
|
||||
internal static string[] ParseMultivalueHeader(string s)
|
||||
{
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
int l = s.Length;
|
||||
|
||||
// collect comma-separated values into list
|
||||
|
||||
var values = new List<string>();
|
||||
int i = 0;
|
||||
|
||||
while (i < l)
|
||||
{
|
||||
// find next ,
|
||||
int ci = s.IndexOf(',', i);
|
||||
if (ci < 0)
|
||||
ci = l;
|
||||
|
||||
// append corresponding server value
|
||||
values.Add(s.Substring(i, ci - i));
|
||||
|
||||
// move to next
|
||||
i = ci + 1;
|
||||
|
||||
// skip leading space
|
||||
if (i < l && s[i] == ' ')
|
||||
i++;
|
||||
}
|
||||
|
||||
// return list as array of strings
|
||||
|
||||
int n = values.Count;
|
||||
string[] strings;
|
||||
|
||||
// if n is 0 that means s was empty string
|
||||
|
||||
if (n == 0)
|
||||
{
|
||||
strings = new string[1];
|
||||
strings[0] = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
strings = new string[n];
|
||||
values.CopyTo(0, strings, 0, n);
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
|
||||
private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
|
||||
{
|
||||
int count = s.Length;
|
||||
var helper = new UrlDecoder(count, e);
|
||||
|
||||
// go through the string's chars collapsing %XX and %uXXXX and
|
||||
// appending each char as char, with exception of %XX constructs
|
||||
// that are appended as bytes
|
||||
|
||||
for (int pos = 0; pos < count; pos++)
|
||||
{
|
||||
char ch = s[pos];
|
||||
|
||||
if (ch == '+')
|
||||
{
|
||||
ch = ' ';
|
||||
}
|
||||
else if (ch == '%' && pos < count - 2)
|
||||
{
|
||||
if (s[pos + 1] == 'u' && pos < count - 5)
|
||||
{
|
||||
int h1 = HexToInt(s[pos + 2]);
|
||||
int h2 = HexToInt(s[pos + 3]);
|
||||
int h3 = HexToInt(s[pos + 4]);
|
||||
int h4 = HexToInt(s[pos + 5]);
|
||||
|
||||
if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0)
|
||||
{ // valid 4 hex chars
|
||||
ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
|
||||
pos += 5;
|
||||
|
||||
// only add as char
|
||||
helper.AddChar(ch);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int h1 = HexToInt(s[pos + 1]);
|
||||
int h2 = HexToInt(s[pos + 2]);
|
||||
|
||||
if (h1 >= 0 && h2 >= 0)
|
||||
{ // valid 2 hex chars
|
||||
byte b = (byte)((h1 << 4) | h2);
|
||||
pos += 2;
|
||||
|
||||
// don't add as char
|
||||
helper.AddByte(b);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((ch & 0xFF80) == 0)
|
||||
helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
|
||||
else
|
||||
helper.AddChar(ch);
|
||||
}
|
||||
|
||||
return helper.GetString();
|
||||
}
|
||||
|
||||
private static int HexToInt(char h)
|
||||
{
|
||||
return (h >= '0' && h <= '9') ? h - '0' :
|
||||
(h >= 'a' && h <= 'f') ? h - 'a' + 10 :
|
||||
(h >= 'A' && h <= 'F') ? h - 'A' + 10 :
|
||||
-1;
|
||||
}
|
||||
|
||||
private class UrlDecoder
|
||||
{
|
||||
private int _bufferSize;
|
||||
|
||||
// Accumulate characters in a special array
|
||||
private int _numChars;
|
||||
private char[] _charBuffer;
|
||||
|
||||
// Accumulate bytes for decoding into characters in a special array
|
||||
private int _numBytes;
|
||||
private byte[] _byteBuffer;
|
||||
|
||||
// Encoding to convert chars to bytes
|
||||
private Encoding _encoding;
|
||||
|
||||
private void FlushBytes()
|
||||
{
|
||||
if (_numBytes > 0)
|
||||
{
|
||||
_numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
|
||||
_numBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal UrlDecoder(int bufferSize, Encoding encoding)
|
||||
{
|
||||
_bufferSize = bufferSize;
|
||||
_encoding = encoding;
|
||||
|
||||
_charBuffer = new char[bufferSize];
|
||||
// byte buffer created on demand
|
||||
}
|
||||
|
||||
internal void AddChar(char ch)
|
||||
{
|
||||
if (_numBytes > 0)
|
||||
FlushBytes();
|
||||
|
||||
_charBuffer[_numChars++] = ch;
|
||||
}
|
||||
|
||||
internal void AddByte(byte b)
|
||||
{
|
||||
{
|
||||
if (_byteBuffer == null)
|
||||
_byteBuffer = new byte[_bufferSize];
|
||||
|
||||
_byteBuffer[_numBytes++] = b;
|
||||
}
|
||||
}
|
||||
|
||||
internal string GetString()
|
||||
{
|
||||
if (_numBytes > 0)
|
||||
FlushBytes();
|
||||
|
||||
if (_numChars > 0)
|
||||
return new string(_charBuffer, 0, _numChars);
|
||||
else
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static void FillFromString(QueryParamCollection nvc, string s, bool urlencoded, Encoding encoding)
|
||||
{
|
||||
int l = (s != null) ? s.Length : 0;
|
||||
int i = (s.Length > 0 && s[0] == '?') ? 1 : 0;
|
||||
|
||||
while (i < l)
|
||||
{
|
||||
// find next & while noting first = on the way (and if there are more)
|
||||
|
||||
int si = i;
|
||||
int ti = -1;
|
||||
|
||||
while (i < l)
|
||||
{
|
||||
char ch = s[i];
|
||||
|
||||
if (ch == '=')
|
||||
{
|
||||
if (ti < 0)
|
||||
ti = i;
|
||||
}
|
||||
else if (ch == '&')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// extract the name / value pair
|
||||
|
||||
string name = null;
|
||||
string value = null;
|
||||
|
||||
if (ti >= 0)
|
||||
{
|
||||
name = s.Substring(si, ti - si);
|
||||
value = s.Substring(ti + 1, i - ti - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = s.Substring(si, i - si);
|
||||
}
|
||||
|
||||
// add name / value pair to the collection
|
||||
|
||||
if (urlencoded)
|
||||
nvc.Add(
|
||||
name == null ? null : UrlDecodeStringFromStringInternal(name, encoding),
|
||||
UrlDecodeStringFromStringInternal(value, encoding));
|
||||
else
|
||||
nvc.Add(name, value);
|
||||
|
||||
// trailing '&'
|
||||
|
||||
if (i == l - 1 && s[i] == '&')
|
||||
nvc.Add(null, "");
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,443 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
// We don't use the cooked URL because http.sys unescapes all percent-encoded values. However,
|
||||
// we also can't just use the raw Uri, since http.sys supports not only Utf-8, but also ANSI/DBCS and
|
||||
// Unicode code points. System.Uri only supports Utf-8.
|
||||
// The purpose of this class is to convert all ANSI, DBCS, and Unicode code points into percent encoded
|
||||
// Utf-8 characters.
|
||||
internal sealed class HttpListenerRequestUriBuilder
|
||||
{
|
||||
private static readonly Encoding s_utf8Encoding = new UTF8Encoding(false, true);
|
||||
private static readonly Encoding s_ansiEncoding = Encoding.GetEncoding(0, new EncoderExceptionFallback(), new DecoderExceptionFallback());
|
||||
|
||||
private readonly string _rawUri;
|
||||
private readonly string _cookedUriScheme;
|
||||
private readonly string _cookedUriHost;
|
||||
private readonly string _cookedUriPath;
|
||||
private readonly string _cookedUriQuery;
|
||||
|
||||
// This field is used to build the final request Uri string from the Uri parts passed to the ctor.
|
||||
private StringBuilder _requestUriString;
|
||||
|
||||
// The raw path is parsed by looping through all characters from left to right. 'rawOctets'
|
||||
// is used to store consecutive percent encoded octets as actual byte values: e.g. for path /pa%C3%84th%2F/
|
||||
// rawOctets will be set to { 0xC3, 0x84 } when we reach character 't' and it will be { 0x2F } when
|
||||
// we reach the final '/'. I.e. after a sequence of percent encoded octets ends, we use rawOctets as
|
||||
// input to the encoding and percent encode the resulting string into UTF-8 octets.
|
||||
//
|
||||
// When parsing ANSI (Latin 1) encoded path '/pa%C4th/', %C4 will be added to rawOctets and when
|
||||
// we reach 't', the content of rawOctets { 0xC4 } will be fed into the ANSI encoding. The resulting
|
||||
// string 'Ä' will be percent encoded into UTF-8 octets and appended to requestUriString. The final
|
||||
// path will be '/pa%C3%84th/', where '%C3%84' is the UTF-8 percent encoded character 'Ä'.
|
||||
private List<byte> _rawOctets;
|
||||
private string _rawPath;
|
||||
|
||||
// Holds the final request Uri.
|
||||
private Uri _requestUri;
|
||||
|
||||
private HttpListenerRequestUriBuilder(string rawUri, string cookedUriScheme, string cookedUriHost,
|
||||
string cookedUriPath, string cookedUriQuery)
|
||||
{
|
||||
_rawUri = rawUri;
|
||||
_cookedUriScheme = cookedUriScheme;
|
||||
_cookedUriHost = cookedUriHost;
|
||||
_cookedUriPath = AddSlashToAsteriskOnlyPath(cookedUriPath);
|
||||
_cookedUriQuery = cookedUriQuery ?? string.Empty;
|
||||
}
|
||||
|
||||
public static Uri GetRequestUri(string rawUri, string cookedUriScheme, string cookedUriHost,
|
||||
string cookedUriPath, string cookedUriQuery)
|
||||
{
|
||||
var builder = new HttpListenerRequestUriBuilder(rawUri,
|
||||
cookedUriScheme, cookedUriHost, cookedUriPath, cookedUriQuery);
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
private Uri Build()
|
||||
{
|
||||
BuildRequestUriUsingRawPath();
|
||||
|
||||
if (_requestUri == null)
|
||||
{
|
||||
BuildRequestUriUsingCookedPath();
|
||||
}
|
||||
|
||||
return _requestUri;
|
||||
}
|
||||
|
||||
private void BuildRequestUriUsingCookedPath()
|
||||
{
|
||||
bool isValid = Uri.TryCreate(_cookedUriScheme + Uri.SchemeDelimiter + _cookedUriHost + _cookedUriPath +
|
||||
_cookedUriQuery, UriKind.Absolute, out _requestUri);
|
||||
|
||||
// Creating a Uri from the cooked Uri should really always work: If not, we log at least.
|
||||
if (!isValid)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _cookedUriPath, _cookedUriQuery));
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildRequestUriUsingRawPath()
|
||||
{
|
||||
bool isValid = false;
|
||||
|
||||
// Initialize 'rawPath' only if really needed; i.e. if we build the request Uri from the raw Uri.
|
||||
_rawPath = GetPath(_rawUri);
|
||||
|
||||
// Try to check the raw path using first the primary encoding (according to http.sys settings);
|
||||
// if it fails try the secondary encoding.
|
||||
ParsingResult result = BuildRequestUriUsingRawPath(GetEncoding(EncodingType.Primary));
|
||||
if (result == ParsingResult.EncodingError)
|
||||
{
|
||||
Encoding secondaryEncoding = GetEncoding(EncodingType.Secondary);
|
||||
result = BuildRequestUriUsingRawPath(secondaryEncoding);
|
||||
}
|
||||
isValid = (result == ParsingResult.Success) ? true : false;
|
||||
|
||||
// Log that we weren't able to create a Uri from the raw string.
|
||||
if (!isValid)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _rawPath, _cookedUriQuery));
|
||||
}
|
||||
}
|
||||
|
||||
private static Encoding GetEncoding(EncodingType type)
|
||||
{
|
||||
Debug.Assert((type == EncodingType.Primary) || (type == EncodingType.Secondary),
|
||||
"Unknown 'EncodingType' value: " + type.ToString());
|
||||
|
||||
if (type == EncodingType.Secondary)
|
||||
{
|
||||
return s_ansiEncoding;
|
||||
}
|
||||
else
|
||||
{
|
||||
return s_utf8Encoding;
|
||||
}
|
||||
}
|
||||
|
||||
private ParsingResult BuildRequestUriUsingRawPath(Encoding encoding)
|
||||
{
|
||||
Debug.Assert(encoding != null, "'encoding' must be assigned.");
|
||||
Debug.Assert(!string.IsNullOrEmpty(_rawPath), "'rawPath' must have at least one character.");
|
||||
|
||||
_rawOctets = new List<byte>();
|
||||
_requestUriString = new StringBuilder();
|
||||
_requestUriString.Append(_cookedUriScheme);
|
||||
_requestUriString.Append(Uri.SchemeDelimiter);
|
||||
_requestUriString.Append(_cookedUriHost);
|
||||
|
||||
ParsingResult result = ParseRawPath(encoding);
|
||||
if (result == ParsingResult.Success)
|
||||
{
|
||||
_requestUriString.Append(_cookedUriQuery);
|
||||
|
||||
Debug.Assert(_rawOctets.Count == 0,
|
||||
"Still raw octets left. They must be added to the result path.");
|
||||
|
||||
if (!Uri.TryCreate(_requestUriString.ToString(), UriKind.Absolute, out _requestUri))
|
||||
{
|
||||
// If we can't create a Uri from the string, this is an invalid string and it doesn't make
|
||||
// sense to try another encoding.
|
||||
result = ParsingResult.InvalidString;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != ParsingResult.Success)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_raw_path, _rawPath, encoding.EncodingName));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ParsingResult ParseRawPath(Encoding encoding)
|
||||
{
|
||||
Debug.Assert(encoding != null, "'encoding' must be assigned.");
|
||||
|
||||
int index = 0;
|
||||
char current = '\0';
|
||||
while (index < _rawPath.Length)
|
||||
{
|
||||
current = _rawPath[index];
|
||||
if (current == '%')
|
||||
{
|
||||
// Assert is enough, since http.sys accepted the request string already. This should never happen.
|
||||
Debug.Assert(index + 2 < _rawPath.Length, "Expected >=2 characters after '%' (e.g. %2F)");
|
||||
|
||||
index++;
|
||||
current = _rawPath[index];
|
||||
if (current == 'u' || current == 'U')
|
||||
{
|
||||
// We found "%u" which means, we have a Unicode code point of the form "%uXXXX".
|
||||
Debug.Assert(index + 4 < _rawPath.Length, "Expected >=4 characters after '%u' (e.g. %u0062)");
|
||||
|
||||
// Decode the content of rawOctets into percent encoded UTF-8 characters and append them
|
||||
// to requestUriString.
|
||||
if (!EmptyDecodeAndAppendRawOctetsList(encoding))
|
||||
{
|
||||
return ParsingResult.EncodingError;
|
||||
}
|
||||
if (!AppendUnicodeCodePointValuePercentEncoded(_rawPath.Substring(index + 1, 4)))
|
||||
{
|
||||
return ParsingResult.InvalidString;
|
||||
}
|
||||
index += 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We found '%', but not followed by 'u', i.e. we have a percent encoded octed: %XX
|
||||
if (!AddPercentEncodedOctetToRawOctetsList(encoding, _rawPath.Substring(index, 2)))
|
||||
{
|
||||
return ParsingResult.InvalidString;
|
||||
}
|
||||
index += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We found a non-'%' character: decode the content of rawOctets into percent encoded
|
||||
// UTF-8 characters and append it to the result.
|
||||
if (!EmptyDecodeAndAppendRawOctetsList(encoding))
|
||||
{
|
||||
return ParsingResult.EncodingError;
|
||||
}
|
||||
// Append the current character to the result.
|
||||
_requestUriString.Append(current);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// if the raw path ends with a sequence of percent encoded octets, make sure those get added to the
|
||||
// result (requestUriString).
|
||||
if (!EmptyDecodeAndAppendRawOctetsList(encoding))
|
||||
{
|
||||
return ParsingResult.EncodingError;
|
||||
}
|
||||
|
||||
return ParsingResult.Success;
|
||||
}
|
||||
|
||||
private bool AppendUnicodeCodePointValuePercentEncoded(string codePoint)
|
||||
{
|
||||
// http.sys only supports %uXXXX (4 hex-digits), even though unicode code points could have up to
|
||||
// 6 hex digits. Therefore we parse always 4 characters after %u and convert them to an int.
|
||||
if (!int.TryParse(codePoint, NumberStyles.HexNumber, null, out var codePointValue))
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint));
|
||||
return false;
|
||||
}
|
||||
|
||||
string unicodeString = null;
|
||||
try
|
||||
{
|
||||
unicodeString = char.ConvertFromUtf32(codePointValue);
|
||||
AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(unicodeString));
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint));
|
||||
}
|
||||
catch (EncoderFallbackException)
|
||||
{
|
||||
// If utf8Encoding.GetBytes() fails
|
||||
//if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, unicodeString, e.Message));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AddPercentEncodedOctetToRawOctetsList(Encoding encoding, string escapedCharacter)
|
||||
{
|
||||
if (!byte.TryParse(escapedCharacter, NumberStyles.HexNumber, null, out byte encodedValue))
|
||||
{
|
||||
//if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, escapedCharacter));
|
||||
return false;
|
||||
}
|
||||
|
||||
_rawOctets.Add(encodedValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EmptyDecodeAndAppendRawOctetsList(Encoding encoding)
|
||||
{
|
||||
if (_rawOctets.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string decodedString = null;
|
||||
try
|
||||
{
|
||||
// If the encoding can get a string out of the byte array, this is a valid string in the
|
||||
// 'encoding' encoding.
|
||||
decodedString = encoding.GetString(_rawOctets.ToArray());
|
||||
|
||||
if (encoding == s_utf8Encoding)
|
||||
{
|
||||
AppendOctetsPercentEncoded(_requestUriString, _rawOctets.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(decodedString));
|
||||
}
|
||||
|
||||
_rawOctets.Clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (DecoderFallbackException)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_bytes, GetOctetsAsString(_rawOctets), e.Message));
|
||||
}
|
||||
catch (EncoderFallbackException)
|
||||
{
|
||||
// If utf8Encoding.GetBytes() fails
|
||||
//if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, decodedString, e.Message));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void AppendOctetsPercentEncoded(StringBuilder target, IEnumerable<byte> octets)
|
||||
{
|
||||
foreach (byte octet in octets)
|
||||
{
|
||||
target.Append('%');
|
||||
target.Append(octet.ToString("X2", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetOctetsAsString(IEnumerable<byte> octets)
|
||||
{
|
||||
var octetString = new StringBuilder();
|
||||
|
||||
bool first = true;
|
||||
foreach (byte octet in octets)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
octetString.Append(' ');
|
||||
}
|
||||
octetString.Append(octet.ToString("X2", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return octetString.ToString();
|
||||
}
|
||||
|
||||
private static string GetPath(string uriString)
|
||||
{
|
||||
Debug.Assert(uriString != null, "uriString must not be null");
|
||||
Debug.Assert(uriString.Length > 0, "uriString must not be empty");
|
||||
|
||||
int pathStartIndex = 0;
|
||||
|
||||
// Perf. improvement: nearly all strings are relative Uris. So just look if the
|
||||
// string starts with '/'. If so, we have a relative Uri and the path starts at position 0.
|
||||
// (http.sys already trimmed leading whitespaces)
|
||||
if (uriString[0] != '/')
|
||||
{
|
||||
// We can't check against cookedUriScheme, since http.sys allows for request http://myserver/ to
|
||||
// use a request line 'GET https://myserver/' (note http vs. https). Therefore check if the
|
||||
// Uri starts with either http:// or https://.
|
||||
int authorityStartIndex = 0;
|
||||
if (uriString.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
authorityStartIndex = 7;
|
||||
}
|
||||
else if (uriString.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
authorityStartIndex = 8;
|
||||
}
|
||||
|
||||
if (authorityStartIndex > 0)
|
||||
{
|
||||
// we have an absolute Uri. Find out where the authority ends and the path begins.
|
||||
// Note that Uris like "http://server?query=value/1/2" are invalid according to RFC2616
|
||||
// and http.sys behavior: If the Uri contains a query, there must be at least one '/'
|
||||
// between the authority and the '?' character: It's safe to just look for the first
|
||||
// '/' after the authority to determine the beginning of the path.
|
||||
pathStartIndex = uriString.IndexOf('/', authorityStartIndex);
|
||||
if (pathStartIndex == -1)
|
||||
{
|
||||
// e.g. for request lines like: 'GET http://myserver' (no final '/')
|
||||
pathStartIndex = uriString.Length;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RFC2616: Request-URI = "*" | absoluteURI | abs_path | authority
|
||||
// 'authority' can only be used with CONNECT which is never received by HttpListener.
|
||||
// I.e. if we don't have an absolute path (must start with '/') and we don't have
|
||||
// an absolute Uri (must start with http:// or https://), then 'uriString' must be '*'.
|
||||
Debug.Assert((uriString.Length == 1) && (uriString[0] == '*'), "Unknown request Uri string format",
|
||||
"Request Uri string is not an absolute Uri, absolute path, or '*': {0}", uriString);
|
||||
|
||||
// Should we ever get here, be consistent with 2.0/3.5 behavior: just add an initial
|
||||
// slash to the string and treat it as a path:
|
||||
uriString = "/" + uriString;
|
||||
}
|
||||
}
|
||||
|
||||
// Find end of path: The path is terminated by
|
||||
// - the first '?' character
|
||||
// - the first '#' character: This is never the case here, since http.sys won't accept
|
||||
// Uris containing fragments. Also, RFC2616 doesn't allow fragments in request Uris.
|
||||
// - end of Uri string
|
||||
int queryIndex = uriString.IndexOf('?');
|
||||
if (queryIndex == -1)
|
||||
{
|
||||
queryIndex = uriString.Length;
|
||||
}
|
||||
|
||||
// will always return a != null string.
|
||||
return AddSlashToAsteriskOnlyPath(uriString.Substring(pathStartIndex, queryIndex - pathStartIndex));
|
||||
}
|
||||
|
||||
private static string AddSlashToAsteriskOnlyPath(string path)
|
||||
{
|
||||
Debug.Assert(path != null, "'path' must not be null");
|
||||
|
||||
// If a request like "OPTIONS * HTTP/1.1" is sent to the listener, then the request Uri
|
||||
// should be "http[s]://server[:port]/*" to be compatible with pre-4.0 behavior.
|
||||
if ((path.Length == 1) && (path[0] == '*'))
|
||||
{
|
||||
return "/*";
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private enum ParsingResult
|
||||
{
|
||||
Success,
|
||||
InvalidString,
|
||||
EncodingError
|
||||
}
|
||||
|
||||
private enum EncodingType
|
||||
{
|
||||
Primary,
|
||||
Secondary
|
||||
}
|
||||
}
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed partial class HttpListenerResponse : IDisposable
|
||||
{
|
||||
private BoundaryType _boundaryType = BoundaryType.None;
|
||||
private CookieCollection _cookies;
|
||||
private HttpListenerContext _httpContext;
|
||||
private bool _keepAlive = true;
|
||||
private HttpResponseStream _responseStream;
|
||||
private string _statusDescription;
|
||||
private WebHeaderCollection _webHeaders = new WebHeaderCollection();
|
||||
|
||||
public WebHeaderCollection Headers => _webHeaders;
|
||||
|
||||
public Encoding ContentEncoding { get; set; }
|
||||
|
||||
public string ContentType
|
||||
{
|
||||
get => Headers["Content-Type"];
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
Headers.Remove("Content-Type");
|
||||
}
|
||||
else
|
||||
{
|
||||
Headers.Set("Content-Type", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HttpListenerContext HttpListenerContext => _httpContext;
|
||||
|
||||
private HttpListenerRequest HttpListenerRequest => HttpListenerContext.Request;
|
||||
|
||||
internal EntitySendFormat EntitySendFormat
|
||||
{
|
||||
get => (EntitySendFormat)_boundaryType;
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
CheckSentHeaders();
|
||||
if (value == EntitySendFormat.Chunked && HttpListenerRequest.ProtocolVersion.Minor == 0)
|
||||
{
|
||||
throw new ProtocolViolationException("net_nochunkuploadonhttp10");
|
||||
}
|
||||
_boundaryType = (BoundaryType)value;
|
||||
if (value != EntitySendFormat.ContentLength)
|
||||
{
|
||||
_contentLength = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SendChunked
|
||||
{
|
||||
get => EntitySendFormat == EntitySendFormat.Chunked;
|
||||
set => EntitySendFormat = value ? EntitySendFormat.Chunked : EntitySendFormat.ContentLength;
|
||||
}
|
||||
|
||||
// We MUST NOT send message-body when we send responses with these Status codes
|
||||
private static readonly int[] s_noResponseBody = { 100, 101, 204, 205, 304 };
|
||||
|
||||
private static bool CanSendResponseBody(int responseCode)
|
||||
{
|
||||
for (int i = 0; i < s_noResponseBody.Length; i++)
|
||||
{
|
||||
if (responseCode == s_noResponseBody[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public long ContentLength64
|
||||
{
|
||||
get => _contentLength;
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
CheckSentHeaders();
|
||||
if (value >= 0)
|
||||
{
|
||||
_contentLength = value;
|
||||
_boundaryType = BoundaryType.ContentLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CookieCollection Cookies
|
||||
{
|
||||
get => _cookies ?? (_cookies = new CookieCollection());
|
||||
set => _cookies = value;
|
||||
}
|
||||
|
||||
public bool KeepAlive
|
||||
{
|
||||
get => _keepAlive;
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
_keepAlive = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream OutputStream
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckDisposed();
|
||||
EnsureResponseStream();
|
||||
return _responseStream;
|
||||
}
|
||||
}
|
||||
|
||||
public string RedirectLocation
|
||||
{
|
||||
get => Headers["Location"];
|
||||
set
|
||||
{
|
||||
// note that this doesn't set the status code to a redirect one
|
||||
CheckDisposed();
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
Headers.Remove("Location");
|
||||
}
|
||||
else
|
||||
{
|
||||
Headers.Set("Location", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string StatusDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_statusDescription == null)
|
||||
{
|
||||
// if the user hasn't set this, generated on the fly, if possible.
|
||||
// We know this one is safe, no need to verify it as in the setter.
|
||||
_statusDescription = HttpStatusDescription.Get(StatusCode);
|
||||
}
|
||||
if (_statusDescription == null)
|
||||
{
|
||||
_statusDescription = string.Empty;
|
||||
}
|
||||
return _statusDescription;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
// Need to verify the status description doesn't contain any control characters except HT. We mask off the high
|
||||
// byte since that's how it's encoded.
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
char c = (char)(0x000000ff & (uint)value[i]);
|
||||
if ((c <= 31 && c != (byte)'\t') || c == 127)
|
||||
{
|
||||
throw new ArgumentException("net_WebHeaderInvalidControlChars");
|
||||
}
|
||||
}
|
||||
|
||||
_statusDescription = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
Headers.Set(name, value);
|
||||
}
|
||||
|
||||
public void AppendHeader(string name, string value)
|
||||
{
|
||||
Headers.Add(name, value);
|
||||
}
|
||||
|
||||
public void AppendCookie(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
}
|
||||
Cookies.Add(cookie);
|
||||
}
|
||||
|
||||
private void ComputeCookies()
|
||||
{
|
||||
if (_cookies != null)
|
||||
{
|
||||
// now go through the collection, and concatenate all the cookies in per-variant strings
|
||||
//string setCookie2 = null, setCookie = null;
|
||||
//for (int index = 0; index < _cookies.Count; index++)
|
||||
//{
|
||||
// Cookie cookie = _cookies[index];
|
||||
// string cookieString = cookie.ToServerString();
|
||||
// if (cookieString == null || cookieString.Length == 0)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (cookie.IsRfc2965Variant())
|
||||
// {
|
||||
// setCookie2 = setCookie2 == null ? cookieString : setCookie2 + ", " + cookieString;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// setCookie = setCookie == null ? cookieString : setCookie + ", " + cookieString;
|
||||
// }
|
||||
//}
|
||||
|
||||
//if (!string.IsNullOrEmpty(setCookie))
|
||||
//{
|
||||
// Headers.Set(HttpKnownHeaderNames.SetCookie, setCookie);
|
||||
// if (string.IsNullOrEmpty(setCookie2))
|
||||
// {
|
||||
// Headers.Remove(HttpKnownHeaderNames.SetCookie2);
|
||||
// }
|
||||
//}
|
||||
|
||||
//if (!string.IsNullOrEmpty(setCookie2))
|
||||
//{
|
||||
// Headers.Set(HttpKnownHeaderNames.SetCookie2, setCookie2);
|
||||
// if (string.IsNullOrEmpty(setCookie))
|
||||
// {
|
||||
// Headers.Remove(HttpKnownHeaderNames.SetCookie);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
public void Redirect(string url)
|
||||
{
|
||||
Headers["Location"] = url;
|
||||
StatusCode = (int)HttpStatusCode.Redirect;
|
||||
StatusDescription = "Found";
|
||||
}
|
||||
|
||||
public void SetCookie(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
}
|
||||
|
||||
//Cookie newCookie = cookie.Clone();
|
||||
//int added = Cookies.InternalAdd(newCookie, true);
|
||||
|
||||
//if (added != 1)
|
||||
//{
|
||||
// // The Cookie already existed and couldn't be replaced.
|
||||
// throw new ArgumentException("Cookie exists");
|
||||
//}
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (Disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckSentHeaders()
|
||||
{
|
||||
if (SentHeaders)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
//
|
||||
// System.Net.ResponseStream
|
||||
//
|
||||
// Author:
|
||||
// Gonzalo Paniagua Javier (gonzalo@novell.com)
|
||||
//
|
||||
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
internal partial class HttpRequestStream : Stream
|
||||
{
|
||||
private byte[] _buffer;
|
||||
private int _offset;
|
||||
private int _length;
|
||||
private long _remainingBody;
|
||||
protected bool _closed;
|
||||
private Stream _stream;
|
||||
|
||||
internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length)
|
||||
: this(stream, buffer, offset, length, -1)
|
||||
{
|
||||
}
|
||||
|
||||
internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length, long contentlength)
|
||||
{
|
||||
_stream = stream;
|
||||
_buffer = buffer;
|
||||
_offset = offset;
|
||||
_length = length;
|
||||
_remainingBody = contentlength;
|
||||
}
|
||||
|
||||
// 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 offset, int count)
|
||||
{
|
||||
if (_remainingBody == 0)
|
||||
return -1;
|
||||
|
||||
if (_length == 0)
|
||||
return 0;
|
||||
|
||||
int 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, offset, size);
|
||||
_offset += size;
|
||||
_length -= size;
|
||||
if (_remainingBody > 0)
|
||||
_remainingBody -= size;
|
||||
return size;
|
||||
}
|
||||
|
||||
protected virtual int ReadCore(byte[] buffer, int offset, int size)
|
||||
{
|
||||
// Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0
|
||||
int nread = FillFromBuffer(buffer, offset, size);
|
||||
if (nread == -1)
|
||||
{ // No more bytes available (Content-Length)
|
||||
return 0;
|
||||
}
|
||||
else if (nread > 0)
|
||||
{
|
||||
return nread;
|
||||
}
|
||||
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
size = (int)Math.Min(_remainingBody, (long)size);
|
||||
}
|
||||
|
||||
nread = _stream.Read(buffer, offset, size);
|
||||
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
if (nread == 0)
|
||||
{
|
||||
throw new Exception("Bad request");
|
||||
}
|
||||
|
||||
//Debug.Assert(nread <= _remainingBody);
|
||||
_remainingBody -= nread;
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
protected virtual IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state)
|
||||
{
|
||||
if (size == 0 || _closed)
|
||||
{
|
||||
var ares = new HttpStreamAsyncResult(this);
|
||||
ares._callback = cback;
|
||||
ares._state = state;
|
||||
ares.Complete();
|
||||
return ares;
|
||||
}
|
||||
|
||||
int nread = FillFromBuffer(buffer, offset, size);
|
||||
if (nread > 0 || nread == -1)
|
||||
{
|
||||
var ares = new HttpStreamAsyncResult(this);
|
||||
ares._buffer = buffer;
|
||||
ares._offset = offset;
|
||||
ares._count = size;
|
||||
ares._callback = cback;
|
||||
ares._state = state;
|
||||
ares._synchRead = Math.Max(0, nread);
|
||||
ares.Complete();
|
||||
return ares;
|
||||
}
|
||||
|
||||
// Avoid reading past the end of the request to allow
|
||||
// for HTTP pipelining
|
||||
if (_remainingBody >= 0 && size > _remainingBody)
|
||||
{
|
||||
size = (int)Math.Min(_remainingBody, (long)size);
|
||||
}
|
||||
|
||||
return _stream.BeginRead(buffer, offset, size, cback, state);
|
||||
}
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
if (asyncResult == null)
|
||||
throw new ArgumentNullException(nameof(asyncResult));
|
||||
|
||||
var r = asyncResult as HttpStreamAsyncResult;
|
||||
if (r != null)
|
||||
{
|
||||
if (!ReferenceEquals(this, r._parent))
|
||||
{
|
||||
throw new ArgumentException("Invalid async result");
|
||||
}
|
||||
if (r._endCalled)
|
||||
{
|
||||
throw new InvalidOperationException("invalid end call");
|
||||
}
|
||||
r._endCalled = true;
|
||||
|
||||
if (!asyncResult.IsCompleted)
|
||||
{
|
||||
asyncResult.AsyncWaitHandle.WaitOne();
|
||||
}
|
||||
|
||||
return r._synchRead;
|
||||
}
|
||||
|
||||
if (_closed)
|
||||
return 0;
|
||||
|
||||
int nread = 0;
|
||||
try
|
||||
{
|
||||
nread = _stream.EndRead(asyncResult);
|
||||
}
|
||||
catch (IOException e) when (e.InnerException is ArgumentException || e.InnerException is InvalidOperationException)
|
||||
{
|
||||
throw e.InnerException;
|
||||
}
|
||||
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
if (nread == 0)
|
||||
{
|
||||
throw new Exception("Bad request");
|
||||
}
|
||||
|
||||
_remainingBody -= nread;
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
//
|
||||
// System.Net.ResponseStream
|
||||
//
|
||||
// Author:
|
||||
// Gonzalo Paniagua Javier (gonzalo@novell.com)
|
||||
//
|
||||
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
internal partial class HttpRequestStream : Stream
|
||||
{
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => false;
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int size)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
if (offset < 0 || offset > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
if (size < 0 || size > buffer.Length - offset)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(size));
|
||||
}
|
||||
if (size == 0 || _closed)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ReadCore(buffer, offset, size);
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
if (offset < 0 || offset > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
if (size < 0 || size > buffer.Length - offset)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(size));
|
||||
}
|
||||
|
||||
return BeginReadCore(buffer, offset, size, callback, state);
|
||||
}
|
||||
|
||||
public override void Flush() { }
|
||||
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
return base.BeginWrite(buffer, offset, count, callback, state);
|
||||
}
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
{
|
||||
base.EndWrite(asyncResult);
|
||||
}
|
||||
|
||||
internal bool Closed => _closed;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_closed = true;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue