Merge pull request #1010 from cvium/kestrel_poc

Remove System.Net and port to Kestrel
This commit is contained in:
Bond-009 2019-03-07 21:08:57 +01:00 committed by GitHub
commit 10a0d6bdba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
125 changed files with 860 additions and 11999 deletions

View File

@ -136,7 +136,7 @@ namespace Emby.Dlna.Api
{
var url = Request.AbsoluteUri;
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress);
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress);
var cacheLength = TimeSpan.FromDays(1);
var cacheKey = Request.RawUrl.GetMD5();
@ -147,21 +147,21 @@ namespace Emby.Dlna.Api
public object Get(GetContentDirectory request)
{
var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary());
var xml = ContentDirectory.GetServiceXml();
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
public object Get(GetMediaReceiverRegistrar request)
{
var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary());
var xml = MediaReceiverRegistrar.GetServiceXml();
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
public object Get(GetConnnectionManager request)
{
var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary());
var xml = ConnectionManager.GetServiceXml();
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
@ -193,7 +193,7 @@ namespace Emby.Dlna.Api
return service.ProcessControlRequest(new ControlRequest
{
Headers = Request.Headers.ToDictionary(),
Headers = Request.Headers,
InputXml = requestStream,
TargetServerUuId = id,
RequestedUrl = Request.AbsoluteUri

View File

@ -1,4 +1,3 @@
using System.Collections.Generic;
using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@ -24,7 +23,7 @@ namespace Emby.Dlna.ConnectionManager
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
}
public string GetServiceXml(IDictionary<string, string> headers)
public string GetServiceXml()
{
return new ConnectionManagerXmlBuilder().GetXml();
}

View File

@ -65,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory
}
}
public string GetServiceXml(IDictionary<string, string> headers)
public string GetServiceXml()
{
return new ContentDirectoryXmlBuilder().GetXml();
}

View File

@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Http;
namespace Emby.Dlna
{
public class ControlRequest
{
public IDictionary<string, string> Headers { get; set; }
public IHeaderDictionary Headers { get; set; }
public Stream InputXml { get; set; }
@ -15,7 +15,7 @@ namespace Emby.Dlna
public ControlRequest()
{
Headers = new Dictionary<string, string>();
Headers = new HeaderDictionary();
}
}
}

View File

@ -17,7 +17,9 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Emby.Dlna
{
@ -203,16 +205,13 @@ namespace Emby.Dlna
}
}
public DeviceProfile GetProfile(IDictionary<string, string> headers)
public DeviceProfile GetProfile(IHeaderDictionary headers)
{
if (headers == null)
{
throw new ArgumentNullException(nameof(headers));
}
// Convert to case insensitive
headers = new Dictionary<string, string>(headers, StringComparer.OrdinalIgnoreCase);
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
if (profile != null)
@ -228,12 +227,12 @@ namespace Emby.Dlna
return profile;
}
private bool IsMatch(IDictionary<string, string> headers, DeviceIdentification profileInfo)
private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo)
{
return profileInfo.Headers.Any(i => IsMatch(headers, i));
}
private bool IsMatch(IDictionary<string, string> headers, HttpHeaderInfo header)
private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header)
{
// Handle invalid user setup
if (string.IsNullOrEmpty(header.Name))
@ -241,14 +240,14 @@ namespace Emby.Dlna
return false;
}
if (headers.TryGetValue(header.Name, out string value))
if (headers.TryGetValue(header.Name, out StringValues value))
{
switch (header.Match)
{
case HeaderMatchType.Equals:
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring:
var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
//_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
return isMatch;
case HeaderMatchType.Regex:
@ -494,7 +493,7 @@ namespace Emby.Dlna
internal string Path { get; set; }
}
public string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId, string serverAddress)
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{
var profile = GetProfile(headers) ??
GetDefaultProfile();

View File

@ -58,4 +58,9 @@
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,3 @@
using System.Collections.Generic;
namespace Emby.Dlna
{
public interface IUpnpService
@ -7,9 +5,8 @@ namespace Emby.Dlna
/// <summary>
/// Gets the content directory XML.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>System.String.</returns>
string GetServiceXml(IDictionary<string, string> headers);
string GetServiceXml();
/// <summary>
/// Processes the control request.

View File

@ -1,4 +1,3 @@
using System.Collections.Generic;
using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@ -19,7 +18,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
}
public string GetServiceXml(IDictionary<string, string> headers)
public string GetServiceXml()
{
return new MediaReceiverRegistrarXmlBuilder().GetXml();
}

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna.Didl;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
@ -17,8 +18,8 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo
@ -847,13 +848,13 @@ namespace Emby.Dlna.PlayTo
if (index == -1) return request;
var query = url.Substring(index + 1);
QueryParamCollection values = MyHttpUtility.ParseQueryString(query);
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
request.DeviceProfileId = values.Get("DeviceProfileId");
request.DeviceId = values.Get("DeviceId");
request.MediaSourceId = values.Get("MediaSourceId");
request.LiveStreamId = values.Get("LiveStreamId");
request.IsDirectStream = string.Equals("true", values.Get("Static"), StringComparison.OrdinalIgnoreCase);
request.DeviceProfileId = values.GetValueOrDefault("DeviceProfileId");
request.DeviceId = values.GetValueOrDefault("DeviceId");
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
@ -867,9 +868,9 @@ namespace Emby.Dlna.PlayTo
}
}
private static int? GetIntValue(QueryParamCollection values, string name)
private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
{
var value = values.Get(name);
var value = values.GetValueOrDefault(name);
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
@ -879,9 +880,9 @@ namespace Emby.Dlna.PlayTo
return null;
}
private static long GetLongValue(QueryParamCollection values, string name)
private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
{
var value = values.Get(name);
var value = values.GetValueOrDefault(name);
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{

View File

@ -34,6 +34,7 @@ using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Library;
using Emby.Server.Implementations.LiveTv;
using Emby.Server.Implementations.Localization;
using Emby.Server.Implementations.Middleware;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Reflection;
@ -41,6 +42,7 @@ using Emby.Server.Implementations.ScheduledTasks;
using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.SocketSharp;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
using Emby.Server.Implementations.Xml;
@ -104,11 +106,15 @@ using MediaBrowser.Providers.Subtitles;
using MediaBrowser.Providers.TV.TheTVDB;
using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using ServiceStack;
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
namespace Emby.Server.Implementations
{
@ -611,6 +617,68 @@ namespace Emby.Server.Implementations
await RegisterResources(serviceCollection);
FindParts();
string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
if (string.IsNullOrEmpty(contentRoot))
{
contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src");
}
var host = new WebHostBuilder()
.UseKestrel(options =>
{
options.ListenAnyIP(HttpPort);
if (EnableHttps)
{
options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); });
}
})
.UseContentRoot(contentRoot)
.ConfigureServices(services =>
{
services.AddResponseCompression();
services.AddHttpContextAccessor();
})
.Configure(app =>
{
app.UseWebSockets();
app.UseResponseCompression();
// TODO app.UseMiddleware<WebSocketMiddleware>();
app.Use(ExecuteWebsocketHandlerAsync);
app.Use(ExecuteHttpHandlerAsync);
})
.Build();
await host.StartAsync();
}
private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await next().ConfigureAwait(false);
return;
}
await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
}
private async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
{
if (context.WebSockets.IsWebSocketRequest)
{
await next().ConfigureAwait(false);
return;
}
var request = context.Request;
var response = context.Response;
var localPath = context.Request.Path.ToString();
var req = new WebSocketSharpRequest(request, response, request.Path, Logger);
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false);
}
protected virtual IHttpClient CreateHttpClient()
@ -673,7 +741,7 @@ namespace Emby.Server.Implementations
ZipClient = new ZipClient();
serviceCollection.AddSingleton(ZipClient);
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper);
serviceCollection.AddSingleton(HttpResultFactory);
serviceCollection.AddSingleton<IServerApplicationHost>(this);
@ -732,7 +800,8 @@ namespace Emby.Server.Implementations
_configuration,
NetworkManager,
JsonSerializer,
XmlSerializer);
XmlSerializer,
CreateHttpListener());
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
serviceCollection.AddSingleton(HttpServer);
@ -836,11 +905,6 @@ namespace Emby.Server.Implementations
_serviceProvider = serviceCollection.BuildServiceProvider();
}
protected virtual IBrotliCompressor CreateBrotliCompressor()
{
return null;
}
public virtual string PackageRuntime => "netcore";
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo)
@ -873,11 +937,9 @@ namespace Emby.Server.Implementations
}
}
protected virtual bool SupportsDualModeSockets => true;
private X509Certificate GetCertificate(CertificateInfo info)
private X509Certificate2 GetCertificate(CertificateInfo info)
{
var certificateLocation = info == null ? null : info.Path;
var certificateLocation = info?.Path;
if (string.IsNullOrWhiteSpace(certificateLocation))
{
@ -952,7 +1014,7 @@ namespace Emby.Server.Implementations
/// </summary>
private void SetStaticProperties()
{
((SqliteItemRepository)ItemRepository).ImageProcessor = ImageProcessor;
ItemRepository.ImageProcessor = ImageProcessor;
// For now there's no real way to inject these properly
BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem");
@ -996,8 +1058,6 @@ namespace Emby.Server.Implementations
HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>());
StartServer();
LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(),
GetExports<IItemResolver>(),
GetExports<IIntroProvider>(),
@ -1080,15 +1140,12 @@ namespace Emby.Server.Implementations
AllConcreteTypes = GetComposablePartAssemblies()
.SelectMany(x => x.ExportedTypes)
.Where(type =>
{
return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType;
})
.Where(type => type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType)
.ToArray();
}
private CertificateInfo CertificateInfo { get; set; }
protected X509Certificate Certificate { get; private set; }
protected X509Certificate2 Certificate { get; private set; }
private IEnumerable<string> GetUrlPrefixes()
{
@ -1110,45 +1167,7 @@ namespace Emby.Server.Implementations
});
}
protected abstract IHttpListener CreateHttpListener();
/// <summary>
/// Starts the server.
/// </summary>
private void StartServer()
{
try
{
((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener());
return;
}
catch (Exception ex)
{
var msg = string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase)
? "The http server is unable to start due to a Socket error. This can occasionally happen when the operating system takes longer than usual to release the IP bindings from the previous session. This can take up to five minutes. Please try waiting or rebooting the system."
: "Error starting Http Server";
Logger.LogError(ex, msg);
if (HttpPort == ServerConfiguration.DefaultHttpPort)
{
throw;
}
}
HttpPort = ServerConfiguration.DefaultHttpPort;
try
{
((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener());
}
catch (Exception ex)
{
Logger.LogError(ex, "Error starting http server");
throw;
}
}
protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(Logger);
private CertificateInfo GetCertificateInfo(bool generateCertificate)
{
@ -1585,7 +1604,7 @@ namespace Emby.Server.Implementations
LogErrorResponseBody = false,
LogErrors = logPing,
LogRequest = logPing,
TimeoutMs = 30000,
TimeoutMs = 5000,
BufferContent = false,
CancellationToken = cancellationToken

View File

@ -9,7 +9,6 @@
<ProjectReference Include="..\MediaBrowser.Providers\MediaBrowser.Providers.csproj" />
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
<ProjectReference Include="..\SocketHttpListener\SocketHttpListener.csproj" />
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
@ -22,6 +21,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />

View File

@ -15,6 +15,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpClientManager
{
@ -179,11 +180,11 @@ namespace Emby.Server.Implementations.HttpClientManager
foreach (var header in options.RequestHeaders)
{
if (string.Equals(header.Key, "Accept", StringComparison.OrdinalIgnoreCase))
if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase))
{
request.Accept = header.Value;
}
else if (string.Equals(header.Key, "User-Agent", StringComparison.OrdinalIgnoreCase))
else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase))
{
SetUserAgent(request, header.Value);
hasUserAgent = true;
@ -327,7 +328,6 @@ namespace Emby.Server.Implementations.HttpClientManager
}
httpWebRequest.ContentType = contentType;
httpWebRequest.ContentLength = bytes.Length;
(await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length);
}
catch (Exception ex)

View File

@ -5,15 +5,19 @@ using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
public class FileWriter : IHttpResult
{
private readonly IStreamHelper _streamHelper;
private ILogger Logger { get; set; }
private readonly IFileSystem _fileSystem;
private string RangeHeader { get; set; }
private bool IsHeadRequest { get; set; }
@ -42,25 +46,27 @@ namespace Emby.Server.Implementations.HttpServer
public string Path { get; set; }
public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem)
public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem, IStreamHelper streamHelper)
{
if (string.IsNullOrEmpty(contentType))
{
throw new ArgumentNullException(nameof(contentType));
}
_streamHelper = streamHelper;
_fileSystem = fileSystem;
Path = path;
Logger = logger;
RangeHeader = rangeHeader;
Headers["Content-Type"] = contentType;
Headers[HeaderNames.ContentType] = contentType;
TotalContentLength = fileSystem.GetFileInfo(path).Length;
Headers["Accept-Ranges"] = "bytes";
Headers[HeaderNames.AcceptRanges] = "bytes";
if (string.IsNullOrWhiteSpace(rangeHeader))
{
Headers["Content-Length"] = TotalContentLength.ToString(UsCulture);
StatusCode = HttpStatusCode.OK;
}
else
@ -93,13 +99,10 @@ namespace Emby.Server.Implementations.HttpServer
RangeStart = requestedRange.Key;
RangeLength = 1 + RangeEnd - RangeStart;
// Content-Length is the length of what we're serving, not the original content
var lengthString = RangeLength.ToString(UsCulture);
Headers["Content-Length"] = lengthString;
var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
Headers["Content-Range"] = rangeString;
var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
Headers[HeaderNames.ContentRange] = rangeString;
Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString);
Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Range: {2}", Path, RangeHeader, rangeString);
}
/// <summary>
@ -145,8 +148,7 @@ namespace Emby.Server.Implementations.HttpServer
}
}
private string[] SkipLogExtensions = new string[]
{
private static readonly string[] SkipLogExtensions = {
".js",
".html",
".css"
@ -163,8 +165,10 @@ namespace Emby.Server.Implementations.HttpServer
}
var path = Path;
var offset = RangeStart;
var count = RangeLength;
if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
if (string.IsNullOrWhiteSpace(RangeHeader) || RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)
{
var extension = System.IO.Path.GetExtension(path);
@ -173,20 +177,15 @@ namespace Emby.Server.Implementations.HttpServer
Logger.LogDebug("Transmit file {0}", path);
}
//var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0;
await response.TransmitFile(path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
return;
offset = 0;
count = 0;
}
await response.TransmitFile(path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false);
await response.TransmitFile(path, offset, count, FileShare, _fileSystem, _streamHelper, cancellationToken).ConfigureAwait(false);
}
finally
{
if (OnComplete != null)
{
OnComplete();
}
OnComplete?.Invoke();
}
}
@ -203,8 +202,5 @@ namespace Emby.Server.Implementations.HttpServer
get => (HttpStatusCode)Status;
set => Status = (int)value;
}
public string StatusDescription { get; set; }
}
}

View File

@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Services;
using Emby.Server.Implementations.SocketSharp;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
@ -20,6 +21,9 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using ServiceStack.Text.Jsv;
@ -29,12 +33,8 @@ namespace Emby.Server.Implementations.HttpServer
public class HttpListenerHost : IHttpServer, IDisposable
{
private string DefaultRedirectPath { get; set; }
private readonly ILogger _logger;
public string[] UrlPrefixes { get; private set; }
private IHttpListener _listener;
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
private readonly IServerConfigurationManager _config;
@ -42,6 +42,7 @@ namespace Emby.Server.Implementations.HttpServer
private readonly IServerApplicationHost _appHost;
private readonly IJsonSerializer _jsonSerializer;
private readonly IXmlSerializer _xmlSerializer;
private readonly IHttpListener _socketListener;
private readonly Func<Type, Func<string, object>> _funcParseFn;
public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; }
@ -59,15 +60,18 @@ namespace Emby.Server.Implementations.HttpServer
IConfiguration configuration,
INetworkManager networkManager,
IJsonSerializer jsonSerializer,
IXmlSerializer xmlSerializer)
IXmlSerializer xmlSerializer,
IHttpListener socketListener)
{
_appHost = applicationHost;
_logger = loggerFactory.CreateLogger("HttpServer");
Logger = loggerFactory.CreateLogger("HttpServer");
_config = config;
DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
_networkManager = networkManager;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
_socketListener = socketListener;
_socketListener.WebSocketConnected = OnWebSocketConnected;
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
@ -77,7 +81,7 @@ namespace Emby.Server.Implementations.HttpServer
public string GlobalResponse { get; set; }
protected ILogger Logger => _logger;
protected ILogger Logger { get; }
public object CreateInstance(Type type)
{
@ -143,11 +147,11 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, Logger)
{
OnReceive = ProcessWebSocketMessageReceived,
Url = e.Url,
QueryString = e.QueryString ?? new QueryParamCollection()
QueryString = e.QueryString ?? new QueryCollection()
};
connection.Closed += Connection_Closed;
@ -212,11 +216,11 @@ namespace Emby.Server.Implementations.HttpServer
if (logExceptionStackTrace)
{
_logger.LogError(ex, "Error processing request");
Logger.LogError(ex, "Error processing request");
}
else if (logExceptionMessage)
{
_logger.LogError(ex.Message);
Logger.LogError(ex.Message);
}
var httpRes = httpReq.Response;
@ -234,7 +238,7 @@ namespace Emby.Server.Implementations.HttpServer
}
catch (Exception errorEx)
{
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
Logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
}
}
@ -277,14 +281,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
if (_listener != null)
{
_logger.LogInformation("Stopping HttpListener...");
var task = _listener.Stop();
Task.WaitAll(task);
_logger.LogInformation("HttpListener stopped");
}
}
public static string RemoveQueryStringByKey(string url, string key)
@ -292,7 +288,7 @@ namespace Emby.Server.Implementations.HttpServer
var uri = new Uri(url);
// this gets all the query string key value pairs as a collection
var newQueryString = MyHttpUtility.ParseQueryString(uri.Query);
var newQueryString = QueryHelpers.ParseQuery(uri.Query);
var originalCount = newQueryString.Count;
@ -313,7 +309,7 @@ namespace Emby.Server.Implementations.HttpServer
string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0];
return newQueryString.Count > 0
? string.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString)
? QueryHelpers.AddQueryString(pagePathWithoutQueryString, newQueryString.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()))
: pagePathWithoutQueryString;
}
@ -422,7 +418,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Overridable method that can be used to implement a custom hnandler
/// </summary>
protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
@ -599,17 +595,17 @@ namespace Emby.Server.Implementations.HttpServer
}
finally
{
httpRes.Close();
// TODO response closes automatically after the handler is done, but some functions rely on knowing if it's closed or not
httpRes.IsClosed = true;
stopWatch.Stop();
var elapsed = stopWatch.Elapsed;
if (elapsed.TotalMilliseconds > 500)
{
_logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
Logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
}
else
{
_logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
Logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
}
}
}
@ -622,7 +618,7 @@ namespace Emby.Server.Implementations.HttpServer
var pathParts = pathInfo.TrimStart('/').Split('/');
if (pathParts.Length == 0)
{
_logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl);
Logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl);
return null;
}
@ -636,15 +632,13 @@ namespace Emby.Server.Implementations.HttpServer
};
}
_logger.LogError("Could not find handler for {PathInfo}", pathInfo);
Logger.LogError("Could not find handler for {PathInfo}", pathInfo);
return null;
}
private static Task Write(IResponse response, string text)
{
var bOutput = Encoding.UTF8.GetBytes(text);
response.SetContentLength(bOutput.Length);
return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length);
}
@ -663,6 +657,7 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
// TODO what is this?
var httpsUrl = url
.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
@ -689,7 +684,7 @@ namespace Emby.Server.Implementations.HttpServer
ServiceController = new ServiceController();
_logger.LogInformation("Calling ServiceStack AppHost.Init");
Logger.LogInformation("Calling ServiceStack AppHost.Init");
var types = services.Select(r => r.GetType()).ToArray();
@ -697,7 +692,7 @@ namespace Emby.Server.Implementations.HttpServer
ResponseFilters = new Action<IRequest, IResponse, object>[]
{
new ResponseFilter(_logger).FilterResponse
new ResponseFilter(Logger).FilterResponse
};
}
@ -759,8 +754,12 @@ namespace Emby.Server.Implementations.HttpServer
return _jsonSerializer.DeserializeFromStreamAsync(stream, type);
}
//TODO Add Jellyfin Route Path Normalizer
public Task ProcessWebSocketRequest(HttpContext context)
{
return _socketListener.ProcessWebSocketRequest(context);
}
//TODO Add Jellyfin Route Path Normalizer
private static string NormalizeEmbyRoutePath(string path)
{
if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
@ -793,6 +792,7 @@ namespace Emby.Server.Implementations.HttpServer
private bool _disposed;
private readonly object _disposeLock = new object();
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
@ -821,7 +821,7 @@ namespace Emby.Server.Implementations.HttpServer
return Task.CompletedTask;
}
_logger.LogDebug("Websocket message received: {0}", result.MessageType);
Logger.LogDebug("Websocket message received: {0}", result.MessageType);
var tasks = _webSocketListeners.Select(i => Task.Run(async () =>
{
@ -831,7 +831,7 @@ namespace Emby.Server.Implementations.HttpServer
}
catch (Exception ex)
{
_logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty);
Logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty);
}
}));
@ -842,18 +842,5 @@ namespace Emby.Server.Implementations.HttpServer
{
Dispose(true);
}
public void StartServer(string[] urlPrefixes, IHttpListener httpListener)
{
UrlPrefixes = urlPrefixes;
_listener = httpListener;
_listener.WebSocketConnected = OnWebSocketConnected;
_listener.ErrorHandler = ErrorHandler;
_listener.RequestHandler = RequestHandler;
_listener.Start(UrlPrefixes);
}
}
}

View File

@ -16,6 +16,8 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using IRequest = MediaBrowser.Model.Services.IRequest;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
@ -32,17 +34,16 @@ namespace Emby.Server.Implementations.HttpServer
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private IBrotliCompressor _brotliCompressor;
private readonly IStreamHelper _streamHelper;
/// <summary>
/// Initializes a new instance of the <see cref="HttpResultFactory" /> class.
/// </summary>
public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IBrotliCompressor brotliCompressor)
public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IStreamHelper streamHelper)
{
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
_brotliCompressor = brotliCompressor;
_streamHelper = streamHelper;
_logger = loggerfactory.CreateLogger("HttpResultFactory");
}
@ -76,7 +77,7 @@ namespace Emby.Server.Implementations.HttpServer
public object GetRedirectResult(string url)
{
var responseHeaders = new Dictionary<string, string>();
responseHeaders["Location"] = url;
responseHeaders[HeaderNames.Location] = url;
var result = new HttpResult(Array.Empty<byte>(), "text/plain", HttpStatusCode.Redirect);
@ -97,9 +98,9 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string expires))
{
responseHeaders["Expires"] = "-1";
responseHeaders[HeaderNames.Expires] = "-1";
}
AddResponseHeaders(result, responseHeaders);
@ -131,7 +132,7 @@ namespace Emby.Server.Implementations.HttpServer
content = Array.Empty<byte>();
}
result = new StreamWriter(content, contentType, contentLength);
result = new StreamWriter(content, contentType);
}
else
{
@ -143,9 +144,9 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _))
{
responseHeaders["Expires"] = "-1";
responseHeaders[HeaderNames.Expires] = "-1";
}
AddResponseHeaders(result, responseHeaders);
@ -175,7 +176,7 @@ namespace Emby.Server.Implementations.HttpServer
bytes = Array.Empty<byte>();
}
result = new StreamWriter(bytes, contentType, contentLength);
result = new StreamWriter(bytes, contentType);
}
else
{
@ -187,9 +188,9 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _))
{
responseHeaders["Expires"] = "-1";
responseHeaders[HeaderNames.Expires] = "-1";
}
AddResponseHeaders(result, responseHeaders);
@ -214,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
responseHeaders["Expires"] = "-1";
responseHeaders[HeaderNames.Expires] = "-1";
return ToOptimizedResultInternal(requestContext, result, responseHeaders);
}
@ -246,9 +247,9 @@ namespace Emby.Server.Implementations.HttpServer
private static string GetCompressionType(IRequest request)
{
var acceptEncoding = request.Headers["Accept-Encoding"];
var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString();
if (acceptEncoding != null)
if (string.IsNullOrEmpty(acceptEncoding))
{
//if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
// return "br";
@ -326,21 +327,21 @@ namespace Emby.Server.Implementations.HttpServer
}
content = Compress(content, requestedCompressionType);
responseHeaders["Content-Encoding"] = requestedCompressionType;
responseHeaders[HeaderNames.ContentEncoding] = requestedCompressionType;
responseHeaders["Vary"] = "Accept-Encoding";
responseHeaders[HeaderNames.Vary] = HeaderNames.AcceptEncoding;
var contentLength = content.Length;
if (isHeadRequest)
{
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength);
var result = new StreamWriter(Array.Empty<byte>(), contentType);
AddResponseHeaders(result, responseHeaders);
return result;
}
else
{
var result = new StreamWriter(content, contentType, contentLength);
var result = new StreamWriter(content, contentType);
AddResponseHeaders(result, responseHeaders);
return result;
}
@ -348,11 +349,6 @@ namespace Emby.Server.Implementations.HttpServer
private byte[] Compress(byte[] bytes, string compressionType)
{
if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase))
{
return CompressBrotli(bytes);
}
if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase))
{
return Deflate(bytes);
@ -366,11 +362,6 @@ namespace Emby.Server.Implementations.HttpServer
throw new NotSupportedException(compressionType);
}
private byte[] CompressBrotli(byte[] bytes)
{
return _brotliCompressor.Compress(bytes);
}
private static byte[] Deflate(byte[] bytes)
{
// In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream
@ -424,12 +415,12 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
{
bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
if (!noCache)
{
DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader);
DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
{
@ -530,7 +521,7 @@ namespace Emby.Server.Implementations.HttpServer
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var contentType = options.ContentType;
if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since")))
if (!StringValues.IsNullOrEmpty(requestContext.Headers[HeaderNames.IfModifiedSince]))
{
// See if the result is already cached in the browser
var result = GetCachedResult(requestContext, options.ResponseHeaders, options);
@ -548,11 +539,11 @@ namespace Emby.Server.Implementations.HttpServer
AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified);
AddAgeHeader(responseHeaders, options.DateLastModified);
var rangeHeader = requestContext.Headers.Get("Range");
var rangeHeader = requestContext.Headers[HeaderNames.Range];
if (!isHeadRequest && !string.IsNullOrEmpty(options.Path))
{
var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem, _streamHelper)
{
OnComplete = options.OnComplete,
OnError = options.OnError,
@ -590,11 +581,6 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
if (totalContentLength.HasValue)
{
responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture);
}
if (isHeadRequest)
{
using (stream)
@ -614,11 +600,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
/// <summary>
/// The us culture
/// </summary>
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary>
/// Adds the caching responseHeaders.
/// </summary>
@ -627,23 +608,23 @@ namespace Emby.Server.Implementations.HttpServer
{
if (noCache)
{
responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate";
responseHeaders["pragma"] = "no-cache, no-store, must-revalidate";
responseHeaders[HeaderNames.CacheControl] = "no-cache, no-store, must-revalidate";
responseHeaders[HeaderNames.Pragma] = "no-cache, no-store, must-revalidate";
return;
}
if (cacheDuration.HasValue)
{
responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds;
responseHeaders[HeaderNames.CacheControl] = "public, max-age=" + cacheDuration.Value.TotalSeconds;
}
else
{
responseHeaders["Cache-Control"] = "public";
responseHeaders[HeaderNames.CacheControl] = "public";
}
if (lastModifiedDate.HasValue)
{
responseHeaders["Last-Modified"] = lastModifiedDate.ToString();
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.ToString();
}
}
@ -656,7 +637,7 @@ namespace Emby.Server.Implementations.HttpServer
{
if (lastDateModified.HasValue)
{
responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
responseHeaders[HeaderNames.Age] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
}
}
@ -714,9 +695,4 @@ namespace Emby.Server.Implementations.HttpServer
}
}
}
public interface IBrotliCompressor
{
byte[] Compress(byte[] content);
}
}

View File

@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Net;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
namespace Emby.Server.Implementations.HttpServer
{
@ -28,21 +27,11 @@ namespace Emby.Server.Implementations.HttpServer
/// <value>The web socket handler.</value>
Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
/// <summary>
/// Gets or sets the web socket connecting.
/// </summary>
/// <value>The web socket connecting.</value>
Action<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; }
/// <summary>
/// Starts this instance.
/// </summary>
/// <param name="urlPrefixes">The URL prefixes.</param>
void Start(IEnumerable<string> urlPrefixes);
/// <summary>
/// Stops this instance.
/// </summary>
Task Stop();
Task ProcessWebSocketRequest(HttpContext ctx);
}
}

View File

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
@ -66,8 +67,8 @@ namespace Emby.Server.Implementations.HttpServer
this._logger = logger;
ContentType = contentType;
Headers["Content-Type"] = contentType;
Headers["Accept-Ranges"] = "bytes";
Headers[HeaderNames.ContentType] = contentType;
Headers[HeaderNames.AcceptRanges] = "bytes";
StatusCode = HttpStatusCode.PartialContent;
SetRangeValues(contentLength);
@ -95,9 +96,7 @@ namespace Emby.Server.Implementations.HttpServer
RangeStart = requestedRange.Key;
RangeLength = 1 + RangeEnd - RangeStart;
// Content-Length is the length of what we're serving, not the original content
Headers["Content-Length"] = RangeLength.ToString(UsCulture);
Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
if (RangeStart > 0 && SourceStream.CanSeek)
{

View File

@ -3,6 +3,7 @@ using System.Globalization;
using System.Text;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.HttpServer
public void FilterResponse(IRequest req, IResponse res, object dto)
{
// Try to prevent compatibility view
res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
res.AddHeader("Access-Control-Allow-Origin", "*");
@ -44,20 +45,19 @@ namespace Emby.Server.Implementations.HttpServer
if (dto is IHasHeaders hasHeaders)
{
if (!hasHeaders.Headers.ContainsKey("Server"))
if (!hasHeaders.Headers.ContainsKey(HeaderNames.Server))
{
hasHeaders.Headers["Server"] = "Microsoft-NetCore/2.0, UPnP/1.0 DLNADOC/1.50";
hasHeaders.Headers[HeaderNames.Server] = "Microsoft-NetCore/2.0, UPnP/1.0 DLNADOC/1.50";
}
// Content length has to be explicitly set on on HttpListenerResponse or it won't be happy
if (hasHeaders.Headers.TryGetValue("Content-Length", out string contentLength)
if (hasHeaders.Headers.TryGetValue(HeaderNames.ContentLength, out string contentLength)
&& !string.IsNullOrEmpty(contentLength))
{
var length = long.Parse(contentLength, UsCulture);
if (length > 0)
{
res.SetContentLength(length);
res.SendChunked = false;
}
}

View File

@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Services;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer.Security
{
@ -176,7 +177,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
if (string.IsNullOrEmpty(auth))
{
auth = httpReq.Headers["Authorization"];
auth = httpReq.Headers[HeaderNames.Authorization];
}
return GetAuthorization(auth);

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
@ -52,12 +53,7 @@ namespace Emby.Server.Implementations.HttpServer
SourceStream = source;
Headers["Content-Type"] = contentType;
if (source.CanSeek)
{
Headers["Content-Length"] = source.Length.ToString(UsCulture);
}
Headers[HeaderNames.ContentType] = contentType;
}
/// <summary>
@ -65,8 +61,7 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="logger">The logger.</param>
public StreamWriter(byte[] source, string contentType, int contentLength)
public StreamWriter(byte[] source, string contentType)
{
if (string.IsNullOrEmpty(contentType))
{
@ -75,9 +70,7 @@ namespace Emby.Server.Implementations.HttpServer
SourceBytes = source;
Headers["Content-Type"] = contentType;
Headers["Content-Length"] = contentLength.ToString(UsCulture);
Headers[HeaderNames.ContentType] = contentType;
}
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)

View File

@ -8,6 +8,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using UtfUnknown;
@ -67,7 +68,7 @@ namespace Emby.Server.Implementations.HttpServer
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
public QueryParamCollection QueryString { get; set; }
public IQueryCollection QueryString { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
@ -101,12 +102,6 @@ namespace Emby.Server.Implementations.HttpServer
_socket = socket;
_socket.OnReceiveBytes = OnReceiveInternal;
var memorySocket = socket as IMemoryWebSocket;
if (memorySocket != null)
{
memorySocket.OnReceiveMemoryBytes = OnReceiveInternal;
}
RemoteEndPoint = remoteEndPoint;
_logger = logger;
@ -142,34 +137,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
/// <summary>
/// Called when [receive].
/// </summary>
/// <param name="memory">The memory block.</param>
/// <param name="length">The length of the memory block.</param>
private void OnReceiveInternal(Memory<byte> memory, int length)
{
LastActivityDate = DateTime.UtcNow;
if (OnReceive == null)
{
return;
}
var bytes = memory.Slice(0, length).ToArray();
var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase))
{
OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length));
}
else
{
OnReceiveInternal(Encoding.ASCII.GetString(bytes, 0, bytes.Length));
}
}
private void OnReceiveInternal(string message)
{
LastActivityDate = DateTime.UtcNow;
@ -193,7 +160,7 @@ namespace Emby.Server.Implementations.HttpServer
var info = new WebSocketMessageInfo
{
MessageType = stub.MessageType,
Data = stub.Data == null ? null : stub.Data.ToString(),
Data = stub.Data?.ToString(),
Connection = this
};

View File

@ -17,6 +17,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.LiveTv.Listings
{
@ -638,7 +639,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
#if NETSTANDARD2_0
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
options.RequestHeaders["Accept-Encoding"] = "deflate";
options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate";
}
#endif
@ -676,7 +677,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
#if NETSTANDARD2_0
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
options.RequestHeaders["Accept-Encoding"] = "deflate";
options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate";
}
#endif

View File

@ -19,6 +19,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (protocol == MediaProtocol.Http)
{
// Use user-defined user-agent. If there isn't one, make it look like a browser.
httpHeaders["User-Agent"] = string.IsNullOrWhiteSpace(info.UserAgent) ?
httpHeaders[HeaderNames.UserAgent] = string.IsNullOrWhiteSpace(info.UserAgent) ?
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.85 Safari/537.36" :
info.UserAgent;
}

View File

@ -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);
}
}
}
}

View File

@ -45,9 +45,4 @@ namespace Emby.Server.Implementations.Net
/// <returns>Task.</returns>
Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken);
}
public interface IMemoryWebSocket
{
Action<Memory<byte>, int> OnReceiveMemoryBytes { get; set; }
}
}

View File

@ -1,5 +1,7 @@
using System;
using System.Net.WebSockets;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
namespace Emby.Server.Implementations.Net
{
@ -14,7 +16,7 @@ namespace Emby.Server.Implementations.Net
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
public QueryParamCollection QueryString { get; set; }
public IQueryCollection QueryString { get; set; }
/// <summary>
/// Gets or sets the web socket.
/// </summary>

View File

@ -43,14 +43,9 @@ namespace Emby.Server.Implementations.Services
{
var contentLength = bytesResponse.Length;
if (response != null)
{
response.SetContentLength(contentLength);
}
if (contentLength > 0)
{
await responseStream.WriteAsync(bytesResponse, 0, contentLength).ConfigureAwait(false);
await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false);
}
return;
}

View File

@ -20,8 +20,6 @@ namespace Emby.Server.Implementations.Services
{
response.StatusCode = (int)HttpStatusCode.NoContent;
}
response.SetContentLength(0);
return Task.CompletedTask;
}
@ -55,7 +53,6 @@ namespace Emby.Server.Implementations.Services
{
if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase))
{
response.SetContentLength(long.Parse(responseHeaders.Value));
continue;
}
@ -104,7 +101,6 @@ namespace Emby.Server.Implementations.Services
if (bytes != null)
{
response.ContentType = "application/octet-stream";
response.SetContentLength(bytes.Length);
if (bytes.Length > 0)
{
@ -117,7 +113,6 @@ namespace Emby.Server.Implementations.Services
if (responseText != null)
{
bytes = Encoding.UTF8.GetBytes(responseText);
response.SetContentLength(bytes.Length);
if (bytes.Length > 0)
{
return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
@ -149,8 +144,6 @@ namespace Emby.Server.Implementations.Services
var contentLength = ms.Length;
response.SetContentLength(contentLength);
if (contentLength > 0)
{
await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false);

View File

@ -154,7 +154,7 @@ namespace Emby.Server.Implementations.Services
{
if (name == null) continue; //thank you ASP.NET
var values = request.QueryString.GetValues(name);
var values = request.QueryString[name];
if (values.Count == 1)
{
map[name] = values[0];

View File

@ -5,6 +5,7 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Session
}
}
private SessionInfo GetSession(QueryParamCollection queryString, string remoteEndpoint)
private SessionInfo GetSession(IQueryCollection queryString, string remoteEndpoint)
{
if (queryString == null)
{

View File

@ -1,7 +1,7 @@
using System.IO;
using MediaBrowser.Model.Services;
namespace Jellyfin.Server.SocketSharp
namespace Emby.Server.Implementations.SocketSharp
{
public class HttpFile : IHttpFile
{

View File

@ -6,14 +6,16 @@ using System.Net;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Jellyfin.Server.SocketSharp
namespace Emby.Server.Implementations.SocketSharp
{
public partial class WebSocketSharpRequest : IHttpRequest
{
internal static string GetParameter(ReadOnlySpan<char> header, string attr)
{
int ap = header.IndexOf(attr, StringComparison.Ordinal);
int ap = header.IndexOf(attr.AsSpan(), StringComparison.Ordinal);
if (ap == -1)
{
return null;
@ -43,7 +45,7 @@ namespace Jellyfin.Server.SocketSharp
private async Task LoadMultiPart(WebROCollection form)
{
string boundary = GetParameter(ContentType, "; boundary=");
string boundary = GetParameter(ContentType.AsSpan(), "; boundary=");
if (boundary == null)
{
return;
@ -105,14 +107,6 @@ namespace Jellyfin.Server.SocketSharp
await LoadWwwForm(form).ConfigureAwait(false);
}
#if NET_4_0
if (validateRequestNewMode && !checked_form) {
// Setting this before calling the validator prevents
// possible endless recursion
checked_form = true;
ValidateNameValueCollection("Form", query_string_nvc, RequestValidationSource.Form);
} else
#endif
if (validate_form && !checked_form)
{
checked_form = true;
@ -122,15 +116,11 @@ namespace Jellyfin.Server.SocketSharp
return form;
}
public string Accept => string.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"];
public string Accept => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Accept]) ? null : request.Headers[HeaderNames.Accept].ToString();
public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
public string Authorization => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Authorization]) ? null : request.Headers[HeaderNames.Authorization].ToString();
protected bool validate_cookies { get; set; }
protected bool validate_query_string { get; set; }
protected bool validate_form { get; set; }
protected bool checked_cookies { get; set; }
protected bool checked_query_string { get; set; }
protected bool checked_form { get; set; }
private static void ThrowValidationException(string name, string key, string value)
@ -210,13 +200,6 @@ namespace Jellyfin.Server.SocketSharp
return false;
}
public void ValidateInput()
{
validate_cookies = true;
validate_query_string = true;
validate_form = true;
}
private bool IsContentType(string ct, bool starts_with)
{
if (ct == null || ContentType == null)
@ -396,14 +379,14 @@ namespace Jellyfin.Server.SocketSharp
var elem = new Element();
ReadOnlySpan<char> header;
while ((header = ReadHeaders()) != null)
while ((header = ReadHeaders().AsSpan()) != null)
{
if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase))
if (header.StartsWith("Content-Disposition:".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
elem.Name = GetContentDispositionAttribute(header, "name");
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
}
else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase))
else if (header.StartsWith("Content-Type:".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
elem.ContentType = header.Slice("Content-Type:".Length).Trim().ToString();
elem.Encoding = GetEncoding(elem.ContentType);
@ -455,7 +438,7 @@ namespace Jellyfin.Server.SocketSharp
private static string GetContentDispositionAttribute(ReadOnlySpan<char> l, string name)
{
int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal);
int idx = l.IndexOf((name + "=\"").AsSpan(), StringComparison.Ordinal);
if (idx < 0)
{
return null;
@ -478,7 +461,7 @@ namespace Jellyfin.Server.SocketSharp
private string GetContentDispositionAttributeWithEncoding(ReadOnlySpan<char> l, string name)
{
int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal);
int idx = l.IndexOf((name + "=\"").AsSpan(), StringComparison.Ordinal);
if (idx < 0)
{
return null;

View File

@ -1,11 +1,12 @@
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Net;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.SocketSharp
namespace Emby.Server.Implementations.SocketSharp
{
public class SharpWebSocket : IWebSocket
{
@ -20,67 +21,22 @@ namespace Jellyfin.Server.SocketSharp
/// Gets or sets the web socket.
/// </summary>
/// <value>The web socket.</value>
private SocketHttpListener.WebSocket WebSocket { get; set; }
private readonly WebSocket _webSocket;
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed = false;
private bool _disposed;
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
public SharpWebSocket(WebSocket socket, ILogger logger)
{
if (socket == null)
{
throw new ArgumentNullException(nameof(socket));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger;
WebSocket = socket;
socket.OnMessage += OnSocketMessage;
socket.OnClose += OnSocketClose;
socket.OnError += OnSocketError;
}
public Task ConnectAsServerAsync()
=> WebSocket.ConnectAsServer();
public Task StartReceive()
{
return _taskCompletionSource.Task;
}
private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e)
{
_logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty);
// Closed?.Invoke(this, EventArgs.Empty);
}
private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e)
{
_taskCompletionSource.TrySetResult(true);
Closed?.Invoke(this, EventArgs.Empty);
}
private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e)
{
if (OnReceiveBytes != null)
{
OnReceiveBytes(e.RawData);
}
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_webSocket = socket ?? throw new ArgumentNullException(nameof(socket));
}
/// <summary>
/// Gets or sets the state.
/// </summary>
/// <value>The state.</value>
public WebSocketState State => WebSocket.ReadyState;
public WebSocketState State => _webSocket.State;
/// <summary>
/// Sends the async.
@ -91,7 +47,7 @@ namespace Jellyfin.Server.SocketSharp
/// <returns>Task.</returns>
public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken)
{
return WebSocket.SendAsync(bytes);
return _webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken);
}
/// <summary>
@ -103,7 +59,7 @@ namespace Jellyfin.Server.SocketSharp
/// <returns>Task.</returns>
public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken)
{
return WebSocket.SendAsync(text);
return _webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken);
}
/// <summary>
@ -128,13 +84,13 @@ namespace Jellyfin.Server.SocketSharp
if (dispose)
{
WebSocket.OnMessage -= OnSocketMessage;
WebSocket.OnClose -= OnSocketClose;
WebSocket.OnError -= OnSocketError;
_cancellationTokenSource.Cancel();
WebSocket.CloseAsync().GetAwaiter().GetResult();
if (_webSocket.State == WebSocketState.Open)
{
_webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client",
CancellationToken.None);
}
Closed?.Invoke(this, EventArgs.Empty);
}
_disposed = true;

View File

@ -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;
}
}
}

View File

@ -2,59 +2,58 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Logging;
using SocketHttpListener.Net;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using IHttpFile = MediaBrowser.Model.Services.IHttpFile;
using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest;
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
using IResponse = MediaBrowser.Model.Services.IResponse;
namespace Jellyfin.Server.SocketSharp
namespace Emby.Server.Implementations.SocketSharp
{
public partial class WebSocketSharpRequest : IHttpRequest
{
private readonly HttpListenerRequest request;
private readonly IHttpResponse response;
private readonly HttpRequest request;
private readonly IResponse response;
public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, ILogger logger)
public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger)
{
this.OperationName = operationName;
this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
this.request = httpContext;
this.response = new WebSocketSharpResponse(logger, response, this);
// HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
}
public HttpListenerRequest HttpRequest => request;
public object OriginalRequest => request;
public HttpRequest HttpRequest => request;
public IResponse Response => response;
public IHttpResponse HttpResponse => response;
public IResponse HttpResponse => response;
public string OperationName { get; set; }
public object Dto { get; set; }
public string RawUrl => request.RawUrl;
public string RawUrl => request.GetEncodedPathAndQuery();
public string AbsoluteUri => request.Url.AbsoluteUri.TrimEnd('/');
public string UserHostAddress => request.UserHostAddress;
public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/');
public string XForwardedFor
=> string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
=> StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString();
public int? XForwardedPort
=> string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture);
=> StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture);
public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"];
public string XForwardedProtocol => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"].ToString();
public string XRealIp => string.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"];
public string XRealIp => StringValues.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"].ToString();
private string remoteIp;
public string RemoteIp
@ -66,19 +65,19 @@ namespace Jellyfin.Server.SocketSharp
return remoteIp;
}
var temp = CheckBadChars(XForwardedFor);
var temp = CheckBadChars(XForwardedFor.AsSpan());
if (temp.Length != 0)
{
return remoteIp = temp.ToString();
}
temp = CheckBadChars(XRealIp);
temp = CheckBadChars(XRealIp.AsSpan());
if (temp.Length != 0)
{
return remoteIp = NormalizeIp(temp).ToString();
}
return remoteIp = NormalizeIp(request.RemoteEndPoint?.Address.ToString()).ToString();
return remoteIp = NormalizeIp(request.HttpContext.Connection.RemoteIpAddress.ToString().AsSpan()).ToString();
}
}
@ -156,26 +155,13 @@ namespace Jellyfin.Server.SocketSharp
return name;
}
internal static bool ContainsNonAsciiChars(string token)
{
for (int i = 0; i < token.Length; ++i)
{
if ((token[i] < 0x20) || (token[i] > 0x7e))
{
return true;
}
}
return false;
}
private ReadOnlySpan<char> NormalizeIp(ReadOnlySpan<char> ip)
{
if (ip.Length != 0 && !ip.IsWhiteSpace())
{
// Handle ipv4 mapped to ipv6
const string srch = "::ffff:";
var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
var index = ip.IndexOf(srch.AsSpan(), StringComparison.OrdinalIgnoreCase);
if (index == 0)
{
ip = ip.Slice(srch.Length);
@ -185,9 +171,7 @@ namespace Jellyfin.Server.SocketSharp
return ip;
}
public bool IsSecureConnection => request.IsSecureConnection || XForwardedProtocol == "https";
public string[] AcceptTypes => request.AcceptTypes;
public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept);
private Dictionary<string, object> items;
public Dictionary<string, object> Items => items ?? (items = new Dictionary<string, object>());
@ -197,13 +181,13 @@ namespace Jellyfin.Server.SocketSharp
{
get =>
responseContentType
?? (responseContentType = GetResponseContentType(this));
?? (responseContentType = GetResponseContentType(HttpRequest));
set => this.responseContentType = value;
}
public const string FormUrlEncoded = "application/x-www-form-urlencoded";
public const string MultiPartFormData = "multipart/form-data";
public static string GetResponseContentType(IRequest httpReq)
public static string GetResponseContentType(HttpRequest httpReq)
{
var specifiedContentType = GetQueryStringContentType(httpReq);
if (!string.IsNullOrEmpty(specifiedContentType))
@ -213,7 +197,7 @@ namespace Jellyfin.Server.SocketSharp
const string serverDefaultContentType = "application/json";
var acceptContentTypes = httpReq.AcceptTypes;
var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept);
string defaultContentType = null;
if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData))
{
@ -261,7 +245,7 @@ namespace Jellyfin.Server.SocketSharp
public const string Soap11 = "text/xml; charset=utf-8";
public static bool HasAnyOfContentTypes(IRequest request, params string[] contentTypes)
public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes)
{
if (contentTypes == null || request.ContentType == null)
{
@ -279,18 +263,18 @@ namespace Jellyfin.Server.SocketSharp
return false;
}
public static bool IsContentType(IRequest request, string contentType)
public static bool IsContentType(HttpRequest request, string contentType)
{
return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase);
}
private static string GetQueryStringContentType(IRequest httpReq)
private static string GetQueryStringContentType(HttpRequest httpReq)
{
ReadOnlySpan<char> format = httpReq.QueryString["format"];
ReadOnlySpan<char> format = httpReq.Query["format"].ToString().AsSpan();
if (format == null)
{
const int formatMaxLength = 4;
ReadOnlySpan<char> pi = httpReq.PathInfo;
ReadOnlySpan<char> pi = httpReq.Path.ToString().AsSpan();
if (pi == null || pi.Length <= formatMaxLength)
{
return null;
@ -309,11 +293,11 @@ namespace Jellyfin.Server.SocketSharp
}
format = LeftPart(format, '.');
if (format.Contains("json", StringComparison.OrdinalIgnoreCase))
if (format.Contains("json".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return "application/json";
}
else if (format.Contains("xml", StringComparison.OrdinalIgnoreCase))
else if (format.Contains("xml".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return "application/xml";
}
@ -343,10 +327,10 @@ namespace Jellyfin.Server.SocketSharp
{
var mode = HandlerFactoryPath;
var pos = request.RawUrl.IndexOf('?', StringComparison.Ordinal);
var pos = RawUrl.IndexOf("?", StringComparison.Ordinal);
if (pos != -1)
{
var path = request.RawUrl.Substring(0, pos);
var path = RawUrl.Substring(0, pos);
this.pathInfo = GetPathInfo(
path,
mode,
@ -354,10 +338,10 @@ namespace Jellyfin.Server.SocketSharp
}
else
{
this.pathInfo = request.RawUrl;
this.pathInfo = RawUrl;
}
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
this.pathInfo = WebUtility.UrlDecode(pathInfo);
this.pathInfo = NormalizePathInfo(pathInfo, mode).ToString();
}
@ -425,55 +409,52 @@ namespace Jellyfin.Server.SocketSharp
return path.Length > 1 ? path.TrimEnd('/') : "/";
}
private Dictionary<string, System.Net.Cookie> cookies;
public IDictionary<string, System.Net.Cookie> Cookies
{
get
{
if (cookies == null)
{
cookies = new Dictionary<string, System.Net.Cookie>();
foreach (var cookie in this.request.Cookies)
{
var httpCookie = (System.Net.Cookie)cookie;
cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain);
}
}
public string UserAgent => request.Headers[HeaderNames.UserAgent];
return cookies;
}
}
public IHeaderDictionary Headers => request.Headers;
public string UserAgent => request.UserAgent;
public IQueryCollection QueryString => request.Query;
public QueryParamCollection Headers => request.Headers;
private QueryParamCollection queryString;
public QueryParamCollection QueryString => queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query));
public bool IsLocal => request.IsLocal;
public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString());
private string httpMethod;
public string HttpMethod =>
httpMethod
?? (httpMethod = request.HttpMethod);
?? (httpMethod = request.Method);
public string Verb => HttpMethod;
public string ContentType => request.ContentType;
private Encoding contentEncoding;
public Encoding ContentEncoding
private Encoding ContentEncoding
{
get => contentEncoding ?? request.ContentEncoding;
set => contentEncoding = value;
get
{
// TODO is this necessary?
if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP"))
{
string postDataCharset = Headers["x-up-devcap-post-charset"];
if (!string.IsNullOrEmpty(postDataCharset))
{
try
{
return Encoding.GetEncoding(postDataCharset);
}
catch (ArgumentException)
{
}
}
}
return request.GetTypedHeaders().ContentType.Encoding ?? Encoding.UTF8;
}
}
public Uri UrlReferrer => request.UrlReferrer;
public Uri UrlReferrer => request.GetTypedHeaders().Referer;
public static Encoding GetEncoding(string contentTypeHeader)
{
var param = GetParameter(contentTypeHeader, "charset=");
var param = GetParameter(contentTypeHeader.AsSpan(), "charset=");
if (param == null)
{
return null;
@ -489,9 +470,9 @@ namespace Jellyfin.Server.SocketSharp
}
}
public Stream InputStream => request.InputStream;
public Stream InputStream => request.Body;
public long ContentLength => request.ContentLength64;
public long ContentLength => request.ContentLength ?? 0;
private IHttpFile[] httpFiles;
public IHttpFile[] Files
@ -530,13 +511,13 @@ namespace Jellyfin.Server.SocketSharp
if (handlerPath != null)
{
var trimmed = pathInfo.AsSpan().TrimStart('/');
if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase))
if (trimmed.StartsWith(handlerPath.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return trimmed.Slice(handlerPath.Length).ToString();
return trimmed.Slice(handlerPath.Length).ToString().AsSpan();
}
}
return pathInfo;
return pathInfo.AsSpan();
}
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}
}
}

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Reflection;
using Emby.Server.Implementations;
using Emby.Server.Implementations.HttpServer;
using Jellyfin.Server.SocketSharp;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Configuration;
@ -35,8 +34,6 @@ namespace Jellyfin.Server
public override bool CanSelfRestart => StartupOptions.RestartPath != null;
protected override bool SupportsDualModeSockets => true;
protected override void RestartInternal() => Program.Restart();
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
@ -45,17 +42,5 @@ namespace Jellyfin.Server
}
protected override void ShutdownInternal() => Program.Shutdown();
protected override IHttpListener CreateHttpListener()
=> new WebSocketSharpListener(
Logger,
Certificate,
StreamHelper,
NetworkManager,
SocketFactory,
CryptographyProvider,
SupportsDualModeSockets,
FileSystemManager,
EnvironmentInfo);
}
}

View File

@ -20,6 +20,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -143,7 +144,6 @@ namespace Jellyfin.Server
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
await appHost.RunStartupTasks().ConfigureAwait(false);
// TODO: read input for a stop command
try

View File

@ -1,245 +0,0 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.Net;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
using SocketHttpListener.Net;
namespace Jellyfin.Server.SocketSharp
{
public class WebSocketSharpListener : IHttpListener
{
private HttpListener _listener;
private readonly ILogger _logger;
private readonly X509Certificate _certificate;
private readonly IStreamHelper _streamHelper;
private readonly INetworkManager _networkManager;
private readonly ISocketFactory _socketFactory;
private readonly ICryptoProvider _cryptoProvider;
private readonly IFileSystem _fileSystem;
private readonly bool _enableDualMode;
private readonly IEnvironmentInfo _environment;
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
private CancellationToken _disposeCancellationToken;
public WebSocketSharpListener(
ILogger logger,
X509Certificate certificate,
IStreamHelper streamHelper,
INetworkManager networkManager,
ISocketFactory socketFactory,
ICryptoProvider cryptoProvider,
bool enableDualMode,
IFileSystem fileSystem,
IEnvironmentInfo environment)
{
_logger = logger;
_certificate = certificate;
_streamHelper = streamHelper;
_networkManager = networkManager;
_socketFactory = socketFactory;
_cryptoProvider = cryptoProvider;
_enableDualMode = enableDualMode;
_fileSystem = fileSystem;
_environment = environment;
_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<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; }
public Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
public void Start(IEnumerable<string> urlPrefixes)
{
if (_listener == null)
{
_listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment);
}
_listener.EnableDualMode = _enableDualMode;
if (_certificate != null)
{
_listener.LoadCert(_certificate);
}
_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes);
_listener.Prefixes.AddRange(urlPrefixes);
_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false);
_listener.Start();
}
private static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var url = request.Url.ToString();
logger.LogInformation(
"{0} {1}. UserAgent: {2}",
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod,
url,
request.UserAgent ?? string.Empty);
}
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
{
IHttpRequest httpReq = null;
var request = context.Request;
try
{
if (request.IsWebSocketRequest)
{
LogRequest(_logger, request);
return ProcessWebSocketRequest(context);
}
httpReq = GetRequest(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing request");
httpReq = httpReq ?? GetRequest(context);
return ErrorHandler(ex, httpReq, true, true);
}
var uri = request.Url;
return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken);
}
private async Task ProcessWebSocketRequest(HttpListenerContext ctx)
{
try
{
var endpoint = ctx.Request.RemoteEndPoint.ToString();
var url = ctx.Request.RawUrl;
var queryString = ctx.Request.QueryString;
var connectingArgs = new WebSocketConnectingEventArgs
{
Url = url,
QueryString = queryString,
Endpoint = endpoint
};
WebSocketConnecting?.Invoke(connectingArgs);
if (connectingArgs.AllowConnection)
{
_logger.LogDebug("Web socket connection allowed");
var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false);
if (WebSocketConnected != null)
{
var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger);
await socket.ConnectAsServerAsync().ConfigureAwait(false);
WebSocketConnected(new WebSocketConnectEventArgs
{
Url = url,
QueryString = queryString,
WebSocket = socket,
Endpoint = endpoint
});
await socket.StartReceive().ConfigureAwait(false);
}
}
else
{
_logger.LogWarning("Web socket connection not allowed");
TryClose(ctx, 401);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "AcceptWebSocketAsync error");
TryClose(ctx, 500);
}
}
private void TryClose(HttpListenerContext ctx, int statusCode)
{
try
{
ctx.Response.StatusCode = statusCode;
ctx.Response.Close();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error closing web socket response");
}
}
private IHttpRequest GetRequest(HttpListenerContext httpContext)
{
var urlSegments = httpContext.Request.Url.Segments;
var operationName = urlSegments[urlSegments.Length - 1];
var req = new WebSocketSharpRequest(httpContext, operationName, _logger);
return req;
}
public Task Stop()
{
_disposeCancellationTokenSource.Cancel();
_listener?.Close();
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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -18,6 +18,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Api.Images
{
@ -634,7 +635,7 @@ namespace MediaBrowser.Api.Images
var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false);
headers["Vary"] = "Accept";
headers[HeaderNames.Vary] = HeaderNames.Accept;
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{

View File

@ -29,6 +29,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Api.Library
{
@ -827,7 +828,7 @@ namespace MediaBrowser.Api.Library
var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
if (!string.IsNullOrWhiteSpace(filename))
{
headers["Content-Disposition"] = "attachment; filename=\"" + filename + "\"";
headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\"";
}
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions

View File

@ -24,6 +24,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Api.LiveTv
{
@ -750,9 +751,10 @@ namespace MediaBrowser.Api.LiveTv
throw new FileNotFoundException();
}
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path);
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path)
};
return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger, _environment)
{
@ -772,9 +774,10 @@ namespace MediaBrowser.Api.LiveTv
var directStreamProvider = liveStreamInfo;
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container);
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file." + request.Container)
};
return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger, _environment)
{

View File

@ -609,12 +609,12 @@ namespace MediaBrowser.Api.Playback
{
foreach (var param in Request.QueryString)
{
if (char.IsLower(param.Name[0]))
if (char.IsLower(param.Key[0]))
{
// This was probably not parsed initially and should be a StreamOptions
// TODO: This should be incorporated either in the lower framework for parsing requests
// or the generated URL should correctly serialize it
request.StreamOptions[param.Name] = param.Value;
request.StreamOptions[param.Key] = param.Value;
}
}
}
@ -867,7 +867,7 @@ namespace MediaBrowser.Api.Playback
private void ApplyDeviceProfileSettings(StreamState state)
{
var headers = Request.Headers.ToDictionary();
var headers = Request.Headers;
if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId))
{

View File

@ -17,6 +17,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Api.Playback.Progressive
{
@ -154,7 +155,7 @@ namespace MediaBrowser.Api.Playback.Progressive
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
// TODO: Don't hardcode this
outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts");
outputHeaders[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file.ts");
return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None)
{
@ -196,9 +197,11 @@ namespace MediaBrowser.Api.Playback.Progressive
{
if (state.MediaSource.IsInfiniteStream)
{
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
[HeaderNames.ContentType] = contentType
};
outputHeaders["Content-Type"] = contentType;
return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None)
{
@ -298,16 +301,16 @@ namespace MediaBrowser.Api.Playback.Progressive
if (trySupportSeek)
{
if (!string.IsNullOrWhiteSpace(Request.QueryString["Range"]))
if (!string.IsNullOrWhiteSpace(Request.QueryString[HeaderNames.Range]))
{
options.RequestHeaders["Range"] = Request.QueryString["Range"];
options.RequestHeaders[HeaderNames.Range] = Request.QueryString[HeaderNames.Range];
}
}
var response = await HttpClient.GetResponse(options).ConfigureAwait(false);
if (trySupportSeek)
{
foreach (var name in new[] { "Content-Range", "Accept-Ranges" })
foreach (var name in new[] { HeaderNames.ContentRange, HeaderNames.AcceptRanges })
{
var val = response.Headers[name];
if (!string.IsNullOrWhiteSpace(val))
@ -318,13 +321,7 @@ namespace MediaBrowser.Api.Playback.Progressive
}
else
{
responseHeaders["Accept-Ranges"] = "none";
}
// Seeing cases of -1 here
if (response.ContentLength.HasValue && response.ContentLength.Value >= 0)
{
responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
responseHeaders[HeaderNames.AcceptRanges] = "none";
}
if (isHeadRequest)
@ -337,7 +334,7 @@ namespace MediaBrowser.Api.Playback.Progressive
var result = new StaticRemoteStreamWriter(response);
result.Headers["Content-Type"] = response.ContentType;
result.Headers[HeaderNames.ContentType] = response.ContentType;
// Add the response headers to the result object
foreach (var header in responseHeaders)
@ -361,41 +358,15 @@ namespace MediaBrowser.Api.Playback.Progressive
// Use the command line args with a dummy playlist path
var outputPath = state.OutputFilePath;
responseHeaders["Accept-Ranges"] = "none";
responseHeaders[HeaderNames.AcceptRanges] = "none";
var contentType = state.GetMimeType(outputPath);
// TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response
// What we really want to do is hunt that down and remove that
var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null;
if (contentLength.HasValue)
{
responseHeaders["Content-Length"] = contentLength.Value.ToString(UsCulture);
}
// Headers only
if (isHeadRequest)
{
var streamResult = ResultFactory.GetResult(null, new byte[] { }, contentType, responseHeaders);
var hasHeaders = streamResult as IHasHeaders;
if (hasHeaders != null)
{
if (contentLength.HasValue)
{
hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture);
}
else
{
if (hasHeaders.Headers.ContainsKey("Content-Length"))
{
hasHeaders.Headers.Remove("Content-Length");
}
}
}
return streamResult;
return ResultFactory.GetResult(null, Array.Empty<byte>(), contentType, responseHeaders);
}
var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath);
@ -414,9 +385,11 @@ namespace MediaBrowser.Api.Playback.Progressive
state.Dispose();
}
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
[HeaderNames.ContentType] = contentType
};
outputHeaders["Content-Type"] = contentType;
// Add the response headers to the result object
foreach (var item in responseHeaders)
@ -431,22 +404,5 @@ namespace MediaBrowser.Api.Playback.Progressive
transcodingLock.Release();
}
}
/// <summary>
/// Gets the length of the estimated content.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.Nullable{System.Int64}.</returns>
private long? GetEstimatedContentLength(StreamState state)
{
var totalBitrate = state.TotalOutputBitrate ?? 0;
if (totalBitrate > 0 && state.RunTimeTicks.HasValue)
{
return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8);
}
return null;
}
}
}

View File

@ -58,9 +58,8 @@ namespace MediaBrowser.Api.ScheduledTasks
/// <summary>
/// Gets the data to send.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>Task{IEnumerable{TaskInfo}}.</returns>
protected override Task<IEnumerable<TaskInfo>> GetDataToSend(WebSocketListenerState state, CancellationToken cancellationToken)
protected override Task<IEnumerable<TaskInfo>> GetDataToSend()
{
return Task.FromResult(TaskManager.ScheduledTasks
.OrderBy(i => i.Name)

View File

@ -79,9 +79,8 @@ namespace MediaBrowser.Api.Session
/// <summary>
/// Gets the data to send.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>Task{SystemInfo}.</returns>
protected override Task<IEnumerable<SessionInfo>> GetDataToSend(WebSocketListenerState state, CancellationToken cancellationToken)
protected override Task<IEnumerable<SessionInfo>> GetDataToSend()
{
return Task.FromResult(_sessionManager.Sessions);
}

View File

@ -38,9 +38,8 @@ namespace MediaBrowser.Api.System
/// <summary>
/// Gets the data to send.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>Task{SystemInfo}.</returns>
protected override Task<List<ActivityLogEntry>> GetDataToSend(WebSocketListenerState state, CancellationToken CancellationToken)
protected override Task<List<ActivityLogEntry>> GetDataToSend()
{
return Task.FromResult(new List<ActivityLogEntry>());
}

View File

@ -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;
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
@ -13,6 +13,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.0" />
</ItemGroup>
<ItemGroup>

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Common.Net
{
@ -24,8 +25,8 @@ namespace MediaBrowser.Common.Net
/// <value>The accept header.</value>
public string AcceptHeader
{
get => GetHeaderValue("Accept");
set => RequestHeaders["Accept"] = value;
get => GetHeaderValue(HeaderNames.Accept);
set => RequestHeaders[HeaderNames.Accept] = value;
}
/// <summary>
/// Gets or sets the cancellation token.
@ -45,8 +46,8 @@ namespace MediaBrowser.Common.Net
/// <value>The user agent.</value>
public string UserAgent
{
get => GetHeaderValue("User-Agent");
set => RequestHeaders["User-Agent"] = value;
get => GetHeaderValue(HeaderNames.UserAgent);
set => RequestHeaders[HeaderNames.UserAgent] = value;
}
/// <summary>

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Dlna;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Dlna
{
@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Dlna
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>DeviceProfile.</returns>
DeviceProfile GetProfile(IDictionary<string, string> headers);
DeviceProfile GetProfile(IHeaderDictionary headers);
/// <summary>
/// Gets the default profile.
@ -64,7 +65,7 @@ namespace MediaBrowser.Controller.Dlna
/// <param name="serverUuId">The server uu identifier.</param>
/// <param name="serverAddress">The server address.</param>
/// <returns>System.String.</returns>
string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId, string serverAddress);
string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress);
/// <summary>
/// Gets the icon.

View File

@ -22,8 +22,8 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// The _active connections
/// </summary>
protected readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType>> ActiveConnections =
new List<Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType>>();
protected readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> ActiveConnections =
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
/// <summary>
/// Gets the name.
@ -34,9 +34,8 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// Gets the data to send.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>Task{`1}.</returns>
protected abstract Task<TReturnDataType> GetDataToSend(TStateType state, CancellationToken cancellationToken);
protected abstract Task<TReturnDataType> GetDataToSend();
/// <summary>
/// The logger
@ -80,13 +79,6 @@ namespace MediaBrowser.Controller.Net
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
protected virtual bool SendOnTimer => false;
protected virtual void ParseMessageParams(string[] values)
{
}
/// <summary>
/// Starts sending messages over a web socket
/// </summary>
@ -98,19 +90,10 @@ namespace MediaBrowser.Controller.Net
var dueTimeMs = long.Parse(vals[0], UsCulture);
var periodMs = long.Parse(vals[1], UsCulture);
if (vals.Length > 2)
{
ParseMessageParams(vals.Skip(2).ToArray());
}
var cancellationTokenSource = new CancellationTokenSource();
Logger.LogDebug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name);
var timer = SendOnTimer ?
new Timer(TimerCallback, message.Connection, Timeout.Infinite, Timeout.Infinite) :
null;
var state = new TStateType
{
IntervalMs = periodMs,
@ -119,47 +102,13 @@ namespace MediaBrowser.Controller.Net
lock (ActiveConnections)
{
ActiveConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType>(message.Connection, cancellationTokenSource, timer, state));
ActiveConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>(message.Connection, cancellationTokenSource, state));
}
if (timer != null)
{
timer.Change(TimeSpan.FromMilliseconds(dueTimeMs), TimeSpan.FromMilliseconds(periodMs));
}
}
/// <summary>
/// Timers the callback.
/// </summary>
/// <param name="state">The state.</param>
private void TimerCallback(object state)
{
var connection = (IWebSocketConnection)state;
Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType> tuple;
lock (ActiveConnections)
{
tuple = ActiveConnections.FirstOrDefault(c => c.Item1 == connection);
}
if (tuple == null)
{
return;
}
if (connection.State != WebSocketState.Open || tuple.Item2.IsCancellationRequested)
{
DisposeConnection(tuple);
return;
}
SendData(tuple);
}
protected void SendData(bool force)
{
Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType>[] tuples;
Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>[] tuples;
lock (ActiveConnections)
{
@ -168,7 +117,7 @@ namespace MediaBrowser.Controller.Net
{
if (c.Item1.State == WebSocketState.Open && !c.Item2.IsCancellationRequested)
{
var state = c.Item4;
var state = c.Item3;
if (force || (DateTime.UtcNow - state.DateLastSendUtc).TotalMilliseconds >= state.IntervalMs)
{
@ -187,17 +136,17 @@ namespace MediaBrowser.Controller.Net
}
}
private async void SendData(Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType> tuple)
private async void SendData(Tuple<IWebSocketConnection, CancellationTokenSource, TStateType> tuple)
{
var connection = tuple.Item1;
try
{
var state = tuple.Item4;
var state = tuple.Item3;
var cancellationToken = tuple.Item2.Token;
var data = await GetDataToSend(state, cancellationToken).ConfigureAwait(false);
var data = await GetDataToSend().ConfigureAwait(false);
if (data != null)
{
@ -246,23 +195,12 @@ namespace MediaBrowser.Controller.Net
/// Disposes the connection.
/// </summary>
/// <param name="connection">The connection.</param>
private void DisposeConnection(Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType> connection)
private void DisposeConnection(Tuple<IWebSocketConnection, CancellationTokenSource, TStateType> connection)
{
Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name);
var timer = connection.Item3;
if (timer != null)
{
try
{
timer.Dispose();
}
catch (ObjectDisposedException)
{
//TODO Investigate and properly fix.
}
}
// TODO disposing the connection seems to break websockets in subtle ways, so what is the purpose of this function really...
// connection.Item1.Dispose();
try
{

View File

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
@ -35,5 +38,24 @@ namespace MediaBrowser.Controller.Net
/// If set, all requests will respond with this message
/// </summary>
string GlobalResponse { get; set; }
/// <summary>
/// Sends the http context to the socket listener
/// </summary>
/// <param name="ctx"></param>
/// <returns></returns>
Task ProcessWebSocketRequest(HttpContext ctx);
/// <summary>
/// The HTTP request handler
/// </summary>
/// <param name="httpReq"></param>
/// <param name="urlString"></param>
/// <param name="host"></param>
/// <param name="localPath"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath,
CancellationToken cancellationToken);
}
}

View File

@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
@ -35,7 +36,7 @@ namespace MediaBrowser.Controller.Net
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
QueryParamCollection QueryString { get; set; }
IQueryCollection QueryString { get; set; }
/// <summary>
/// Gets or sets the receive action.

View File

@ -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;
}
}
}

View File

@ -13,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
</ItemGroup>

View File

@ -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
}
}

View File

@ -2,11 +2,6 @@ namespace MediaBrowser.Model.Services
{
public interface IHttpRequest : IRequest
{
/// <summary>
/// The HttpResponse
/// </summary>
IHttpResponse HttpResponse { get; }
/// <summary>
/// The HTTP Verb
/// </summary>

View File

@ -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();
}
}

View File

@ -1,20 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Model.Services
{
public interface IRequest
{
/// <summary>
/// The underlying ASP.NET or HttpListener HttpRequest
/// </summary>
object OriginalRequest { get; }
IResponse Response { get; }
/// <summary>
@ -41,8 +36,6 @@ namespace MediaBrowser.Model.Services
string UserAgent { get; }
IDictionary<string, Cookie> Cookies { get; }
/// <summary>
/// The expected Response ContentType for this request
/// </summary>
@ -53,9 +46,9 @@ namespace MediaBrowser.Model.Services
/// </summary>
Dictionary<string, object> Items { get; }
QueryParamCollection Headers { get; }
IHeaderDictionary Headers { get; }
QueryParamCollection QueryString { get; }
IQueryCollection QueryString { get; }
Task<QueryParamCollection> GetFormData();
@ -63,11 +56,6 @@ namespace MediaBrowser.Model.Services
string AbsoluteUri { get; }
/// <summary>
/// The Remote Ip as reported by Request.UserHostAddress
/// </summary>
string UserHostAddress { get; }
/// <summary>
/// The Remote Ip as reported by X-Forwarded-For, X-Real-IP or Request.UserHostAddress
/// </summary>
@ -78,11 +66,6 @@ namespace MediaBrowser.Model.Services
/// </summary>
string Authorization { get; }
/// <summary>
/// e.g. is https or not
/// </summary>
bool IsSecureConnection { get; }
string[] AcceptTypes { get; }
string PathInfo { get; }
@ -134,25 +117,17 @@ namespace MediaBrowser.Model.Services
Stream OutputStream { get; }
/// <summary>
/// Signal that this response has been handled and no more processing should be done.
/// When used in a request or response filter, no more filters or processing is done on this request.
/// </summary>
void Close();
/// <summary>
/// Gets a value indicating whether this instance is closed.
/// </summary>
bool IsClosed { get; }
void SetContentLength(long contentLength);
bool IsClosed { get; set; }
//Add Metadata to Response
Dictionary<string, object> Items { get; }
QueryParamCollection Headers { get; }
IHeaderDictionary Headers { get; }
Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken);
Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken);
bool SendChunked { get; set; }
}

View File

@ -5,19 +5,11 @@ using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.Services
{
// Remove this garbage class, it's just a bastard copy of NameValueCollection
public class QueryParamCollection : List<NameValuePair>
{
public QueryParamCollection()
{
}
public QueryParamCollection(IDictionary<string, string> headers)
{
foreach (var pair in headers)
{
Add(pair.Key, pair.Value);
}
}
private static StringComparison GetStringComparison()
@ -30,30 +22,15 @@ namespace MediaBrowser.Model.Services
return StringComparer.OrdinalIgnoreCase;
}
public string GetKey(int index)
{
return this[index].Name;
}
public string Get(int index)
{
return this[index].Value;
}
public virtual string[] GetValues(int index)
{
return new[] { Get(index) };
}
/// <summary>
/// Adds a new query parameter.
/// </summary>
public virtual void Add(string key, string value)
public void Add(string key, string value)
{
Add(new NameValuePair(key, value));
}
public virtual void Set(string key, string value)
private void Set(string key, string value)
{
if (string.IsNullOrEmpty(value))
{
@ -81,17 +58,7 @@ namespace MediaBrowser.Model.Services
Add(key, value);
}
/// <summary>
/// Removes all parameters of the given name.
/// </summary>
/// <returns>The number of parameters that were removed</returns>
/// <exception cref="ArgumentNullException"><paramref name="name" /> is null.</exception>
public virtual int Remove(string name)
{
return RemoveAll(p => p.Name == name);
}
public string Get(string name)
private string Get(string name)
{
var stringComparison = GetStringComparison();
@ -106,7 +73,7 @@ namespace MediaBrowser.Model.Services
return null;
}
public virtual List<NameValuePair> GetItems(string name)
private List<NameValuePair> GetItems(string name)
{
var stringComparison = GetStringComparison();
@ -140,20 +107,6 @@ namespace MediaBrowser.Model.Services
return list;
}
public Dictionary<string, string> ToDictionary()
{
var stringComparer = GetStringComparer();
var headers = new Dictionary<string, string>(stringComparer);
foreach (var pair in this)
{
headers[pair.Name] = pair.Value;
}
return headers;
}
public IEnumerable<string> Keys
{
get

View File

@ -1,4 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.3
MinimumVisualStudioVersion = 10.0.40219.1
@ -36,8 +37,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocketHttpListener", "SocketHttpListener\SocketHttpListener.csproj", "{1D74413B-E7CF-455B-B021-F52BDF881542}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}"
@ -56,7 +55,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
SharedVersion.cs = SharedVersion.cs
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -132,10 +131,6 @@ Global
{CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU
{1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.Build.0 = Release|Any CPU
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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&lt;int&gt; 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&lt;int&gt; 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&lt;TEventArgs&gt;</c> delegate
/// if it isn't <see langword="null"/>.
/// </summary>
/// <param name="eventHandler">
/// An <c>EventHandler&lt;TEventArgs&gt;</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&lt;int&gt;</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&lt;int&gt;</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
}
}

View File

@ -1,8 +0,0 @@
namespace SocketHttpListener
{
internal enum Fin : byte
{
More = 0x0,
Final = 0x1
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -1,8 +0,0 @@
namespace SocketHttpListener
{
internal enum Mask : byte
{
Unmask = 0x0,
Mask = 0x1
}
}

View File

@ -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
}
}

View File

@ -1,6 +0,0 @@
using System.Net;
namespace SocketHttpListener.Net
{
public delegate AuthenticationSchemes AuthenticationSchemeSelector(HttpListenerRequest httpRequest);
}

View File

@ -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";
}
}

View File

@ -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,
}
}

View File

@ -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;
}
}
}

View File

@ -1,178 +0,0 @@
using System;
using System.IO;
using System.Net;
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 ChunkedInputStream : HttpRequestStream
{
private ChunkStream _decoder;
private readonly HttpListenerContext _context;
private bool _no_more_data;
private class ReadBufferState
{
public byte[] Buffer;
public int Offset;
public int Count;
public int InitialCount;
public HttpStreamAsyncResult Ares;
public ReadBufferState(byte[] buffer, int offset, int count, HttpStreamAsyncResult ares)
{
Buffer = buffer;
Offset = offset;
Count = count;
InitialCount = count;
Ares = ares;
}
}
public ChunkedInputStream(HttpListenerContext context, Stream stream, byte[] buffer, int offset, int length)
: base(stream, buffer, offset, length)
{
_context = context;
var coll = (WebHeaderCollection)context.Request.Headers;
_decoder = new ChunkStream(coll);
}
public ChunkStream Decoder
{
get => _decoder;
set => _decoder = value;
}
protected override int ReadCore(byte[] buffer, int offset, int count)
{
IAsyncResult ares = BeginReadCore(buffer, offset, count, null, null);
return EndRead(ares);
}
protected override IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state)
{
var ares = new HttpStreamAsyncResult(this);
ares._callback = cback;
ares._state = state;
if (_no_more_data || size == 0 || _closed)
{
ares.Complete();
return ares;
}
int nread = _decoder.Read(buffer, offset, size);
offset += nread;
size -= nread;
if (size == 0)
{
// got all we wanted, no need to bother the decoder yet
ares._count = nread;
ares.Complete();
return ares;
}
if (!_decoder.WantMore)
{
_no_more_data = nread == 0;
ares._count = nread;
ares.Complete();
return ares;
}
ares._buffer = new byte[8192];
ares._offset = 0;
ares._count = 8192;
var rb = new ReadBufferState(buffer, offset, size, ares);
rb.InitialCount += nread;
base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb);
return ares;
}
private void OnRead(IAsyncResult base_ares)
{
ReadBufferState rb = (ReadBufferState)base_ares.AsyncState;
var ares = rb.Ares;
try
{
int nread = base.EndRead(base_ares);
if (nread == 0)
{
_no_more_data = true;
ares._count = rb.InitialCount - rb.Count;
ares.Complete();
return;
}
_decoder.Write(ares._buffer, ares._offset, nread);
nread = _decoder.Read(rb.Buffer, rb.Offset, rb.Count);
rb.Offset += nread;
rb.Count -= nread;
if (rb.Count == 0 || !_decoder.WantMore)
{
_no_more_data = !_decoder.WantMore && nread == 0;
ares._count = rb.InitialCount - rb.Count;
ares.Complete();
return;
}
ares._offset = 0;
ares._count = Math.Min(8192, _decoder.ChunkLeft + 6);
base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb);
}
catch (Exception e)
{
_context.Connection.SendError(e.Message, 400);
ares.Complete(e);
}
}
public override int EndRead(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException(nameof(asyncResult));
var ares = asyncResult as HttpStreamAsyncResult;
if (ares == null || !ReferenceEquals(this, ares._parent))
{
throw new ArgumentException("Invalid async result");
}
if (ares._endCalled)
{
throw new InvalidOperationException("Invalid end call");
}
ares._endCalled = true;
if (!asyncResult.IsCompleted)
asyncResult.AsyncWaitHandle.WaitOne();
if (ares._error != null)
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "Operation aborted");
return ares._count;
}
}
}

View File

@ -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;
}
}
}

View File

@ -1,8 +0,0 @@
namespace SocketHttpListener.Net
{
internal enum EntitySendFormat
{
ContentLength = 0, // Content-Length: XXX
Chunked = 1, // Transfer-Encoding: chunked
}
}

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}
}
}

View File

@ -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("");
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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++;
}
}
}
}
}

View File

@ -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
}
}
}

View File

@ -1,333 +0,0 @@
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
namespace SocketHttpListener.Net
{
public sealed partial class HttpListenerResponse : IDisposable
{
private long _contentLength;
private Version _version = HttpVersion.Version11;
private int _statusCode = 200;
internal object _headersLock = new object();
private bool _forceCloseChunked;
internal HttpListenerResponse(HttpListenerContext context)
{
_httpContext = context;
}
internal bool ForceCloseChunked => _forceCloseChunked;
private void EnsureResponseStream()
{
if (_responseStream == null)
{
_responseStream = _httpContext.Connection.GetResponseStream();
}
}
public Version ProtocolVersion
{
get => _version;
set
{
CheckDisposed();
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
{
throw new ArgumentException("Wrong version");
}
_version = new Version(value.Major, value.Minor); // match Windows behavior, trimming to just Major.Minor
}
}
public int StatusCode
{
get => _statusCode;
set
{
CheckDisposed();
if (value < 100 || value > 999)
throw new ProtocolViolationException("Invalid status");
_statusCode = value;
}
}
private void Dispose()
{
Close(true);
}
public void Close()
{
if (Disposed)
return;
Close(false);
}
public void Abort()
{
if (Disposed)
return;
Close(true);
}
private void Close(bool force)
{
Disposed = true;
_httpContext.Connection.Close(force);
}
public void Close(byte[] responseEntity, bool willBlock)
{
CheckDisposed();
if (responseEntity == null)
{
throw new ArgumentNullException(nameof(responseEntity));
}
if (!SentHeaders && _boundaryType != BoundaryType.Chunked)
{
ContentLength64 = responseEntity.Length;
}
if (willBlock)
{
try
{
OutputStream.Write(responseEntity, 0, responseEntity.Length);
}
finally
{
Close(false);
}
}
else
{
OutputStream.BeginWrite(responseEntity, 0, responseEntity.Length, iar =>
{
var thisRef = (HttpListenerResponse)iar.AsyncState;
try
{
try
{
thisRef.OutputStream.EndWrite(iar);
}
finally
{
thisRef.Close(false);
}
}
catch (Exception)
{
// In case response was disposed during this time
}
}, this);
}
}
public void CopyFrom(HttpListenerResponse templateResponse)
{
_webHeaders.Clear();
//_webHeaders.Add(templateResponse._webHeaders);
_contentLength = templateResponse._contentLength;
_statusCode = templateResponse._statusCode;
_statusDescription = templateResponse._statusDescription;
_keepAlive = templateResponse._keepAlive;
_version = templateResponse._version;
}
internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandshake = false)
{
if (!isWebSocketHandshake)
{
if (_webHeaders["Server"] == null)
{
_webHeaders.Set("Server", "Microsoft-NetCore/2.0");
}
if (_webHeaders["Date"] == null)
{
_webHeaders.Set("Date", DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture));
}
if (_boundaryType == BoundaryType.None)
{
if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10)
{
_keepAlive = false;
}
else
{
_boundaryType = BoundaryType.Chunked;
}
if (CanSendResponseBody(_httpContext.Response.StatusCode))
{
_contentLength = -1;
}
else
{
_boundaryType = BoundaryType.ContentLength;
_contentLength = 0;
}
}
if (_boundaryType != BoundaryType.Chunked)
{
if (_boundaryType != BoundaryType.ContentLength && closing)
{
_contentLength = CanSendResponseBody(_httpContext.Response.StatusCode) ? -1 : 0;
}
if (_boundaryType == BoundaryType.ContentLength)
{
_webHeaders.Set("Content-Length", _contentLength.ToString("D", CultureInfo.InvariantCulture));
}
}
/* Apache forces closing the connection for these status codes:
* HttpStatusCode.BadRequest 400
* HttpStatusCode.RequestTimeout 408
* HttpStatusCode.LengthRequired 411
* HttpStatusCode.RequestEntityTooLarge 413
* HttpStatusCode.RequestUriTooLong 414
* HttpStatusCode.InternalServerError 500
* HttpStatusCode.ServiceUnavailable 503
*/
bool conn_close = (_statusCode == (int)HttpStatusCode.BadRequest || _statusCode == (int)HttpStatusCode.RequestTimeout
|| _statusCode == (int)HttpStatusCode.LengthRequired || _statusCode == (int)HttpStatusCode.RequestEntityTooLarge
|| _statusCode == (int)HttpStatusCode.RequestUriTooLong || _statusCode == (int)HttpStatusCode.InternalServerError
|| _statusCode == (int)HttpStatusCode.ServiceUnavailable);
if (!conn_close)
{
conn_close = !_httpContext.Request.KeepAlive;
}
// They sent both KeepAlive: true and Connection: close
if (!_keepAlive || conn_close)
{
_webHeaders.Set("Connection", "Close");
conn_close = true;
}
if (SendChunked)
{
_webHeaders.Set("Transfer-Encoding", "Chunked");
}
int reuses = _httpContext.Connection.Reuses;
if (reuses >= 100)
{
_forceCloseChunked = true;
if (!conn_close)
{
_webHeaders.Set("Connection", "Close");
conn_close = true;
}
}
if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10)
{
if (_keepAlive)
{
Headers["Keep-Alive"] = "true";
}
if (!conn_close)
{
_webHeaders.Set("Connection", "Keep-Alive");
}
}
ComputeCookies();
}
var encoding = Encoding.UTF8;
var writer = new StreamWriter(ms, encoding, 256);
writer.Write("HTTP/1.1 {0} ", _statusCode); // "1.1" matches Windows implementation, which ignores the response version
writer.Flush();
byte[] statusDescriptionBytes = WebHeaderEncoding.GetBytes(StatusDescription);
ms.Write(statusDescriptionBytes, 0, statusDescriptionBytes.Length);
writer.Write("\r\n");
writer.Write(FormatHeaders(_webHeaders));
writer.Flush();
int preamble = encoding.GetPreamble().Length;
EnsureResponseStream();
/* Assumes that the ms was at position 0 */
ms.Position = preamble;
SentHeaders = !isWebSocketHandshake;
}
private static bool HeaderCanHaveEmptyValue(string name) =>
!string.Equals(name, "Location", StringComparison.OrdinalIgnoreCase);
private static string FormatHeaders(WebHeaderCollection headers)
{
var sb = new StringBuilder();
for (int i = 0; i < headers.Count; i++)
{
string key = headers.GetKey(i);
string[] values = headers.GetValues(i);
int startingLength = sb.Length;
sb.Append(key).Append(": ");
bool anyValues = false;
for (int j = 0; j < values.Length; j++)
{
string value = values[j];
if (!string.IsNullOrWhiteSpace(value))
{
if (anyValues)
{
sb.Append(", ");
}
sb.Append(value);
anyValues = true;
}
}
if (anyValues || HeaderCanHaveEmptyValue(key))
{
// Complete the header
sb.Append("\r\n");
}
else
{
// Empty header; remove it.
sb.Length = startingLength;
}
}
return sb.Append("\r\n").ToString();
}
private bool Disposed { get; set; }
internal bool SentHeaders { get; set; }
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
return ((HttpResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken);
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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