Update to 3.5.2 and .net core 2.1

This commit is contained in:
stefan
2018-09-12 19:26:21 +02:00
parent c32d865638
commit 48facb797e
1419 changed files with 27525 additions and 88927 deletions

View File

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Services;
using System.Linq;
namespace Emby.Server.Implementations.HttpServer
{
@@ -147,6 +148,13 @@ namespace Emby.Server.Implementations.HttpServer
}
}
private string[] SkipLogExtensions = new string[]
{
".js",
".html",
".css"
};
public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken)
{
try
@@ -157,17 +165,24 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
var path = Path;
if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
{
Logger.Info("Transmit file {0}", Path);
var extension = System.IO.Path.GetExtension(path);
if (extension == null || !SkipLogExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Transmit file {0}", path);
}
//var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0;
await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
await response.TransmitFile(path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
return;
}
await response.TransmitFile(Path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false);
await response.TransmitFile(path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false);
}
finally
{

View File

@@ -12,7 +12,6 @@ using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer.SocketSharp;
using Emby.Server.Implementations.Services;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Security;
@@ -25,6 +24,10 @@ using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Text;
using System.Net.Sockets;
using Emby.Server.Implementations.Net;
using MediaBrowser.Common.Events;
using MediaBrowser.Model.Events;
namespace Emby.Server.Implementations.HttpServer
{
@@ -35,64 +38,47 @@ namespace Emby.Server.Implementations.HttpServer
private readonly ILogger _logger;
public string[] UrlPrefixes { get; private set; }
private readonly List<IService> _restServices = new List<IService>();
private IHttpListener _listener;
public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
public event EventHandler<WebSocketConnectingEventArgs> WebSocketConnecting;
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
private readonly IMemoryStreamFactory _memoryStreamProvider;
private readonly IServerApplicationHost _appHost;
private readonly ITextEncoding _textEncoding;
private readonly ISocketFactory _socketFactory;
private readonly ICryptoProvider _cryptoProvider;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IXmlSerializer _xmlSerializer;
private readonly X509Certificate _certificate;
private readonly IEnvironmentInfo _environment;
private readonly Func<Type, Func<string, object>> _funcParseFn;
private readonly bool _enableDualModeSockets;
public Action<IRequest, IResponse, object>[] RequestFilters { get; set; }
public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; }
private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
public static HttpListenerHost Instance { get; protected set; }
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
public HttpListenerHost(IServerApplicationHost applicationHost,
ILogger logger,
IServerConfigurationManager config,
string serviceName,
string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, X509Certificate certificate, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem)
string defaultRedirectPath, INetworkManager networkManager, ITextEncoding textEncoding, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, Func<Type, Func<string, object>> funcParseFn)
{
Instance = this;
_appHost = applicationHost;
DefaultRedirectPath = defaultRedirectPath;
_networkManager = networkManager;
_memoryStreamProvider = memoryStreamProvider;
_textEncoding = textEncoding;
_socketFactory = socketFactory;
_cryptoProvider = cryptoProvider;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
_environment = environment;
_certificate = certificate;
_funcParseFn = funcParseFn;
_enableDualModeSockets = enableDualModeSockets;
_fileSystem = fileSystem;
_config = config;
_logger = logger;
_funcParseFn = funcParseFn;
RequestFilters = new Action<IRequest, IResponse, object>[] { };
ResponseFilters = new Action<IRequest, IResponse, object>[] { };
}
@@ -140,12 +126,6 @@ namespace Emby.Server.Implementations.HttpServer
attribute.RequestFilter(req, res, requestDto);
}
//Exec global filters
foreach (var requestFilter in RequestFilters)
{
requestFilter(req, res, requestDto);
}
//Exec remaining RequestFilter attributes with Priority >= 0
for (; i < count && attributes[i].Priority >= 0; i++)
{
@@ -181,45 +161,38 @@ namespace Emby.Server.Implementations.HttpServer
return attributes;
}
private IHttpListener GetListener()
{
//return new KestrelHost.KestrelListener(_logger, _environment, _fileSystem);
return new WebSocketSharpListener(_logger,
_certificate,
_memoryStreamProvider,
_textEncoding,
_networkManager,
_socketFactory,
_cryptoProvider,
_enableDualModeSockets,
_fileSystem,
_environment);
}
private void OnWebSocketConnecting(WebSocketConnectingEventArgs args)
private void OnWebSocketConnected(WebSocketConnectEventArgs e)
{
if (_disposed)
{
return;
}
if (WebSocketConnecting != null)
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger, _textEncoding)
{
WebSocketConnecting(this, args);
}
}
OnReceive = ProcessWebSocketMessageReceived,
Url = e.Url,
QueryString = e.QueryString ?? new QueryParamCollection()
};
private void OnWebSocketConnected(WebSocketConnectEventArgs args)
{
if (_disposed)
connection.Closed += Connection_Closed;
lock (_webSocketConnections)
{
return;
_webSocketConnections.Add(connection);
}
if (WebSocketConnected != null)
{
WebSocketConnected(this, args);
EventHelper.FireEventIfNotNull(WebSocketConnected, this, new GenericEventArgs<IWebSocketConnection>(connection), _logger);
}
}
private void Connection_Closed(object sender, EventArgs e)
{
lock (_webSocketConnections)
{
_webSocketConnections.Remove((IWebSocketConnection)sender);
}
}
@@ -271,16 +244,20 @@ namespace Emby.Server.Implementations.HttpServer
return statusCode;
}
private void ErrorHandler(Exception ex, IRequest httpReq, bool logException = true)
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, bool logExceptionMessage)
{
try
{
ex = GetActualException(ex);
if (logException)
if (logExceptionStackTrace)
{
_logger.ErrorException("Error processing request", ex);
}
else if (logExceptionMessage)
{
_logger.Error(ex.Message);
}
var httpRes = httpReq.Response;
@@ -293,7 +270,7 @@ namespace Emby.Server.Implementations.HttpServer
httpRes.StatusCode = statusCode;
httpRes.ContentType = "text/html";
Write(httpRes, ex.Message);
await Write(httpRes, NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false);
}
catch
{
@@ -301,11 +278,46 @@ namespace Emby.Server.Implementations.HttpServer
}
}
private string NormalizeExceptionMessage(string msg)
{
if (msg == null)
{
return string.Empty;
}
// Strip any information we don't want to reveal
msg = msg.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase);
msg = msg.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
return msg;
}
/// <summary>
/// Shut down the Web Service
/// </summary>
public void Stop()
{
List<IWebSocketConnection> connections;
lock (_webSocketConnections)
{
connections = _webSocketConnections.ToList();
_webSocketConnections.Clear();
}
foreach (var connection in connections)
{
try
{
connection.Dispose();
}
catch
{
}
}
if (_listener != null)
{
_logger.Info("Stopping HttpListener...");
@@ -329,9 +341,9 @@ namespace Emby.Server.Implementations.HttpServer
{
var extension = GetExtension(url);
if (string.IsNullOrWhiteSpace(extension) || !_skipLogExtensions.ContainsKey(extension))
if (string.IsNullOrEmpty(extension) || !_skipLogExtensions.ContainsKey(extension))
{
if (string.IsNullOrWhiteSpace(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1)
if (string.IsNullOrEmpty(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1)
{
return true;
}
@@ -422,12 +434,53 @@ namespace Emby.Server.Implementations.HttpServer
return true;
}
private bool ValidateRequest(string remoteIp, bool isLocal)
{
if (isLocal)
{
return true;
}
if (_config.Configuration.EnableRemoteAccess)
{
var addressFilter = _config.Configuration.RemoteIPFilter.Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
if (addressFilter.Length > 0 && !_networkManager.IsInLocalNetwork(remoteIp))
{
if (_config.Configuration.IsRemoteIPFilterBlacklist)
{
return !_networkManager.IsAddressInSubnets(remoteIp, addressFilter);
}
else
{
return _networkManager.IsAddressInSubnets(remoteIp, addressFilter);
}
}
}
else
{
if (!_networkManager.IsInLocalNetwork(remoteIp))
{
return false;
}
}
return true;
}
private bool ValidateSsl(string remoteIp, string urlString)
{
if (_config.Configuration.RequireHttps && _appHost.EnableHttps)
if (_config.Configuration.RequireHttps && _appHost.EnableHttps && !_config.Configuration.IsBehindProxy)
{
if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1)
{
// These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected
if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1 ||
urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1)
{
return true;
}
if (!_networkManager.IsInLocalNetwork(remoteIp))
{
return false;
@@ -448,7 +501,7 @@ namespace Emby.Server.Implementations.HttpServer
bool enableLog = false;
bool logHeaders = false;
string urlToLog = null;
string remoteIp = null;
string remoteIp = httpReq.RemoteIp;
try
{
@@ -456,7 +509,7 @@ namespace Emby.Server.Implementations.HttpServer
{
httpRes.StatusCode = 503;
httpRes.ContentType = "text/plain";
Write(httpRes, "Server shutting down");
await Write(httpRes, "Server shutting down").ConfigureAwait(false);
return;
}
@@ -464,17 +517,21 @@ namespace Emby.Server.Implementations.HttpServer
{
httpRes.StatusCode = 400;
httpRes.ContentType = "text/plain";
Write(httpRes, "Invalid host");
await Write(httpRes, "Invalid host").ConfigureAwait(false);
return;
}
if (!ValidateRequest(remoteIp, httpReq.IsLocal))
{
httpRes.StatusCode = 403;
httpRes.ContentType = "text/plain";
await Write(httpRes, "Forbidden").ConfigureAwait(false);
return;
}
if (!ValidateSsl(httpReq.RemoteIp, urlString))
{
var httpsUrl = urlString
.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
RedirectToUrl(httpRes, httpsUrl);
RedirectToSecureUrl(httpReq, httpRes, urlString);
return;
}
@@ -485,7 +542,7 @@ namespace Emby.Server.Implementations.HttpServer
httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
httpRes.ContentType = "text/plain";
Write(httpRes, string.Empty);
await Write(httpRes, string.Empty).ConfigureAwait(false);
return;
}
@@ -498,7 +555,6 @@ namespace Emby.Server.Implementations.HttpServer
if (enableLog)
{
urlToLog = GetUrlToLog(urlString);
remoteIp = httpReq.RemoteIp;
LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null);
}
@@ -527,9 +583,9 @@ namespace Emby.Server.Implementations.HttpServer
if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
{
Write(httpRes,
await Write(httpRes,
"<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
newUrl + "\">" + newUrl + "</a></body></html>");
newUrl + "\">" + newUrl + "</a></body></html>").ConfigureAwait(false);
return;
}
}
@@ -544,9 +600,9 @@ namespace Emby.Server.Implementations.HttpServer
if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
{
Write(httpRes,
await Write(httpRes,
"<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
newUrl + "\">" + newUrl + "</a></body></html>");
newUrl + "\">" + newUrl + "</a></body></html>").ConfigureAwait(false);
return;
}
}
@@ -572,18 +628,29 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
if (!string.Equals(httpReq.QueryString["r"], "0", StringComparison.OrdinalIgnoreCase))
{
RedirectToUrl(httpRes, "web/pin.html");
return;
if (localPath.EndsWith("web/dashboard.html", StringComparison.OrdinalIgnoreCase))
{
RedirectToUrl(httpRes, "index.html#!/dashboard.html");
}
if (localPath.EndsWith("web/home.html", StringComparison.OrdinalIgnoreCase))
{
RedirectToUrl(httpRes, "index.html");
}
}
if (!string.IsNullOrWhiteSpace(GlobalResponse))
if (!string.IsNullOrEmpty(GlobalResponse))
{
httpRes.StatusCode = 503;
httpRes.ContentType = "text/html";
Write(httpRes, GlobalResponse);
return;
// We don't want the address pings in ApplicationHost to fail
if (localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1)
{
httpRes.StatusCode = 503;
httpRes.ContentType = "text/html";
await Write(httpRes, GlobalResponse).ConfigureAwait(false);
return;
}
}
var handler = GetServiceHandler(httpReq);
@@ -594,23 +661,34 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
ErrorHandler(new FileNotFoundException(), httpReq, false);
await ErrorHandler(new FileNotFoundException(), httpReq, false, false).ConfigureAwait(false);
}
}
catch (OperationCanceledException ex)
{
ErrorHandler(ex, httpReq, false);
await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
}
catch (IOException ex)
{
await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
}
catch (SocketException ex)
{
await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
}
catch (SecurityException ex)
{
await ErrorHandler(ex, httpReq, false, true).ConfigureAwait(false);
}
catch (Exception ex)
{
var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
#if DEBUG
logException = true;
#endif
ErrorHandler(ex, httpReq, logException);
await ErrorHandler(ex, httpReq, logException, false).ConfigureAwait(false);
}
finally
{
@@ -655,13 +733,36 @@ namespace Emby.Server.Implementations.HttpServer
return null;
}
private void Write(IResponse response, string text)
private Task Write(IResponse response, string text)
{
var bOutput = Encoding.UTF8.GetBytes(text);
response.SetContentLength(bOutput.Length);
var outputStream = response.OutputStream;
outputStream.Write(bOutput, 0, bOutput.Length);
return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length);
}
private void RedirectToSecureUrl(IHttpRequest httpReq, IResponse httpRes, string url)
{
int currentPort;
Uri uri;
if (Uri.TryCreate(url, UriKind.Absolute, out uri))
{
currentPort = uri.Port;
var builder = new UriBuilder(uri);
builder.Port = _config.Configuration.PublicHttpsPort;
builder.Scheme = "https";
url = builder.Uri.ToString();
RedirectToUrl(httpRes, url);
}
else
{
var httpsUrl = url
.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
RedirectToUrl(httpRes, url);
}
}
public static void RedirectToUrl(IResponse httpRes, string url)
@@ -676,26 +777,18 @@ namespace Emby.Server.Implementations.HttpServer
/// Adds the rest handlers.
/// </summary>
/// <param name="services">The services.</param>
public void Init(IEnumerable<IService> services)
public void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listeners)
{
_restServices.AddRange(services);
_webSocketListeners = listeners.ToArray();
ServiceController = new ServiceController();
_logger.Info("Calling ServiceStack AppHost.Init");
var types = _restServices.Select(r => r.GetType()).ToArray();
var types = services.Select(r => r.GetType()).ToArray();
ServiceController.Init(this, types);
var list = new List<Action<IRequest, IResponse, object>>();
foreach (var filter in _appHost.GetExports<IRequestFilter>())
{
list.Add(filter.Filter);
}
RequestFilters = list.ToArray();
ResponseFilters = new Action<IRequest, IResponse, object>[]
{
new ResponseFilter(_logger).FilterResponse
@@ -750,12 +843,12 @@ namespace Emby.Server.Implementations.HttpServer
_xmlSerializer.SerializeToStream(o, stream);
}
public object DeserializeXml(Type type, Stream stream)
public Task<object> DeserializeXml(Type type, Stream stream)
{
return _xmlSerializer.DeserializeFromStream(type, stream);
return Task.FromResult(_xmlSerializer.DeserializeFromStream(type, stream));
}
public object DeserializeJson(Type type, Stream stream)
public Task<object> DeserializeJson(Type type, Stream stream)
{
//using (var reader = new StreamReader(stream))
//{
@@ -763,7 +856,7 @@ namespace Emby.Server.Implementations.HttpServer
// Logger.Info(json);
// return _jsonSerializer.DeserializeFromString(json, type);
//}
return _jsonSerializer.DeserializeFromStream(stream, type);
return _jsonSerializer.DeserializeFromStreamAsync(stream, type);
}
private string NormalizeEmbyRoutePath(string path)
@@ -815,20 +908,46 @@ namespace Emby.Server.Implementations.HttpServer
}
}
/// <summary>
/// Processes the web socket message received.
/// </summary>
/// <param name="result">The result.</param>
private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result)
{
if (_disposed)
{
return Task.CompletedTask;
}
//_logger.Debug("Websocket message received: {0}", result.MessageType);
var tasks = _webSocketListeners.Select(i => Task.Run(async () =>
{
try
{
await i.ProcessMessage(result).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("{0} failed processing WebSocket message {1}", ex, i.GetType().Name, result.MessageType ?? string.Empty);
}
}));
return Task.WhenAll(tasks);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void StartServer(string[] urlPrefixes)
public void StartServer(string[] urlPrefixes, IHttpListener httpListener)
{
UrlPrefixes = urlPrefixes;
_listener = GetListener();
_listener = httpListener;
_listener.WebSocketConnected = OnWebSocketConnected;
_listener.WebSocketConnecting = OnWebSocketConnecting;
_listener.ErrorHandler = ErrorHandler;
_listener.RequestHandler = RequestHandler;

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Runtime.Serialization;
using System.Text;
@@ -30,16 +31,17 @@ namespace Emby.Server.Implementations.HttpServer
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IMemoryStreamFactory _memoryStreamFactory;
private IBrotliCompressor _brotliCompressor;
/// <summary>
/// Initializes a new instance of the <see cref="HttpResultFactory" /> class.
/// </summary>
public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IMemoryStreamFactory memoryStreamFactory)
public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IBrotliCompressor brotliCompressor)
{
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
_memoryStreamFactory = memoryStreamFactory;
_brotliCompressor = brotliCompressor;
_logger = logManager.GetLogger("HttpResultFactory");
}
@@ -50,9 +52,24 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="contentType">Type of the content.</param>
/// <param name="responseHeaders">The response headers.</param>
/// <returns>System.Object.</returns>
public object GetResult(object content, string contentType, IDictionary<string, string> responseHeaders = null)
public object GetResult(IRequest requestContext, byte[] content, string contentType, IDictionary<string, string> responseHeaders = null)
{
return GetHttpResult(content, contentType, true, responseHeaders);
return GetHttpResult(requestContext, content, contentType, true, responseHeaders);
}
public object GetResult(string content, string contentType, IDictionary<string, string> responseHeaders = null)
{
return GetHttpResult(null, content, contentType, true, responseHeaders);
}
public object GetResult(IRequest requestContext, Stream content, string contentType, IDictionary<string, string> responseHeaders = null)
{
return GetHttpResult(requestContext, content, contentType, true, responseHeaders);
}
public object GetResult(IRequest requestContext, string content, string contentType, IDictionary<string, string> responseHeaders = null)
{
return GetHttpResult(requestContext, content, contentType, true, responseHeaders);
}
public object GetRedirectResult(string url)
@@ -60,7 +77,7 @@ namespace Emby.Server.Implementations.HttpServer
var responseHeaders = new Dictionary<string, string>();
responseHeaders["Location"] = url;
var result = new HttpResult(new byte[] { }, "text/plain", HttpStatusCode.Redirect);
var result = new HttpResult(Array.Empty<byte>(), "text/plain", HttpStatusCode.Redirect);
AddResponseHeaders(result, responseHeaders);
@@ -70,39 +87,98 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Gets the HTTP result.
/// </summary>
private IHasHeaders GetHttpResult(object content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
{
var result = new StreamWriter(content, contentType, _logger);
if (responseHeaders == null)
{
responseHeaders = new Dictionary<string, string>();
}
string expires;
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out expires))
{
responseHeaders["Expires"] = "-1";
}
AddResponseHeaders(result, responseHeaders);
return result;
}
/// <summary>
/// Gets the HTTP result.
/// </summary>
private IHasHeaders GetHttpResult(IRequest requestContext, byte[] content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
{
IHasHeaders result;
var stream = content as Stream;
var compressionType = requestContext == null ? null : GetCompressionType(requestContext, content, contentType);
if (stream != null)
var isHeadRequest = string.Equals(requestContext.Verb, "head", StringComparison.OrdinalIgnoreCase);
if (string.IsNullOrEmpty(compressionType))
{
result = new StreamWriter(stream, contentType, _logger);
}
var contentLength = content.Length;
if (isHeadRequest)
{
content = Array.Empty<byte>();
}
result = new StreamWriter(content, contentType, contentLength, _logger);
}
else
{
var bytes = content as byte[];
if (bytes != null)
{
result = new StreamWriter(bytes, contentType, _logger);
}
else
{
var text = content as string;
if (text != null)
{
result = new StreamWriter(Encoding.UTF8.GetBytes(text), contentType, _logger);
}
else
{
result = new HttpResult(content, contentType, HttpStatusCode.OK);
}
}
result = GetCompressedResult(content, compressionType, responseHeaders, isHeadRequest, contentType);
}
if (responseHeaders == null)
{
responseHeaders = new Dictionary<string, string>();
}
string expires;
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out expires))
{
responseHeaders["Expires"] = "-1";
}
AddResponseHeaders(result, responseHeaders);
return result;
}
/// <summary>
/// Gets the HTTP result.
/// </summary>
private IHasHeaders GetHttpResult(IRequest requestContext, string content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
{
IHasHeaders result;
var bytes = Encoding.UTF8.GetBytes(content);
var compressionType = requestContext == null ? null : GetCompressionType(requestContext, bytes, contentType);
var isHeadRequest = requestContext == null ? false : string.Equals(requestContext.Verb, "head", StringComparison.OrdinalIgnoreCase);
if (string.IsNullOrEmpty(compressionType))
{
var contentLength = bytes.Length;
if (isHeadRequest)
{
bytes = Array.Empty<byte>();
}
result = new StreamWriter(bytes, contentType, contentLength, _logger);
}
else
{
result = GetCompressedResult(bytes, compressionType, responseHeaders, isHeadRequest, contentType);
}
if (responseHeaders == null)
{
responseHeaders = new Dictionary<string, string>();
@@ -123,19 +199,8 @@ namespace Emby.Server.Implementations.HttpServer
/// Gets the optimized result.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="requestContext">The request context.</param>
/// <param name="result">The result.</param>
/// <param name="responseHeaders">The response headers.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">result</exception>
public object GetOptimizedResult<T>(IRequest requestContext, T result, IDictionary<string, string> responseHeaders = null)
public object GetResult<T>(IRequest requestContext, T result, IDictionary<string, string> responseHeaders = null)
where T : class
{
return GetOptimizedResultInternal<T>(requestContext, result, true, responseHeaders);
}
private object GetOptimizedResultInternal<T>(IRequest requestContext, T result, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
where T : class
{
if (result == null)
{
@@ -147,24 +212,49 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
if (addCachePrevention)
{
responseHeaders["Expires"] = "-1";
}
responseHeaders["Expires"] = "-1";
return ToOptimizedResultInternal(requestContext, result, responseHeaders);
}
public static string GetCompressionType(IRequest request)
private string GetCompressionType(IRequest request, byte[] content, string responseContentType)
{
if (responseContentType == null)
{
return null;
}
// Per apple docs, hls manifests must be compressed
if (!responseContentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase) &&
responseContentType.IndexOf("json", StringComparison.OrdinalIgnoreCase) == -1 &&
responseContentType.IndexOf("javascript", StringComparison.OrdinalIgnoreCase) == -1 &&
responseContentType.IndexOf("xml", StringComparison.OrdinalIgnoreCase) == -1 &&
responseContentType.IndexOf("application/x-mpegURL", StringComparison.OrdinalIgnoreCase) == -1)
{
return null;
}
if (content.Length < 1024)
{
return null;
}
return GetCompressionType(request);
}
private string GetCompressionType(IRequest request)
{
var acceptEncoding = request.Headers["Accept-Encoding"];
if (!string.IsNullOrWhiteSpace(acceptEncoding))
if (acceptEncoding != null)
{
if (acceptEncoding.Contains("deflate"))
//if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
// return "br";
if (acceptEncoding.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1)
return "deflate";
if (acceptEncoding.Contains("gzip"))
if (acceptEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)
return "gzip";
}
@@ -180,7 +270,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <returns></returns>
public object ToOptimizedResult<T>(IRequest request, T dto)
{
return ToOptimizedResultInternal(request, dto, null);
return ToOptimizedResultInternal(request, dto);
}
private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null)
@@ -192,28 +282,110 @@ namespace Emby.Server.Implementations.HttpServer
case "application/xml":
case "text/xml":
case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
return GetHttpResult(SerializeToXmlString(dto), contentType, false, responseHeaders);
return GetHttpResult(request, SerializeToXmlString(dto), contentType, false, responseHeaders);
case "application/json":
case "text/json":
return GetHttpResult(_jsonSerializer.SerializeToString(dto), contentType, false, responseHeaders);
return GetHttpResult(request, _jsonSerializer.SerializeToString(dto), contentType, false, responseHeaders);
default:
break;
}
var isHeadRequest = string.Equals(request.Verb, "head", StringComparison.OrdinalIgnoreCase);
var ms = new MemoryStream();
var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
writerFn(dto, ms);
ms.Position = 0;
if (isHeadRequest)
{
using (ms)
{
var ms = new MemoryStream();
var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
writerFn(dto, ms);
ms.Position = 0;
if (string.Equals(request.Verb, "head", StringComparison.OrdinalIgnoreCase))
{
return GetHttpResult(new byte[] { }, contentType, true, responseHeaders);
}
return GetHttpResult(ms, contentType, true, responseHeaders);
return GetHttpResult(request, Array.Empty<byte>(), contentType, true, responseHeaders);
}
}
return GetHttpResult(request, ms, contentType, true, responseHeaders);
}
private IHasHeaders GetCompressedResult(byte[] content,
string requestedCompressionType,
IDictionary<string, string> responseHeaders,
bool isHeadRequest,
string contentType)
{
if (responseHeaders == null)
{
responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
content = Compress(content, requestedCompressionType);
responseHeaders["Content-Encoding"] = requestedCompressionType;
responseHeaders["Vary"] = "Accept-Encoding";
var contentLength = content.Length;
if (isHeadRequest)
{
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger);
AddResponseHeaders(result, responseHeaders);
return result;
}
else
{
var result = new StreamWriter(content, contentType, contentLength, _logger);
AddResponseHeaders(result, responseHeaders);
return result;
}
}
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);
if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase))
return GZip(bytes);
throw new NotSupportedException(compressionType);
}
private byte[] CompressBrotli(byte[] bytes)
{
return _brotliCompressor.Compress(bytes);
}
private byte[] Deflate(byte[] bytes)
{
// In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream
// Which means we must use MemoryStream since you have to use ToArray() on a closed Stream
using (var ms = new MemoryStream())
using (var zipStream = new DeflateStream(ms, CompressionMode.Compress))
{
zipStream.Write(bytes, 0, bytes.Length);
zipStream.Dispose();
return ms.ToArray();
}
}
private byte[] GZip(byte[] buffer)
{
using (var ms = new MemoryStream())
using (var zipStream = new GZipStream(ms, CompressionMode.Compress))
{
zipStream.Write(buffer, 0, buffer.Length);
zipStream.Dispose();
return ms.ToArray();
}
}
public static string GetRealContentType(string contentType)
@@ -243,104 +415,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
/// <summary>
/// Gets the optimized result using cache.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="requestContext">The request context.</param>
/// <param name="cacheKey">The cache key.</param>
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <param name="factoryFn">The factory fn.</param>
/// <param name="responseHeaders">The response headers.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">cacheKey
/// or
/// factoryFn</exception>
public object GetOptimizedResultUsingCache<T>(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, IDictionary<string, string> responseHeaders = null)
where T : class
{
if (cacheKey == Guid.Empty)
{
throw new ArgumentNullException("cacheKey");
}
if (factoryFn == null)
{
throw new ArgumentNullException("factoryFn");
}
var key = cacheKey.ToString("N");
if (responseHeaders == null)
{
responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
// See if the result is already cached in the browser
var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, null);
if (result != null)
{
return result;
}
return GetOptimizedResultInternal(requestContext, factoryFn(), false, responseHeaders);
}
/// <summary>
/// To the cached result.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="requestContext">The request context.</param>
/// <param name="cacheKey">The cache key.</param>
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <param name="factoryFn">The factory fn.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="responseHeaders">The response headers.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
public object GetCachedResult<T>(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType, IDictionary<string, string> responseHeaders = null)
where T : class
{
if (cacheKey == Guid.Empty)
{
throw new ArgumentNullException("cacheKey");
}
if (factoryFn == null)
{
throw new ArgumentNullException("factoryFn");
}
var key = cacheKey.ToString("N");
if (responseHeaders == null)
{
responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
// See if the result is already cached in the browser
var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, contentType);
if (result != null)
{
return result;
}
result = factoryFn();
// Apply caching headers
var hasHeaders = result as IHasHeaders;
if (hasHeaders != null)
{
AddResponseHeaders(hasHeaders, responseHeaders);
return hasHeaders;
}
return GetHttpResult(result, contentType, false, responseHeaders);
}
/// <summary>
/// Pres the process optimized result.
/// </summary>
@@ -357,7 +431,7 @@ namespace Emby.Server.Implementations.HttpServer
AddAgeHeader(responseHeaders, lastDateModified);
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified);
var result = new HttpResult(Array.Empty<byte>(), contentType ?? "text/html", HttpStatusCode.NotModified);
AddResponseHeaders(result, responseHeaders);
@@ -402,7 +476,7 @@ namespace Emby.Server.Implementations.HttpServer
throw new ArgumentException("FileShare must be either Read or ReadWrite");
}
if (string.IsNullOrWhiteSpace(options.ContentType))
if (string.IsNullOrEmpty(options.ContentType))
{
options.ContentType = MimeTypes.GetMimeType(path);
}
@@ -460,19 +534,17 @@ namespace Emby.Server.Implementations.HttpServer
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var contentType = options.ContentType;
if (cacheKey == Guid.Empty)
if (!cacheKey.Equals(Guid.Empty))
{
throw new ArgumentNullException("cacheKey");
}
var key = cacheKey.ToString("N");
var key = cacheKey.ToString("N");
// See if the result is already cached in the browser
var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType);
// See if the result is already cached in the browser
var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType);
if (result != null)
{
return result;
if (result != null)
{
return result;
}
}
// TODO: We don't really need the option value
@@ -484,7 +556,7 @@ namespace Emby.Server.Implementations.HttpServer
var rangeHeader = requestContext.Headers.Get("Range");
if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path))
if (!isHeadRequest && !string.IsNullOrEmpty(options.Path))
{
var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
{
@@ -497,11 +569,24 @@ namespace Emby.Server.Implementations.HttpServer
return hasHeaders;
}
if (!string.IsNullOrWhiteSpace(rangeHeader))
{
var stream = await factoryFn().ConfigureAwait(false);
var stream = await factoryFn().ConfigureAwait(false);
var hasHeaders = new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger)
var totalContentLength = options.ContentLength;
if (!totalContentLength.HasValue)
{
try
{
totalContentLength = stream.Length;
}
catch (NotSupportedException)
{
}
}
if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue)
{
var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest, _logger)
{
OnComplete = options.OnComplete
};
@@ -511,15 +596,17 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
var stream = await factoryFn().ConfigureAwait(false);
responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
if (totalContentLength.HasValue)
{
responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture);
}
if (isHeadRequest)
{
stream.Dispose();
return GetHttpResult(new byte[] { }, contentType, true, responseHeaders);
using (stream)
{
return GetHttpResult(requestContext, Array.Empty<byte>(), contentType, true, responseHeaders);
}
}
var hasHeaders = new StreamWriter(stream, contentType, _logger)
@@ -603,7 +690,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
private bool IsNotModified(IRequest requestContext, Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
private bool IsNotModified(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
{
//var isNotModified = true;
@@ -624,8 +711,10 @@ namespace Emby.Server.Implementations.HttpServer
var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match");
var hasCacheKey = !cacheKey.Equals(Guid.Empty);
// Validate If-None-Match
if ((cacheKey.HasValue || !string.IsNullOrEmpty(ifNoneMatchHeader)))
if ((hasCacheKey || !string.IsNullOrEmpty(ifNoneMatchHeader)))
{
Guid ifNoneMatch;
@@ -633,7 +722,7 @@ namespace Emby.Server.Implementations.HttpServer
if (Guid.TryParse(ifNoneMatchHeader, out ifNoneMatch))
{
if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch)
if (hasCacheKey && cacheKey.Equals(ifNoneMatch))
{
return true;
}
@@ -697,4 +786,9 @@ namespace Emby.Server.Implementations.HttpServer
}
}
}
public interface IBrotliCompressor
{
byte[] Compress(byte[] content);
}
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
using Emby.Server.Implementations.Net;
namespace Emby.Server.Implementations.HttpServer
{
@@ -13,7 +14,7 @@ namespace Emby.Server.Implementations.HttpServer
/// Gets or sets the error handler.
/// </summary>
/// <value>The error handler.</value>
Action<Exception, IRequest, bool> ErrorHandler { get; set; }
Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
/// <summary>
/// Gets or sets the request handler.

View File

@@ -2,24 +2,11 @@
using System;
using System.Globalization;
using MediaBrowser.Model.Services;
using SocketHttpListener.Net;
namespace Emby.Server.Implementations.HttpServer
{
public static class LoggerUtils
{
/// <summary>
/// Logs the request.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="request">The request.</param>
public static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var url = request.Url.ToString();
logger.Info("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
}
public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers)
{
if (headers == null)

View File

@@ -58,7 +58,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
public RangeRequestWriter(string rangeHeader, Stream source, string contentType, bool isHeadRequest, ILogger logger)
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
{
if (string.IsNullOrEmpty(contentType))
{
@@ -76,17 +76,17 @@ namespace Emby.Server.Implementations.HttpServer
StatusCode = HttpStatusCode.PartialContent;
Cookies = new List<Cookie>();
SetRangeValues();
SetRangeValues(contentLength);
}
/// <summary>
/// Sets the range values.
/// </summary>
private void SetRangeValues()
private void SetRangeValues(long contentLength)
{
var requestedRange = RequestedRanges[0];
TotalContentLength = SourceStream.Length;
TotalContentLength = contentLength;
// If the requested range is "0-", we can optimize by just doing a stream copy
if (!requestedRange.Value.HasValue)
@@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.HttpServer
Headers["Content-Length"] = RangeLength.ToString(UsCulture);
Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
if (RangeStart > 0)
if (RangeStart > 0 && SourceStream.CanSeek)
{
SourceStream.Position = RangeStart;
}

View File

@@ -2,7 +2,6 @@
using System;
using System.Globalization;
using System.Text;
using Emby.Server.Implementations.HttpServer.SocketSharp;
using MediaBrowser.Model.Services;
namespace Emby.Server.Implementations.HttpServer
@@ -47,7 +46,6 @@ namespace Emby.Server.Implementations.HttpServer
}
var hasHeaders = dto as IHasHeaders;
var sharpResponse = res as WebSocketSharpResponse;
if (hasHeaders != null)
{
@@ -67,7 +65,7 @@ namespace Emby.Server.Implementations.HttpServer
if (length > 0)
{
res.SetContentLength(length);
//var listenerResponse = res.OriginalResponse as HttpListenerResponse;
//if (listenerResponse != null)
@@ -78,10 +76,7 @@ namespace Emby.Server.Implementations.HttpServer
// return;
//}
if (sharpResponse != null)
{
sharpResponse.SendChunked = false;
}
res.SendChunked = false;
}
}
}

View File

@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Session;
using System;
using System.Linq;
using MediaBrowser.Model.Services;
using MediaBrowser.Common.Net;
namespace Emby.Server.Implementations.HttpServer.Security
{
@@ -16,21 +17,21 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
private readonly IServerConfigurationManager _config;
public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager, ISessionManager sessionManager, IDeviceManager deviceManager)
public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager, ISessionManager sessionManager, INetworkManager networkManager)
{
AuthorizationContext = authorizationContext;
_config = config;
DeviceManager = deviceManager;
SessionManager = sessionManager;
ConnectManager = connectManager;
UserManager = userManager;
NetworkManager = networkManager;
}
public IUserManager UserManager { get; private set; }
public IAuthorizationContext AuthorizationContext { get; private set; }
public IConnectManager ConnectManager { get; private set; }
public ISessionManager SessionManager { get; private set; }
public IDeviceManager DeviceManager { get; private set; }
public INetworkManager NetworkManager { get; private set; }
/// <summary>
/// Redirect the client to a specific URL if authentication failed.
@@ -38,14 +39,12 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary>
public string HtmlRedirect { get; set; }
public void Authenticate(IRequest request,
IAuthenticationAttributes authAttribtues)
public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues)
{
ValidateUser(request, authAttribtues);
}
private void ValidateUser(IRequest request,
IAuthenticationAttributes authAttribtues)
private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
{
// This code is executed before the service
var auth = AuthorizationContext.GetAuthorizationInfo(request);
@@ -60,11 +59,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
}
}
var user = string.IsNullOrWhiteSpace(auth.UserId)
? null
: UserManager.GetUserById(auth.UserId);
if (authAttribtues.AllowLocalOnly && !request.IsLocal)
{
throw new SecurityException("Operation not found.");
}
if (user == null & !string.IsNullOrWhiteSpace(auth.UserId))
var user = auth.User;
if (user == null & !auth.UserId.Equals(Guid.Empty))
{
throw new SecurityException("User with Id " + auth.UserId + " not found");
}
@@ -83,9 +85,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
ValidateRoles(roles, user);
}
if (!string.IsNullOrWhiteSpace(auth.DeviceId) &&
!string.IsNullOrWhiteSpace(auth.Client) &&
!string.IsNullOrWhiteSpace(auth.Device))
if (!string.IsNullOrEmpty(auth.DeviceId) &&
!string.IsNullOrEmpty(auth.Client) &&
!string.IsNullOrEmpty(auth.Device))
{
SessionManager.LogSessionActivity(auth.Client,
auth.Version,
@@ -108,6 +110,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
};
}
if (!user.Policy.EnableRemoteAccess && !NetworkManager.IsInLocalNetwork(request.RemoteIp))
{
throw new SecurityException("User account has been disabled.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
}
if (!user.Policy.IsAdministrator &&
!authAttribtues.EscapeParentalControl &&
!user.IsParentalScheduleAllowed())
@@ -119,17 +129,6 @@ namespace Emby.Server.Implementations.HttpServer.Security
SecurityExceptionType = SecurityExceptionType.ParentalControl
};
}
if (!string.IsNullOrWhiteSpace(auth.DeviceId))
{
if (!DeviceManager.CanAccessDevice(user.Id.ToString("N"), auth.DeviceId))
{
throw new SecurityException("User is not allowed access from this device.")
{
SecurityExceptionType = SecurityExceptionType.ParentalControl
};
}
}
}
private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request)
@@ -143,6 +142,10 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
return true;
}
if (authAttribtues.AllowLocalOnly && request.IsLocal)
{
return true;
}
return false;
}
@@ -159,12 +162,17 @@ namespace Emby.Server.Implementations.HttpServer.Security
return true;
}
if (string.IsNullOrWhiteSpace(auth.Token))
if (authAttribtues.AllowLocalOnly && request.IsLocal)
{
return true;
}
if (tokenInfo != null && string.IsNullOrWhiteSpace(tokenInfo.UserId))
if (string.IsNullOrEmpty(auth.Token))
{
return true;
}
if (tokenInfo != null && tokenInfo.UserId.Equals(Guid.Empty))
{
return true;
}
@@ -225,7 +233,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
private void ValidateSecurityToken(IRequest request, string token)
{
if (string.IsNullOrWhiteSpace(token))
if (string.IsNullOrEmpty(token))
{
throw new SecurityException("Access token is required.");
}
@@ -237,12 +245,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
throw new SecurityException("Access token is invalid or expired.");
}
if (!info.IsActive)
{
throw new SecurityException("Access token has expired.");
}
//if (!string.IsNullOrWhiteSpace(info.UserId))
//if (!string.IsNullOrEmpty(info.UserId))
//{
// var user = _userManager.GetUserById(info.UserId);

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using MediaBrowser.Model.Services;
using System.Linq;
using System.Threading;
using MediaBrowser.Controller.Library;
namespace Emby.Server.Implementations.HttpServer.Security
{
@@ -13,11 +14,13 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
private readonly IAuthenticationRepository _authRepo;
private readonly IConnectManager _connectManager;
private readonly IUserManager _userManager;
public AuthorizationContext(IAuthenticationRepository authRepo, IConnectManager connectManager)
public AuthorizationContext(IAuthenticationRepository authRepo, IConnectManager connectManager, IUserManager userManager)
{
_authRepo = authRepo;
_connectManager = connectManager;
_userManager = userManager;
}
public AuthorizationInfo GetAuthorizationInfo(object requestContext)
@@ -60,16 +63,16 @@ namespace Emby.Server.Implementations.HttpServer.Security
auth.TryGetValue("Token", out token);
}
if (string.IsNullOrWhiteSpace(token))
if (string.IsNullOrEmpty(token))
{
token = httpReq.Headers["X-Emby-Token"];
}
if (string.IsNullOrWhiteSpace(token))
if (string.IsNullOrEmpty(token))
{
token = httpReq.Headers["X-MediaBrowser-Token"];
}
if (string.IsNullOrWhiteSpace(token))
if (string.IsNullOrEmpty(token))
{
token = httpReq.QueryString["api_key"];
}
@@ -94,8 +97,6 @@ namespace Emby.Server.Implementations.HttpServer.Security
if (tokenInfo != null)
{
info.UserId = tokenInfo.UserId;
var updateToken = false;
// TODO: Remove these checks for IsNullOrWhiteSpace
@@ -109,15 +110,21 @@ namespace Emby.Server.Implementations.HttpServer.Security
info.DeviceId = tokenInfo.DeviceId;
}
// Temporary. TODO - allow clients to specify that the token has been shared with a casting device
var allowTokenInfoUpdate = info.Client == null || info.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
if (string.IsNullOrWhiteSpace(info.Device))
{
info.Device = tokenInfo.DeviceName;
}
else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
{
updateToken = true;
tokenInfo.DeviceName = info.Device;
if (allowTokenInfoUpdate)
{
updateToken = true;
tokenInfo.DeviceName = info.Device;
}
}
if (string.IsNullOrWhiteSpace(info.Version))
@@ -126,22 +133,38 @@ namespace Emby.Server.Implementations.HttpServer.Security
}
else if (!string.Equals(info.Version, tokenInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
{
if (allowTokenInfoUpdate)
{
updateToken = true;
tokenInfo.AppVersion = info.Version;
}
}
if ((DateTime.UtcNow - tokenInfo.DateLastActivity).TotalMinutes > 3)
{
tokenInfo.DateLastActivity = DateTime.UtcNow;
updateToken = true;
tokenInfo.AppVersion = info.Version;
}
if (!tokenInfo.UserId.Equals(Guid.Empty))
{
info.User = _userManager.GetUserById(tokenInfo.UserId);
if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase))
{
tokenInfo.UserName = info.User.Name;
updateToken = true;
}
}
if (updateToken)
{
_authRepo.Update(tokenInfo, CancellationToken.None);
_authRepo.Update(tokenInfo);
}
}
else
{
var user = _connectManager.GetUserFromExchangeToken(token);
if (user != null)
{
info.UserId = user.Id.ToString("N");
}
info.User = _connectManager.GetUserFromExchangeToken(token);
}
httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo;
}
@@ -160,7 +183,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
var auth = httpReq.Headers["X-Emby-Authorization"];
if (string.IsNullOrWhiteSpace(auth))
if (string.IsNullOrEmpty(auth))
{
auth = httpReq.Headers["Authorization"];
}
@@ -212,7 +235,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
private string NormalizeValue(string value)
{
if (string.IsNullOrWhiteSpace(value))
if (string.IsNullOrEmpty(value))
{
return value;
}

View File

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
using System;
namespace Emby.Server.Implementations.HttpServer.Security
{
@@ -21,11 +22,11 @@ namespace Emby.Server.Implementations.HttpServer.Security
_sessionManager = sessionManager;
}
public Task<SessionInfo> GetSession(IRequest requestContext)
public SessionInfo GetSession(IRequest requestContext)
{
var authorization = _authContext.GetAuthorizationInfo(requestContext);
var user = string.IsNullOrWhiteSpace(authorization.UserId) ? null : _userManager.GetUserById(authorization.UserId);
var user = authorization.User;
return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.RemoteIp, user);
}
@@ -36,19 +37,19 @@ namespace Emby.Server.Implementations.HttpServer.Security
return info as AuthenticationInfo;
}
public Task<SessionInfo> GetSession(object requestContext)
public SessionInfo GetSession(object requestContext)
{
return GetSession((IRequest)requestContext);
}
public async Task<User> GetUser(IRequest requestContext)
public User GetUser(IRequest requestContext)
{
var session = await GetSession(requestContext).ConfigureAwait(false);
var session = GetSession(requestContext);
return session == null || !session.UserId.HasValue ? null : _userManager.GetUserById(session.UserId.Value);
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
}
public Task<User> GetUser(object requestContext)
public User GetUser(object requestContext)
{
return GetUser((IRequest)requestContext);
}

View File

@@ -1,12 +0,0 @@
using SocketHttpListener.Net;
namespace Emby.Server.Implementations.HttpServer.SocketSharp
{
public static class Extensions
{
public static string GetOperationName(this HttpListenerRequest request)
{
return request.Url.Segments[request.Url.Segments.Length - 1];
}
}
}

View File

@@ -1,923 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.HttpServer.SocketSharp
{
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.Count);
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("s");
if (s.IndexOf('&') == -1)
return s;
StringBuilder entity = new StringBuilder();
StringBuilder 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("query");
if (encoding == null)
throw new ArgumentNullException("encoding");
if (query.Length == 0 || (query.Length == 1 && query[0] == '?'))
return new QueryParamCollection();
if (query[0] == '?')
query = query.Substring(1);
QueryParamCollection 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

@@ -1,846 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
namespace Emby.Server.Implementations.HttpServer.SocketSharp
{
public partial class WebSocketSharpRequest : IHttpRequest
{
static internal string GetParameter(string header, string attr)
{
int ap = header.IndexOf(attr);
if (ap == -1)
return null;
ap += attr.Length;
if (ap >= header.Length)
return null;
char ending = header[ap];
if (ending != '"')
ending = ' ';
int end = header.IndexOf(ending, ap + 1);
if (end == -1)
return ending == '"' ? null : header.Substring(ap);
return header.Substring(ap + 1, end - ap - 1);
}
async Task LoadMultiPart()
{
string boundary = GetParameter(ContentType, "; boundary=");
if (boundary == null)
return;
using (var requestStream = GetSubStream(InputStream, _memoryStreamProvider))
{
//DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
//Not ending with \r\n?
var ms = _memoryStreamProvider.CreateNew(32 * 1024);
await requestStream.CopyToAsync(ms).ConfigureAwait(false);
var input = ms;
ms.WriteByte((byte)'\r');
ms.WriteByte((byte)'\n');
input.Position = 0;
//Uncomment to debug
//var content = new StreamReader(ms).ReadToEnd();
//Console.WriteLine(boundary + "::" + content);
//input.Position = 0;
var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
HttpMultipart.Element e;
while ((e = multi_part.ReadNextElement()) != null)
{
if (e.Filename == null)
{
byte[] copy = new byte[e.Length];
input.Position = e.Start;
input.Read(copy, 0, (int)e.Length);
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length));
}
else
{
//
// We use a substream, as in 2.x we will support large uploads streamed to disk,
//
HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
files[e.Name] = sub;
}
}
}
}
public QueryParamCollection Form
{
get
{
if (form == null)
{
form = new WebROCollection();
files = new Dictionary<string, HttpPostedFile>();
if (IsContentType("multipart/form-data", true))
{
var task = LoadMultiPart();
Task.WaitAll(task);
}
else if (IsContentType("application/x-www-form-urlencoded", true))
{
var task = LoadWwwForm();
Task.WaitAll(task);
}
form.Protect();
}
#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;
ValidateNameValueCollection("Form", form);
}
return form;
}
}
public string Accept
{
get
{
return string.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"];
}
}
public string Authorization
{
get
{
return string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
}
}
protected bool validate_cookies, validate_query_string, validate_form;
protected bool checked_cookies, checked_query_string, checked_form;
static void ThrowValidationException(string name, string key, string value)
{
string v = "\"" + value + "\"";
if (v.Length > 20)
v = v.Substring(0, 16) + "...\"";
string msg = String.Format("A potentially dangerous Request.{0} value was " +
"detected from the client ({1}={2}).", name, key, v);
throw new Exception(msg);
}
static void ValidateNameValueCollection(string name, QueryParamCollection coll)
{
if (coll == null)
return;
foreach (var pair in coll)
{
var key = pair.Name;
var val = pair.Value;
if (val != null && val.Length > 0 && IsInvalidString(val))
ThrowValidationException(name, key, val);
}
}
internal static bool IsInvalidString(string val)
{
int validationFailureIndex;
return IsInvalidString(val, out validationFailureIndex);
}
internal static bool IsInvalidString(string val, out int validationFailureIndex)
{
validationFailureIndex = 0;
int len = val.Length;
if (len < 2)
return false;
char current = val[0];
for (int idx = 1; idx < len; idx++)
{
char next = val[idx];
// See http://secunia.com/advisories/14325
if (current == '<' || current == '\xff1c')
{
if (next == '!' || next < ' '
|| (next >= 'a' && next <= 'z')
|| (next >= 'A' && next <= 'Z'))
{
validationFailureIndex = idx - 1;
return true;
}
}
else if (current == '&' && next == '#')
{
validationFailureIndex = idx - 1;
return true;
}
current = next;
}
return false;
}
public void ValidateInput()
{
validate_cookies = true;
validate_query_string = true;
validate_form = true;
}
bool IsContentType(string ct, bool starts_with)
{
if (ct == null || ContentType == null) return false;
if (starts_with)
return StrUtils.StartsWith(ContentType, ct, true);
return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase);
}
async Task LoadWwwForm()
{
using (Stream input = GetSubStream(InputStream, _memoryStreamProvider))
{
using (var ms = _memoryStreamProvider.CreateNew())
{
await input.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
using (StreamReader s = new StreamReader(ms, ContentEncoding))
{
StringBuilder key = new StringBuilder();
StringBuilder value = new StringBuilder();
int c;
while ((c = s.Read()) != -1)
{
if (c == '=')
{
value.Length = 0;
while ((c = s.Read()) != -1)
{
if (c == '&')
{
AddRawKeyValue(key, value);
break;
}
else
value.Append((char)c);
}
if (c == -1)
{
AddRawKeyValue(key, value);
return;
}
}
else if (c == '&')
AddRawKeyValue(key, value);
else
key.Append((char)c);
}
if (c == -1)
AddRawKeyValue(key, value);
}
}
}
}
void AddRawKeyValue(StringBuilder key, StringBuilder value)
{
string decodedKey = WebUtility.UrlDecode(key.ToString());
form.Add(decodedKey,
WebUtility.UrlDecode(value.ToString()));
key.Length = 0;
value.Length = 0;
}
WebROCollection form;
Dictionary<string, HttpPostedFile> files;
class WebROCollection : QueryParamCollection
{
bool got_id;
int id;
public bool GotID
{
get { return got_id; }
}
public int ID
{
get { return id; }
set
{
got_id = true;
id = value;
}
}
public void Protect()
{
//IsReadOnly = true;
}
public void Unprotect()
{
//IsReadOnly = false;
}
public override string ToString()
{
StringBuilder result = new StringBuilder();
foreach (var pair in this)
{
if (result.Length > 0)
result.Append('&');
var key = pair.Name;
if (key != null && key.Length > 0)
{
result.Append(key);
result.Append('=');
}
result.Append(pair.Value);
}
return result.ToString();
}
}
public sealed class HttpPostedFile
{
string name;
string content_type;
Stream stream;
class ReadSubStream : Stream
{
Stream s;
long offset;
long end;
long position;
public ReadSubStream(Stream s, long offset, long length)
{
this.s = s;
this.offset = offset;
this.end = offset + length;
position = offset;
}
public override void Flush()
{
}
public override int Read(byte[] buffer, int dest_offset, int count)
{
if (buffer == null)
throw new ArgumentNullException("buffer");
if (dest_offset < 0)
throw new ArgumentOutOfRangeException("dest_offset", "< 0");
if (count < 0)
throw new ArgumentOutOfRangeException("count", "< 0");
int len = buffer.Length;
if (dest_offset > len)
throw new ArgumentException("destination offset is beyond array size");
// reordered to avoid possible integer overflow
if (dest_offset > len - count)
throw new ArgumentException("Reading would overrun buffer");
if (count > end - position)
count = (int)(end - position);
if (count <= 0)
return 0;
s.Position = position;
int result = s.Read(buffer, dest_offset, count);
if (result > 0)
position += result;
else
position = end;
return result;
}
public override int ReadByte()
{
if (position >= end)
return -1;
s.Position = position;
int result = s.ReadByte();
if (result < 0)
position = end;
else
position++;
return result;
}
public override long Seek(long d, SeekOrigin origin)
{
long real;
switch (origin)
{
case SeekOrigin.Begin:
real = offset + d;
break;
case SeekOrigin.End:
real = end + d;
break;
case SeekOrigin.Current:
real = position + d;
break;
default:
throw new ArgumentException();
}
long virt = real - offset;
if (virt < 0 || virt > Length)
throw new ArgumentException();
position = s.Seek(real, SeekOrigin.Begin);
return position;
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Length
{
get { return end - offset; }
}
public override long Position
{
get
{
return position - offset;
}
set
{
if (value > Length)
throw new ArgumentOutOfRangeException();
position = Seek(value, SeekOrigin.Begin);
}
}
}
internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length)
{
this.name = name;
this.content_type = content_type;
this.stream = new ReadSubStream(base_stream, offset, length);
}
public string ContentType
{
get
{
return content_type;
}
}
public int ContentLength
{
get
{
return (int)stream.Length;
}
}
public string FileName
{
get
{
return name;
}
}
public Stream InputStream
{
get
{
return stream;
}
}
}
class Helpers
{
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
}
internal sealed class StrUtils
{
public static bool StartsWith(string str1, string str2, bool ignore_case)
{
if (string.IsNullOrWhiteSpace(str1))
{
return false;
}
var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
return str1.IndexOf(str2, comparison) == 0;
}
public static bool EndsWith(string str1, string str2, bool ignore_case)
{
int l2 = str2.Length;
if (l2 == 0)
return true;
int l1 = str1.Length;
if (l2 > l1)
return false;
var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
return str1.IndexOf(str2, comparison) == str1.Length - str2.Length - 1;
}
}
class HttpMultipart
{
public class Element
{
public string ContentType;
public string Name;
public string Filename;
public Encoding Encoding;
public long Start;
public long Length;
public override string ToString()
{
return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
Start.ToString() + ", Length " + Length.ToString();
}
}
Stream data;
string boundary;
byte[] boundary_bytes;
byte[] buffer;
bool at_eof;
Encoding encoding;
StringBuilder sb;
const byte HYPHEN = (byte)'-', LF = (byte)'\n', CR = (byte)'\r';
// See RFC 2046
// In the case of multipart entities, in which one or more different
// sets of data are combined in a single body, a "multipart" media type
// field must appear in the entity's header. The body must then contain
// one or more body parts, each preceded by a boundary delimiter line,
// and the last one followed by a closing boundary delimiter line.
// After its boundary delimiter line, each body part then consists of a
// header area, a blank line, and a body area. Thus a body part is
// similar to an RFC 822 message in syntax, but different in meaning.
public HttpMultipart(Stream data, string b, Encoding encoding)
{
this.data = data;
//DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET
//var ms = new MemoryStream(32 * 1024);
//data.CopyTo(ms);
//this.data = ms;
boundary = b;
boundary_bytes = encoding.GetBytes(b);
buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--'
this.encoding = encoding;
sb = new StringBuilder();
}
string ReadLine()
{
// CRLF or LF are ok as line endings.
bool got_cr = false;
int b = 0;
sb.Length = 0;
while (true)
{
b = data.ReadByte();
if (b == -1)
{
return null;
}
if (b == LF)
{
break;
}
got_cr = b == CR;
sb.Append((char)b);
}
if (got_cr)
sb.Length--;
return sb.ToString();
}
static string GetContentDispositionAttribute(string l, string name)
{
int idx = l.IndexOf(name + "=\"");
if (idx < 0)
return null;
int begin = idx + name.Length + "=\"".Length;
int end = l.IndexOf('"', begin);
if (end < 0)
return null;
if (begin == end)
return "";
return l.Substring(begin, end - begin);
}
string GetContentDispositionAttributeWithEncoding(string l, string name)
{
int idx = l.IndexOf(name + "=\"");
if (idx < 0)
return null;
int begin = idx + name.Length + "=\"".Length;
int end = l.IndexOf('"', begin);
if (end < 0)
return null;
if (begin == end)
return "";
string temp = l.Substring(begin, end - begin);
byte[] source = new byte[temp.Length];
for (int i = temp.Length - 1; i >= 0; i--)
source[i] = (byte)temp[i];
return encoding.GetString(source, 0, source.Length);
}
bool ReadBoundary()
{
try
{
string line = ReadLine();
while (line == "")
line = ReadLine();
if (line[0] != '-' || line[1] != '-')
return false;
if (!StrUtils.EndsWith(line, boundary, false))
return true;
}
catch
{
}
return false;
}
string ReadHeaders()
{
string s = ReadLine();
if (s == "")
return null;
return s;
}
bool CompareBytes(byte[] orig, byte[] other)
{
for (int i = orig.Length - 1; i >= 0; i--)
if (orig[i] != other[i])
return false;
return true;
}
long MoveToNextBoundary()
{
long retval = 0;
bool got_cr = false;
int state = 0;
int c = data.ReadByte();
while (true)
{
if (c == -1)
return -1;
if (state == 0 && c == LF)
{
retval = data.Position - 1;
if (got_cr)
retval--;
state = 1;
c = data.ReadByte();
}
else if (state == 0)
{
got_cr = c == CR;
c = data.ReadByte();
}
else if (state == 1 && c == '-')
{
c = data.ReadByte();
if (c == -1)
return -1;
if (c != '-')
{
state = 0;
got_cr = false;
continue; // no ReadByte() here
}
int nread = data.Read(buffer, 0, buffer.Length);
int bl = buffer.Length;
if (nread != bl)
return -1;
if (!CompareBytes(boundary_bytes, buffer))
{
state = 0;
data.Position = retval + 2;
if (got_cr)
{
data.Position++;
got_cr = false;
}
c = data.ReadByte();
continue;
}
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
{
at_eof = true;
}
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
{
state = 0;
data.Position = retval + 2;
if (got_cr)
{
data.Position++;
got_cr = false;
}
c = data.ReadByte();
continue;
}
data.Position = retval + 2;
if (got_cr)
data.Position++;
break;
}
else
{
// state == 1
state = 0; // no ReadByte() here
}
}
return retval;
}
public Element ReadNextElement()
{
if (at_eof || ReadBoundary())
return null;
Element elem = new Element();
string header;
while ((header = ReadHeaders()) != null)
{
if (StrUtils.StartsWith(header, "Content-Disposition:", true))
{
elem.Name = GetContentDispositionAttribute(header, "name");
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
}
else if (StrUtils.StartsWith(header, "Content-Type:", true))
{
elem.ContentType = header.Substring("Content-Type:".Length).Trim();
elem.Encoding = GetEncoding(elem.ContentType);
}
}
long start = 0;
start = data.Position;
elem.Start = start;
long pos = MoveToNextBoundary();
if (pos == -1)
return null;
elem.Length = pos - start;
return elem;
}
static string StripPath(string path)
{
if (path == null || path.Length == 0)
return path;
if (path.IndexOf(":\\") != 1 && !path.StartsWith("\\\\"))
return path;
return path.Substring(path.LastIndexOf('\\') + 1);
}
}
}
}

View File

@@ -1,159 +0,0 @@
using MediaBrowser.Common.Events;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using WebSocketState = MediaBrowser.Model.Net.WebSocketState;
namespace Emby.Server.Implementations.HttpServer.SocketSharp
{
public class SharpWebSocket : IWebSocket
{
/// <summary>
/// The logger
/// </summary>
private readonly ILogger _logger;
public event EventHandler<EventArgs> Closed;
/// <summary>
/// Gets or sets the web socket.
/// </summary>
/// <value>The web socket.</value>
private SocketHttpListener.WebSocket WebSocket { get; set; }
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
{
if (socket == null)
{
throw new ArgumentNullException("socket");
}
if (logger == null)
{
throw new ArgumentNullException("logger");
}
_logger = logger;
WebSocket = socket;
socket.OnMessage += socket_OnMessage;
socket.OnClose += socket_OnClose;
socket.OnError += socket_OnError;
WebSocket.ConnectAsServer();
}
void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e)
{
_logger.Error("Error in SharpWebSocket: {0}", e.Message ?? string.Empty);
//EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
}
void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e)
{
EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
}
void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e)
{
//if (!string.IsNullOrWhiteSpace(e.Data))
//{
// if (OnReceive != null)
// {
// OnReceive(e.Data);
// }
// return;
//}
if (OnReceiveBytes != null)
{
OnReceiveBytes(e.RawData);
}
}
/// <summary>
/// Gets or sets the state.
/// </summary>
/// <value>The state.</value>
public WebSocketState State
{
get
{
WebSocketState commonState;
if (!Enum.TryParse(WebSocket.ReadyState.ToString(), true, out commonState))
{
_logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.ReadyState.ToString());
}
return commonState;
}
}
/// <summary>
/// Sends the async.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken)
{
return WebSocket.SendAsync(bytes);
}
/// <summary>
/// Sends the asynchronous.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken)
{
return WebSocket.SendAsync(text);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
WebSocket.OnMessage -= socket_OnMessage;
WebSocket.OnClose -= socket_OnClose;
WebSocket.OnError -= socket_OnError;
_cancellationTokenSource.Cancel();
WebSocket.Close();
}
}
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; }
/// <summary>
/// Gets or sets the on receive.
/// </summary>
/// <value>The on receive.</value>
public Action<string> OnReceive { get; set; }
}
}

View File

@@ -1,220 +0,0 @@
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using SocketHttpListener.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Text;
using SocketHttpListener.Primitives;
namespace Emby.Server.Implementations.HttpServer.SocketSharp
{
public class WebSocketSharpListener : IHttpListener
{
private HttpListener _listener;
private readonly ILogger _logger;
private readonly X509Certificate _certificate;
private readonly IMemoryStreamFactory _memoryStreamProvider;
private readonly ITextEncoding _textEncoding;
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, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment)
{
_logger = logger;
_certificate = certificate;
_memoryStreamProvider = memoryStreamProvider;
_textEncoding = textEncoding;
_networkManager = networkManager;
_socketFactory = socketFactory;
_cryptoProvider = cryptoProvider;
_enableDualMode = enableDualMode;
_fileSystem = fileSystem;
_environment = environment;
_disposeCancellationToken = _disposeCancellationTokenSource.Token;
}
public Action<Exception, IRequest, bool> 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, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem, _environment);
_listener.EnableDualMode = _enableDualMode;
if (_certificate != null)
{
_listener.LoadCert(_certificate);
}
foreach (var prefix in urlPrefixes)
{
_logger.Info("Adding HttpListener prefix " + prefix);
_listener.Prefixes.Add(prefix);
}
_listener.OnContext = ProcessContext;
_listener.Start();
}
private void ProcessContext(HttpListenerContext context)
{
//InitTask(context, _disposeCancellationToken);
Task.Run(() => InitTask(context, _disposeCancellationToken));
}
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
{
IHttpRequest httpReq = null;
var request = context.Request;
try
{
if (request.IsWebSocketRequest)
{
LoggerUtils.LogRequest(_logger, request);
ProcessWebSocketRequest(context);
return Task.FromResult(true);
}
httpReq = GetRequest(context);
}
catch (Exception ex)
{
_logger.ErrorException("Error processing request", ex);
httpReq = httpReq ?? GetRequest(context);
ErrorHandler(ex, httpReq, true);
return Task.FromResult(true);
}
var uri = request.Url;
return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken);
}
private void ProcessWebSocketRequest(HttpListenerContext ctx)
{
try
{
var endpoint = ctx.Request.RemoteEndPoint.ToString();
var url = ctx.Request.RawUrl;
var connectingArgs = new WebSocketConnectingEventArgs
{
Url = url,
QueryString = ctx.Request.QueryString,
Endpoint = endpoint
};
if (WebSocketConnecting != null)
{
WebSocketConnecting(connectingArgs);
}
if (connectingArgs.AllowConnection)
{
_logger.Debug("Web socket connection allowed");
var webSocketContext = ctx.AcceptWebSocket(null);
if (WebSocketConnected != null)
{
WebSocketConnected(new WebSocketConnectEventArgs
{
Url = url,
QueryString = ctx.Request.QueryString,
WebSocket = new SharpWebSocket(webSocketContext.WebSocket, _logger),
Endpoint = endpoint
});
}
}
else
{
_logger.Warn("Web socket connection not allowed");
ctx.Response.StatusCode = 401;
ctx.Response.Close();
}
}
catch (Exception ex)
{
_logger.ErrorException("AcceptWebSocketAsync error", ex);
ctx.Response.StatusCode = 500;
ctx.Response.Close();
}
}
private IHttpRequest GetRequest(HttpListenerContext httpContext)
{
var operationName = httpContext.Request.GetOperationName();
var req = new WebSocketSharpRequest(httpContext, operationName, _logger, _memoryStreamProvider);
return req;
}
public Task Stop()
{
_disposeCancellationTokenSource.Cancel();
if (_listener != null)
{
_listener.Close();
}
return Task.FromResult(true);
}
public void Dispose()
{
Dispose(true);
}
private bool _disposed;
private readonly object _disposeLock = new object();
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
lock (_disposeLock)
{
if (_disposed) return;
if (disposing)
{
Stop();
}
//release unmanaged resources here...
_disposed = true;
}
}
}
}

View File

@@ -1,611 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.HttpServer.SocketSharp;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Services;
using SocketHttpListener.Net;
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 Emby.Server.Implementations.HttpServer.SocketSharp
{
public partial class WebSocketSharpRequest : IHttpRequest
{
private readonly HttpListenerRequest request;
private readonly IHttpResponse response;
private readonly IMemoryStreamFactory _memoryStreamProvider;
public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, ILogger logger, IMemoryStreamFactory memoryStreamProvider)
{
this.OperationName = operationName;
_memoryStreamProvider = memoryStreamProvider;
this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
//HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
}
private static string GetHandlerPathIfAny(string listenerUrl)
{
if (listenerUrl == null) return null;
var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase);
if (pos == -1) return null;
var startHostUrl = listenerUrl.Substring(pos + "://".Length);
var endPos = startHostUrl.IndexOf('/');
if (endPos == -1) return null;
var endHostUrl = startHostUrl.Substring(endPos + 1);
return string.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/');
}
public HttpListenerRequest HttpRequest
{
get { return request; }
}
public object OriginalRequest
{
get { return request; }
}
public IResponse Response
{
get { return response; }
}
public IHttpResponse HttpResponse
{
get { return response; }
}
public string OperationName { get; set; }
public object Dto { get; set; }
public string RawUrl
{
get { return request.RawUrl; }
}
public string AbsoluteUri
{
get { return request.Url.AbsoluteUri.TrimEnd('/'); }
}
public string UserHostAddress
{
get { return request.UserHostAddress; }
}
public string XForwardedFor
{
get
{
return String.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
}
}
public int? XForwardedPort
{
get
{
return string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]);
}
}
public string XForwardedProtocol
{
get
{
return string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"];
}
}
public string XRealIp
{
get
{
return String.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"];
}
}
private string remoteIp;
public string RemoteIp
{
get
{
return remoteIp ??
(remoteIp = (CheckBadChars(XForwardedFor)) ??
(NormalizeIp(CheckBadChars(XRealIp)) ??
(request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null)));
}
}
private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 };
//
// CheckBadChars - throws on invalid chars to be not found in header name/value
//
internal static string CheckBadChars(string name)
{
if (name == null || name.Length == 0)
{
return name;
}
// VALUE check
//Trim spaces from both ends
name = name.Trim(HttpTrimCharacters);
//First, check for correctly formed multi-line value
//Second, check for absenece of CTL characters
int crlf = 0;
for (int i = 0; i < name.Length; ++i)
{
char c = (char)(0x000000ff & (uint)name[i]);
switch (crlf)
{
case 0:
if (c == '\r')
{
crlf = 1;
}
else if (c == '\n')
{
// Technically this is bad HTTP. But it would be a breaking change to throw here.
// Is there an exploit?
crlf = 2;
}
else if (c == 127 || (c < ' ' && c != '\t'))
{
throw new ArgumentException("net_WebHeaderInvalidControlChars");
}
break;
case 1:
if (c == '\n')
{
crlf = 2;
break;
}
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
case 2:
if (c == ' ' || c == '\t')
{
crlf = 0;
break;
}
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
}
if (crlf != 0)
{
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
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 string NormalizeIp(string ip)
{
if (!string.IsNullOrWhiteSpace(ip))
{
// Handle ipv4 mapped to ipv6
const string srch = "::ffff:";
var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
if (index == 0)
{
ip = ip.Substring(srch.Length);
}
}
return ip;
}
public bool IsSecureConnection
{
get { return request.IsSecureConnection || XForwardedProtocol == "https"; }
}
public string[] AcceptTypes
{
get { return request.AcceptTypes; }
}
private Dictionary<string, object> items;
public Dictionary<string, object> Items
{
get { return items ?? (items = new Dictionary<string, object>()); }
}
private string responseContentType;
public string ResponseContentType
{
get
{
return responseContentType
?? (responseContentType = GetResponseContentType(this));
}
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)
{
var specifiedContentType = GetQueryStringContentType(httpReq);
if (!string.IsNullOrEmpty(specifiedContentType)) return specifiedContentType;
var serverDefaultContentType = "application/json";
var acceptContentTypes = httpReq.AcceptTypes;
var defaultContentType = httpReq.ContentType;
if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData))
{
defaultContentType = serverDefaultContentType;
}
var preferredContentTypes = new string[] {};
var acceptsAnything = false;
var hasDefaultContentType = !string.IsNullOrEmpty(defaultContentType);
if (acceptContentTypes != null)
{
var hasPreferredContentTypes = new bool[preferredContentTypes.Length];
foreach (var acceptsType in acceptContentTypes)
{
var contentType = HttpResultFactory.GetRealContentType(acceptsType);
acceptsAnything = acceptsAnything || contentType == "*/*";
for (var i = 0; i < preferredContentTypes.Length; i++)
{
if (hasPreferredContentTypes[i]) continue;
var preferredContentType = preferredContentTypes[i];
hasPreferredContentTypes[i] = contentType.StartsWith(preferredContentType);
//Prefer Request.ContentType if it is also a preferredContentType
if (hasPreferredContentTypes[i] && preferredContentType == defaultContentType)
return preferredContentType;
}
}
for (var i = 0; i < preferredContentTypes.Length; i++)
{
if (hasPreferredContentTypes[i]) return preferredContentTypes[i];
}
if (acceptsAnything)
{
if (hasDefaultContentType)
return defaultContentType;
if (serverDefaultContentType != null)
return serverDefaultContentType;
}
}
if (acceptContentTypes == null && httpReq.ContentType == Soap11)
{
return Soap11;
}
//We could also send a '406 Not Acceptable', but this is allowed also
return serverDefaultContentType;
}
public const string Soap11 = "text/xml; charset=utf-8";
public static bool HasAnyOfContentTypes(IRequest request, params string[] contentTypes)
{
if (contentTypes == null || request.ContentType == null) return false;
foreach (var contentType in contentTypes)
{
if (IsContentType(request, contentType)) return true;
}
return false;
}
public static bool IsContentType(IRequest request, string contentType)
{
return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase);
}
public const string Xml = "application/xml";
private static string GetQueryStringContentType(IRequest httpReq)
{
var format = httpReq.QueryString["format"];
if (format == null)
{
const int formatMaxLength = 4;
var pi = httpReq.PathInfo;
if (pi == null || pi.Length <= formatMaxLength) return null;
if (pi[0] == '/') pi = pi.Substring(1);
format = LeftPart(pi, '/');
if (format.Length > formatMaxLength) return null;
}
format = LeftPart(format, '.').ToLower();
if (format.Contains("json")) return "application/json";
if (format.Contains("xml")) return Xml;
return null;
}
public static string LeftPart(string strVal, char needle)
{
if (strVal == null) return null;
var pos = strVal.IndexOf(needle);
return pos == -1
? strVal
: strVal.Substring(0, pos);
}
public static string HandlerFactoryPath;
private string pathInfo;
public string PathInfo
{
get
{
if (this.pathInfo == null)
{
var mode = HandlerFactoryPath;
var pos = request.RawUrl.IndexOf("?");
if (pos != -1)
{
var path = request.RawUrl.Substring(0, pos);
this.pathInfo = GetPathInfo(
path,
mode,
mode ?? "");
}
else
{
this.pathInfo = request.RawUrl;
}
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
this.pathInfo = NormalizePathInfo(pathInfo, mode);
}
return this.pathInfo;
}
}
private static string GetPathInfo(string fullPath, string mode, string appPath)
{
var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode);
if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
//Wildcard mode relies on this to work out the handlerPath
pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath);
if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
return fullPath;
}
private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot)
{
if (mappedPathRoot == null) return null;
var sbPathInfo = new StringBuilder();
var fullPathParts = fullPath.Split('/');
var mappedPathRootParts = mappedPathRoot.Split('/');
var fullPathIndexOffset = mappedPathRootParts.Length - 1;
var pathRootFound = false;
for (var fullPathIndex = 0; fullPathIndex < fullPathParts.Length; fullPathIndex++)
{
if (pathRootFound)
{
sbPathInfo.Append("/" + fullPathParts[fullPathIndex]);
}
else if (fullPathIndex - fullPathIndexOffset >= 0)
{
pathRootFound = true;
for (var mappedPathRootIndex = 0; mappedPathRootIndex < mappedPathRootParts.Length; mappedPathRootIndex++)
{
if (!string.Equals(fullPathParts[fullPathIndex - fullPathIndexOffset + mappedPathRootIndex], mappedPathRootParts[mappedPathRootIndex], StringComparison.OrdinalIgnoreCase))
{
pathRootFound = false;
break;
}
}
}
}
if (!pathRootFound) return null;
var path = sbPathInfo.ToString();
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);
}
}
return cookies;
}
}
public string UserAgent
{
get { return request.UserAgent; }
}
public QueryParamCollection Headers
{
get { return request.Headers; }
}
private QueryParamCollection queryString;
public QueryParamCollection QueryString
{
get { return queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query)); }
}
private QueryParamCollection formData;
public QueryParamCollection FormData
{
get { return formData ?? (formData = this.Form); }
}
public bool IsLocal
{
get { return request.IsLocal; }
}
private string httpMethod;
public string HttpMethod
{
get
{
return httpMethod
?? (httpMethod = request.HttpMethod);
}
}
public string Verb
{
get { return HttpMethod; }
}
public string ContentType
{
get { return request.ContentType; }
}
public Encoding contentEncoding;
public Encoding ContentEncoding
{
get { return contentEncoding ?? request.ContentEncoding; }
set { contentEncoding = value; }
}
public Uri UrlReferrer
{
get { return request.UrlReferrer; }
}
public static Encoding GetEncoding(string contentTypeHeader)
{
var param = GetParameter(contentTypeHeader, "charset=");
if (param == null) return null;
try
{
return Encoding.GetEncoding(param);
}
catch (ArgumentException)
{
return null;
}
}
public Stream InputStream
{
get { return request.InputStream; }
}
public long ContentLength
{
get { return request.ContentLength64; }
}
private IHttpFile[] httpFiles;
public IHttpFile[] Files
{
get
{
if (httpFiles == null)
{
if (files == null)
return httpFiles = new IHttpFile[0];
httpFiles = new IHttpFile[files.Count];
var i = 0;
foreach (var pair in files)
{
var reqFile = pair.Value;
httpFiles[i] = new HttpFile
{
ContentType = reqFile.ContentType,
ContentLength = reqFile.ContentLength,
FileName = reqFile.FileName,
InputStream = reqFile.InputStream,
};
i++;
}
}
return httpFiles;
}
}
static Stream GetSubStream(Stream stream, IMemoryStreamFactory streamProvider)
{
if (stream is MemoryStream)
{
var other = (MemoryStream)stream;
byte[] buffer;
if (streamProvider.TryGetBuffer(other, out buffer))
{
return streamProvider.CreateNew(buffer);
}
return streamProvider.CreateNew(other.ToArray());
}
return stream;
}
public static string NormalizePathInfo(string pathInfo, string handlerPath)
{
if (handlerPath != null && pathInfo.TrimStart('/').StartsWith(
handlerPath, StringComparison.OrdinalIgnoreCase))
{
return pathInfo.TrimStart('/').Substring(handlerPath.Length);
}
return pathInfo;
}
}
public class HttpFile : IHttpFile
{
public string Name { get; set; }
public string FileName { get; set; }
public long ContentLength { get; set; }
public string ContentType { get; set; }
public Stream InputStream { get; set; }
}
}

View File

@@ -1,203 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Services;
using SocketHttpListener.Net;
using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
using IRequest = MediaBrowser.Model.Services.IRequest;
namespace Emby.Server.Implementations.HttpServer.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
{
get { return _response; }
}
public int StatusCode
{
get { return this._response.StatusCode; }
set { this._response.StatusCode = value; }
}
public string StatusDescription
{
get { return this._response.StatusDescription; }
set { this._response.StatusDescription = value; }
}
public string ContentType
{
get { return _response.ContentType; }
set { _response.ContentType = value; }
}
//public ICookies Cookies { get; set; }
public void AddHeader(string name, string value)
{
if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase))
{
ContentType = value;
return;
}
_response.AddHeader(name, value);
}
public QueryParamCollection Headers
{
get
{
return _response.Headers;
}
}
public string GetHeader(string name)
{
return _response.Headers[name];
}
public void Redirect(string url)
{
_response.Redirect(url);
}
public Stream OutputStream
{
get { return _response.OutputStream; }
}
public void Close()
{
if (!this.IsClosed)
{
this.IsClosed = true;
try
{
CloseOutputStream(this._response);
}
catch (Exception ex)
{
_logger.ErrorException("Error closing HttpListener output stream", ex);
}
}
}
public void CloseOutputStream(HttpListenerResponse response)
{
try
{
var outputStream = response.OutputStream;
// This is needed with compression
outputStream.Flush();
outputStream.Dispose();
response.Close();
}
catch (Exception ex)
{
_logger.ErrorException("Error in HttpListenerResponseWrapper: " + ex.Message, ex);
}
}
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 static string AsHeaderValue(Cookie cookie)
{
var 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}");
}
//else if (restrictAllCookiesToDomain != null)
//{
// sb.Append($";domain={restrictAllCookiesToDomain}");
//}
if (cookie.Secure)
{
sb.Append(";Secure");
}
if (cookie.HttpOnly)
{
sb.Append(";HttpOnly");
}
return sb.ToString();
}
public bool SendChunked
{
get { return _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

@@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <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, ILogger logger)
public StreamWriter(byte[] source, string contentType, int contentLength, ILogger logger)
{
if (string.IsNullOrEmpty(contentType))
{
@@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.HttpServer
Headers["Content-Type"] = contentType;
Headers["Content-Length"] = source.Length.ToString(UsCulture);
Headers["Content-Length"] = contentLength.ToString(UsCulture);
}
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
@@ -106,10 +106,8 @@ namespace Emby.Server.Implementations.HttpServer
}
}
}
catch (Exception ex)
catch
{
Logger.ErrorException("Error streaming data", ex);
if (OnError != null)
{
OnError();

View File

@@ -0,0 +1,290 @@
using System.Text;
using MediaBrowser.Common.Events;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.Text;
using System.Net.WebSockets;
using Emby.Server.Implementations.Net;
namespace Emby.Server.Implementations.HttpServer
{
/// <summary>
/// Class WebSocketConnection
/// </summary>
public class WebSocketConnection : IWebSocketConnection
{
public event EventHandler<EventArgs> Closed;
/// <summary>
/// The _socket
/// </summary>
private readonly IWebSocket _socket;
/// <summary>
/// The _remote end point
/// </summary>
public string RemoteEndPoint { get; private set; }
/// <summary>
/// The logger
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// The _json serializer
/// </summary>
private readonly IJsonSerializer _jsonSerializer;
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
public Func<WebSocketMessageInfo, Task> OnReceive { get; set; }
/// <summary>
/// Gets the last activity date.
/// </summary>
/// <value>The last activity date.</value>
public DateTime LastActivityDate { get; private set; }
/// <summary>
/// Gets the id.
/// </summary>
/// <value>The id.</value>
public Guid Id { get; private set; }
/// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>The URL.</value>
public string Url { get; set; }
/// <summary>
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
public QueryParamCollection QueryString { get; set; }
private readonly ITextEncoding _textEncoding;
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
/// </summary>
/// <param name="socket">The socket.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">socket</exception>
public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger, ITextEncoding textEncoding)
{
if (socket == null)
{
throw new ArgumentNullException("socket");
}
if (string.IsNullOrEmpty(remoteEndPoint))
{
throw new ArgumentNullException("remoteEndPoint");
}
if (jsonSerializer == null)
{
throw new ArgumentNullException("jsonSerializer");
}
if (logger == null)
{
throw new ArgumentNullException("logger");
}
Id = Guid.NewGuid();
_jsonSerializer = jsonSerializer;
_socket = socket;
_socket.OnReceiveBytes = OnReceiveInternal;
var memorySocket = socket as IMemoryWebSocket;
if (memorySocket != null)
{
memorySocket.OnReceiveMemoryBytes = OnReceiveInternal;
}
RemoteEndPoint = remoteEndPoint;
_logger = logger;
_textEncoding = textEncoding;
socket.Closed += socket_Closed;
}
void socket_Closed(object sender, EventArgs e)
{
EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
}
/// <summary>
/// Called when [receive].
/// </summary>
/// <param name="bytes">The bytes.</param>
private void OnReceiveInternal(byte[] bytes)
{
LastActivityDate = DateTime.UtcNow;
if (OnReceive == null)
{
return;
}
var charset = _textEncoding.GetDetectedEncodingName(bytes, bytes.Length, null, false);
if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase))
{
OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length));
}
else
{
OnReceiveInternal(_textEncoding.GetASCIIEncoding().GetString(bytes, 0, bytes.Length));
}
}
/// <summary>
/// Called when [receive].
/// </summary>
/// <param name="bytes">The bytes.</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 = _textEncoding.GetDetectedEncodingName(bytes, bytes.Length, null, false);
if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase))
{
OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length));
}
else
{
OnReceiveInternal(_textEncoding.GetASCIIEncoding().GetString(bytes, 0, bytes.Length));
}
}
private void OnReceiveInternal(string message)
{
LastActivityDate = DateTime.UtcNow;
if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase))
{
// This info is useful sometimes but also clogs up the log
//_logger.Error("Received web socket message that is not a json structure: " + message);
return;
}
if (OnReceive == null)
{
return;
}
try
{
var stub = (WebSocketMessage<object>)_jsonSerializer.DeserializeFromString(message, typeof(WebSocketMessage<object>));
var info = new WebSocketMessageInfo
{
MessageType = stub.MessageType,
Data = stub.Data == null ? null : stub.Data.ToString(),
Connection = this
};
OnReceive(info);
}
catch (Exception ex)
{
_logger.ErrorException("Error processing web socket message", ex);
}
}
/// <summary>
/// Sends a message asynchronously.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="message">The message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">message</exception>
public Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
{
if (message == null)
{
throw new ArgumentNullException("message");
}
var json = _jsonSerializer.SerializeToString(message);
return SendAsync(json, cancellationToken);
}
/// <summary>
/// Sends a message asynchronously.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
cancellationToken.ThrowIfCancellationRequested();
return _socket.SendAsync(buffer, true, cancellationToken);
}
public Task SendAsync(string text, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(text))
{
throw new ArgumentNullException("text");
}
cancellationToken.ThrowIfCancellationRequested();
return _socket.SendAsync(text, true, cancellationToken);
}
/// <summary>
/// Gets the state.
/// </summary>
/// <value>The state.</value>
public WebSocketState State
{
get { return _socket.State; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
_socket.Dispose();
}
}
}
}