diff options
Diffstat (limited to 'Emby.Server.Implementations/HttpServer')
13 files changed, 218 insertions, 366 deletions
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 8cb7b5dbf..aa679e1b9 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -160,6 +160,9 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) { Logger.Info("Transmit file {0}", Path); + + //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; + await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); return; } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 05f78eba9..86df798d0 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -7,10 +7,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; using Emby.Server.Implementations.Services; using MediaBrowser.Common.Net; @@ -24,8 +24,6 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Text; -using SocketHttpListener.Net; -using SocketHttpListener.Primitives; namespace Emby.Server.Implementations.HttpServer { @@ -34,7 +32,7 @@ namespace Emby.Server.Implementations.HttpServer private string DefaultRedirectPath { get; set; } private readonly ILogger _logger; - public IEnumerable<string> UrlPrefixes { get; private set; } + public string[] UrlPrefixes { get; private set; } private readonly List<IService> _restServices = new List<IService>(); @@ -56,14 +54,13 @@ namespace Emby.Server.Implementations.HttpServer private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; - private readonly ICertificate _certificate; + private readonly X509Certificate _certificate; private readonly IEnvironmentInfo _environment; - private readonly IStreamFactory _streamFactory; private readonly Func<Type, Func<string, object>> _funcParseFn; private readonly bool _enableDualModeSockets; - public List<Action<IRequest, IResponse, object>> RequestFilters { get; set; } - public List<Action<IRequest, IResponse, object>> ResponseFilters { get; set; } + 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; } @@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer ILogger logger, IServerConfigurationManager config, string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) + 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) { Instance = this; @@ -87,7 +84,6 @@ namespace Emby.Server.Implementations.HttpServer _xmlSerializer = xmlSerializer; _environment = environment; _certificate = certificate; - _streamFactory = streamFactory; _funcParseFn = funcParseFn; _enableDualModeSockets = enableDualModeSockets; _fileSystem = fileSystem; @@ -95,8 +91,8 @@ namespace Emby.Server.Implementations.HttpServer _logger = logger; - RequestFilters = new List<Action<IRequest, IResponse, object>>(); - ResponseFilters = new List<Action<IRequest, IResponse, object>>(); + RequestFilters = new Action<IRequest, IResponse, object>[] { }; + ResponseFilters = new Action<IRequest, IResponse, object>[] { }; } public string GlobalResponse { get; set; } @@ -125,13 +121,6 @@ namespace Emby.Server.Implementations.HttpServer return _appHost.CreateInstance(type); } - private ServiceController CreateServiceController() - { - var types = _restServices.Select(r => r.GetType()).ToArray(); - - return new ServiceController(() => types); - } - /// <summary> /// Applies the request filters. Returns whether or not the request has been handled /// and no more processing should be done. @@ -142,7 +131,9 @@ namespace Emby.Server.Implementations.HttpServer //Exec all RequestFilter attributes with Priority < 0 var attributes = GetRequestFilterAttributes(requestDto.GetType()); var i = 0; - for (; i < attributes.Length && attributes[i].Priority < 0; i++) + var count = attributes.Count; + + for (; i < count && attributes[i].Priority < 0; i++) { var attribute = attributes[i]; attribute.RequestFilter(req, res, requestDto); @@ -155,7 +146,7 @@ namespace Emby.Server.Implementations.HttpServer } //Exec remaining RequestFilter attributes with Priority >= 0 - for (; i < attributes.Length && attributes[i].Priority >= 0; i++) + for (; i < count && attributes[i].Priority >= 0; i++) { var attribute = attributes[i]; attribute.RequestFilter(req, res, requestDto); @@ -169,12 +160,12 @@ namespace Emby.Server.Implementations.HttpServer return serviceType; } - public void AddServiceInfo(Type serviceType, Type requestType, Type responseType) + public void AddServiceInfo(Type serviceType, Type requestType) { ServiceOperationsMap[requestType] = serviceType; } - private IHasRequestFilter[] GetRequestFilterAttributes(Type requestDtoType) + private List<IHasRequestFilter> GetRequestFilterAttributes(Type requestDtoType) { var attributes = requestDtoType.GetTypeInfo().GetCustomAttributes(true).OfType<IHasRequestFilter>().ToList(); @@ -186,40 +177,13 @@ namespace Emby.Server.Implementations.HttpServer attributes.Sort((x, y) => x.Priority - y.Priority); - return attributes.ToArray(); - } - - /// <summary> - /// Starts the Web Service - /// </summary> - private void StartListener() - { - WebSocketSharpRequest.HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes.First()); - - _listener = GetListener(); - - _listener.WebSocketConnected = OnWebSocketConnected; - _listener.WebSocketConnecting = OnWebSocketConnecting; - _listener.ErrorHandler = ErrorHandler; - _listener.RequestHandler = RequestHandler; - - _listener.Start(UrlPrefixes); - } - - public 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('/'); + return attributes; } private IHttpListener GetListener() { + //return new KestrelHost.KestrelListener(_logger, _environment, _fileSystem); + return new WebSocketSharpListener(_logger, _certificate, _memoryStreamProvider, @@ -227,22 +191,11 @@ namespace Emby.Server.Implementations.HttpServer _networkManager, _socketFactory, _cryptoProvider, - _streamFactory, _enableDualModeSockets, - GetRequest, _fileSystem, _environment); } - private IHttpRequest GetRequest(HttpListenerContext httpContext) - { - var operationName = httpContext.Request.GetOperationName(); - - var req = new WebSocketSharpRequest(httpContext, operationName, _logger, _memoryStreamProvider); - - return req; - } - private void OnWebSocketConnecting(WebSocketConnectingEventArgs args) { if (_disposed) @@ -355,7 +308,8 @@ namespace Emby.Server.Implementations.HttpServer if (_listener != null) { _logger.Info("Stopping HttpListener..."); - _listener.Stop(); + var task = _listener.Stop(); + Task.WaitAll(task); _logger.Info("HttpListener stopped"); } } @@ -441,7 +395,7 @@ namespace Emby.Server.Implementations.HttpServer return address.Trim('/'); } - private bool ValidateHost(Uri url) + private bool ValidateHost(string host) { var hosts = _config .Configuration @@ -454,7 +408,7 @@ namespace Emby.Server.Implementations.HttpServer return true; } - var host = url.Host ?? string.Empty; + host = host ?? string.Empty; _logger.Debug("Validating host {0}", host); @@ -472,7 +426,7 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Overridable method that can be used to implement a custom hnandler /// </summary> - protected async Task RequestHandler(IHttpRequest httpReq, Uri url, CancellationToken cancellationToken) + protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { var date = DateTime.Now; var httpRes = httpReq.Response; @@ -491,7 +445,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (!ValidateHost(url)) + if (!ValidateHost(host)) { httpRes.StatusCode = 400; httpRes.ContentType = "text/plain"; @@ -511,9 +465,7 @@ namespace Emby.Server.Implementations.HttpServer } var operationName = httpReq.OperationName; - var localPath = url.LocalPath; - var urlString = url.OriginalString; enableLog = EnableLogging(urlString, localPath); urlToLog = urlString; logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1; @@ -697,19 +649,26 @@ namespace Emby.Server.Implementations.HttpServer { _restServices.AddRange(services); - ServiceController = CreateServiceController(); + ServiceController = new ServiceController(); _logger.Info("Calling ServiceStack AppHost.Init"); - ServiceController.Init(this); + var types = _restServices.Select(r => r.GetType()).ToArray(); + + ServiceController.Init(this, types); - var requestFilters = _appHost.GetExports<IRequestFilter>().ToList(); - foreach (var filter in requestFilters) + var list = new List<Action<IRequest, IResponse, object>>(); + foreach (var filter in _appHost.GetExports<IRequestFilter>()) { - RequestFilters.Add(filter.Filter); + list.Add(filter.Filter); } - ResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); + RequestFilters = list.ToArray(); + + ResponseFilters = new Action<IRequest, IResponse, object>[] + { + new ResponseFilter(_logger).FilterResponse + }; } public RouteAttribute[] GetRouteAttributes(Type requestType) @@ -726,22 +685,22 @@ namespace Emby.Server.Implementations.HttpServer Summary = route.Summary }); - routes.Add(new RouteAttribute(NormalizeRoutePath(route.Path), route.Verbs) + routes.Add(new RouteAttribute(NormalizeMediaBrowserRoutePath(route.Path), route.Verbs) { Notes = route.Notes, Priority = route.Priority, Summary = route.Summary }); - routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs) - { - Notes = route.Notes, - Priority = route.Priority, - Summary = route.Summary - }); + //routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs) + //{ + // Notes = route.Notes, + // Priority = route.Priority, + // Summary = route.Summary + //}); } - return routes.ToArray(); + return routes.ToArray(routes.Count); } public Func<string, object> GetParseFn(Type propertyType) @@ -779,24 +738,24 @@ namespace Emby.Server.Implementations.HttpServer return "emby/" + path; } - private string DoubleNormalizeEmbyRoutePath(string path) + private string NormalizeMediaBrowserRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) { - return "/emby/emby" + path; + return "/mediabrowser" + path; } - return "emby/emby/" + path; + return "mediabrowser/" + path; } - private string NormalizeRoutePath(string path) + private string DoubleNormalizeEmbyRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) { - return "/mediabrowser" + path; + return "/emby/emby" + path; } - return "mediabrowser/" + path; + return "emby/emby/" + path; } private bool _disposed; @@ -824,10 +783,18 @@ namespace Emby.Server.Implementations.HttpServer GC.SuppressFinalize(this); } - public void StartServer(IEnumerable<string> urlPrefixes) + public void StartServer(string[] urlPrefixes) { - UrlPrefixes = urlPrefixes.ToList(); - StartListener(); + UrlPrefixes = urlPrefixes; + + _listener = GetListener(); + + _listener.WebSocketConnected = OnWebSocketConnected; + _listener.WebSocketConnecting = OnWebSocketConnecting; + _listener.ErrorHandler = ErrorHandler; + _listener.RequestHandler = RequestHandler; + + _listener.Start(UrlPrefixes); } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 396bd8e88..f5a1fe246 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -6,19 +6,16 @@ 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; using System.Threading.Tasks; using System.Xml; -using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.Services; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using IRequest = MediaBrowser.Model.Services.IRequest; using MimeTypes = MediaBrowser.Model.Net.MimeTypes; -using StreamWriter = Emby.Server.Implementations.HttpServer.StreamWriter; namespace Emby.Server.Implementations.HttpServer { @@ -193,50 +190,37 @@ namespace Emby.Server.Implementations.HttpServer /// <returns></returns> public object ToOptimizedResult<T>(IRequest request, T dto) { - var compressionType = GetCompressionType(request); - if (compressionType == null) - { - var contentType = request.ResponseContentType; + var contentType = request.ResponseContentType; - switch (GetRealContentType(contentType)) - { - case "application/xml": - case "text/xml": - case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml - return SerializeToXmlString(dto); - - case "application/json": - case "text/json": - return _jsonSerializer.SerializeToString(dto); - } - } - - // Do not use the memoryStreamFactory here, they don't place nice with compression - using (var ms = new MemoryStream()) + switch (GetRealContentType(contentType)) { - var contentType = request.ResponseContentType; - var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType); + case "application/xml": + case "text/xml": + case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml + return SerializeToXmlString(dto); - writerFn(dto, ms); + case "application/json": + case "text/json": + return _jsonSerializer.SerializeToString(dto); + default: + { + var ms = new MemoryStream(); + var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType); - ms.Position = 0; + writerFn(dto, ms); + + ms.Position = 0; - var responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + if (string.Equals(request.Verb, "head", StringComparison.OrdinalIgnoreCase)) + { + return GetHttpResult(new byte[] { }, contentType, true); + } - return GetCompressedResult(ms, compressionType, responseHeaders, false, request.ResponseContentType).Result; + return GetHttpResult(ms, contentType, true); + } } } - private static Stream GetCompressionStream(Stream outputStream, string compressionType) - { - if (compressionType == "deflate") - return new DeflateStream(outputStream, CompressionMode.Compress, true); - if (compressionType == "gzip") - return new GZipStream(outputStream, CompressionMode.Compress, true); - - throw new NotSupportedException(compressionType); - } - public static string GetRealContentType(string contentType) { return contentType == null @@ -440,11 +424,14 @@ namespace Emby.Server.Implementations.HttpServer options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - // Quotes are valid in linux. They'll possibly cause issues here - var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); - if (!string.IsNullOrWhiteSpace(filename)) + if (!options.ResponseHeaders.ContainsKey("Content-Disposition")) { - options.ResponseHeaders["Content-Disposition"] = "inline; filename=\"" + filename + "\""; + // Quotes are valid in linux. They'll possibly cause issues here + var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); + if (!string.IsNullOrWhiteSpace(filename)) + { + options.ResponseHeaders["Content-Disposition"] = "inline; filename=\"" + filename + "\""; + } } return GetStaticResult(requestContext, options); @@ -503,190 +490,67 @@ namespace Emby.Server.Implementations.HttpServer return result; } - var compress = ShouldCompressResponse(requestContext, contentType); - var hasHeaders = await GetStaticResult(requestContext, options, compress).ConfigureAwait(false); - AddResponseHeaders(hasHeaders, options.ResponseHeaders); - - return hasHeaders; - } - - /// <summary> - /// Shoulds the compress response. - /// </summary> - /// <param name="requestContext">The request context.</param> - /// <param name="contentType">Type of the content.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool ShouldCompressResponse(IRequest requestContext, string contentType) - { - // It will take some work to support compression with byte range requests - if (!string.IsNullOrWhiteSpace(requestContext.Headers.Get("Range"))) - { - return false; - } - - // Don't compress media - if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Don't compress images - if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(contentType, "application/x-javascript", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (string.Equals(contentType, "application/xml", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - return false; - } - - return true; - } - - /// <summary> - /// The us culture - /// </summary> - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private async Task<IHasHeaders> GetStaticResult(IRequest requestContext, StaticResultOptions options, bool compress) - { var isHeadRequest = options.IsHeadRequest; var factoryFn = options.ContentFactory; - var contentType = options.ContentType; var responseHeaders = options.ResponseHeaders; - var requestedCompressionType = GetCompressionType(requestContext); - - if (!compress || string.IsNullOrEmpty(requestedCompressionType)) - { - var rangeHeader = requestContext.Headers.Get("Range"); - - if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path)) - { - return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) - { - OnComplete = options.OnComplete, - OnError = options.OnError, - FileShare = options.FileShare - }; - } + //var requestedCompressionType = GetCompressionType(requestContext); - if (!string.IsNullOrWhiteSpace(rangeHeader)) - { - var stream = await factoryFn().ConfigureAwait(false); + var rangeHeader = requestContext.Headers.Get("Range"); - return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) - { - OnComplete = options.OnComplete - }; - } - else + if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path)) + { + var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) { - var stream = await factoryFn().ConfigureAwait(false); - - responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); - - if (isHeadRequest) - { - stream.Dispose(); - - return GetHttpResult(new byte[] { }, contentType, true); - } + OnComplete = options.OnComplete, + OnError = options.OnError, + FileShare = options.FileShare + }; - return new StreamWriter(stream, contentType, _logger) - { - OnComplete = options.OnComplete, - OnError = options.OnError - }; - } + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + return hasHeaders; } - using (var stream = await factoryFn().ConfigureAwait(false)) + if (!string.IsNullOrWhiteSpace(rangeHeader)) { - return await GetCompressedResult(stream, requestedCompressionType, responseHeaders, isHeadRequest, contentType).ConfigureAwait(false); - } - } + var stream = await factoryFn().ConfigureAwait(false); - private async Task<IHasHeaders> GetCompressedResult(Stream stream, - string requestedCompressionType, - IDictionary<string, string> responseHeaders, - bool isHeadRequest, - string contentType) - { - using (var reader = new MemoryStream()) - { - await stream.CopyToAsync(reader).ConfigureAwait(false); - - reader.Position = 0; - var content = reader.ToArray(); - - if (content.Length >= 1024) + var hasHeaders = new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) { - content = Compress(content, requestedCompressionType); - responseHeaders["Content-Encoding"] = requestedCompressionType; - } + OnComplete = options.OnComplete + }; - responseHeaders["Vary"] = "Accept-Encoding"; - responseHeaders["Content-Length"] = content.Length.ToString(UsCulture); + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + return hasHeaders; + } + else + { + var stream = await factoryFn().ConfigureAwait(false); + + responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); if (isHeadRequest) { + stream.Dispose(); + return GetHttpResult(new byte[] { }, contentType, true); } - return GetHttpResult(content, contentType, true, responseHeaders); - } - } - - private byte[] Compress(byte[] bytes, string compressionType) - { - if (compressionType == "deflate") - return Deflate(bytes); - - if (compressionType == "gzip") - return GZip(bytes); - - throw new NotSupportedException(compressionType); - } - - 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(); + var hasHeaders = new StreamWriter(stream, contentType, _logger) + { + OnComplete = options.OnComplete, + OnError = options.OnError + }; - return ms.ToArray(); + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + return hasHeaders; } } - 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(); - } - } + /// <summary> + /// The us culture + /// </summary> + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); /// <summary> /// Adds the caching responseHeaders. diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 82175dbed..9feb2311d 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer /// Gets or sets the request handler. /// </summary> /// <value>The request handler.</value> - Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; } + Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; } /// <summary> /// Gets or sets the web socket handler. @@ -42,6 +42,6 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Stops this instance. /// </summary> - void Stop(); + Task Stop(); } } diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs index f0e75eea4..46bb4c7f9 100644 --- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs @@ -1,7 +1,6 @@ using MediaBrowser.Model.Logging; using System; using System.Globalization; -using System.Linq; using MediaBrowser.Model.Services; using SocketHttpListener.Net; @@ -29,7 +28,20 @@ namespace Emby.Server.Implementations.HttpServer } else { - var headerText = string.Join(", ", headers.Select(i => i.Name + "=" + i.Value).ToArray()); + var headerText = string.Empty; + var index = 0; + + foreach (var i in headers) + { + if (index > 0) + { + headerText += ", "; + } + + headerText += i.Name + "=" + i.Value; + + index++; + } logger.Info("HTTP {0} {1}. {2}", method, url, headerText); } @@ -48,7 +60,8 @@ namespace Emby.Server.Implementations.HttpServer var durationMs = duration.TotalMilliseconds; var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; - var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); + //var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); + var headerText = string.Empty; logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText); } } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 4d00c9b19..fadab4482 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -7,8 +7,8 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using System; -using System.Collections.Generic; using System.Linq; +using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.HttpServer.Security { @@ -38,19 +38,19 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> public string HtmlRedirect { get; set; } - public void Authenticate(IServiceRequest request, + public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues) { ValidateUser(request, authAttribtues); } - private void ValidateUser(IServiceRequest request, + private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(request); - if (!IsExemptFromAuthenticationToken(auth, authAttribtues)) + if (!IsExemptFromAuthenticationToken(auth, authAttribtues, request)) { var valid = IsValidConnectKey(auth.Token); @@ -76,9 +76,9 @@ namespace Emby.Server.Implementations.HttpServer.Security var info = GetTokenInfo(request); - if (!IsExemptFromRoles(auth, authAttribtues, info)) + if (!IsExemptFromRoles(auth, authAttribtues, request, info)) { - var roles = authAttribtues.GetRoles().ToList(); + var roles = authAttribtues.GetRoles(); ValidateRoles(roles, user); } @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private void ValidateUserAccess(User user, IServiceRequest request, + private void ValidateUserAccess(User user, IRequest request, IAuthenticationAttributes authAttribtues, AuthorizationInfo auth) { @@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.HttpServer.Security !authAttribtues.EscapeParentalControl && !user.IsParentalScheduleAllowed()) { - request.AddResponseHeader("X-Application-Error-Code", "ParentalControl"); + request.Response.AddHeader("X-Application-Error-Code", "ParentalControl"); throw new SecurityException("This user account is not allowed access at this time.") { @@ -132,23 +132,33 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues) + private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request) { if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard) { return true; } + if (authAttribtues.AllowLocal && request.IsLocal) + { + return true; + } + return false; } - private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, AuthenticationInfo tokenInfo) + private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request, AuthenticationInfo tokenInfo) { if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard) { return true; } + if (authAttribtues.AllowLocal && request.IsLocal) + { + return true; + } + if (string.IsNullOrWhiteSpace(auth.Token)) { return true; @@ -162,7 +172,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return false; } - private void ValidateRoles(List<string> roles, User user) + private void ValidateRoles(string[] roles, User user) { if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase)) { @@ -196,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private AuthenticationInfo GetTokenInfo(IServiceRequest request) + private AuthenticationInfo GetTokenInfo(IRequest request) { object info; request.Items.TryGetValue("OriginalAuthenticationInfo", out info); @@ -213,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return ConnectManager.IsAuthorizationTokenValid(token); } - private void ValidateSecurityToken(IServiceRequest request, string token) + private void ValidateSecurityToken(IRequest request, string token) { if (string.IsNullOrWhiteSpace(token)) { diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index ede85fb67..c9d5ed007 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -3,8 +3,8 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using System; using System.Collections.Generic; -using System.Linq; using MediaBrowser.Model.Services; +using System.Linq; namespace Emby.Server.Implementations.HttpServer.Security { @@ -21,11 +21,10 @@ namespace Emby.Server.Implementations.HttpServer.Security public AuthorizationInfo GetAuthorizationInfo(object requestContext) { - var req = new ServiceRequest((IRequest)requestContext); - return GetAuthorizationInfo(req); + return GetAuthorizationInfo((IRequest)requestContext); } - public AuthorizationInfo GetAuthorizationInfo(IServiceRequest requestContext) + public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext) { object cached; if (requestContext.Items.TryGetValue("AuthorizationInfo", out cached)) @@ -41,7 +40,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private AuthorizationInfo GetAuthorization(IServiceRequest httpReq) + private AuthorizationInfo GetAuthorization(IRequest httpReq) { var auth = GetAuthorizationDictionary(httpReq); @@ -90,7 +89,7 @@ namespace Emby.Server.Implementations.HttpServer.Security AccessToken = token }); - var tokenInfo = result.Items.FirstOrDefault(); + var tokenInfo = result.Items.Length > 0 ? result.Items[0] : null; if (tokenInfo != null) { @@ -135,7 +134,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(IServiceRequest httpReq) + private Dictionary<string, string> GetAuthorizationDictionary(IRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -161,7 +160,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // There should be at least to parts if (parts.Length != 2) return null; - var acceptedNames = new[] { "MediaBrowser", "Emby"}; + var acceptedNames = new[] { "MediaBrowser", "Emby" }; // It has to be a digest request if (!acceptedNames.Contains(parts[0] ?? string.Empty, StringComparer.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 33dd4e2d7..dd5d64bf6 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.HttpServer.Security _sessionManager = sessionManager; } - public Task<SessionInfo> GetSession(IServiceRequest requestContext) + public Task<SessionInfo> GetSession(IRequest requestContext) { var authorization = _authContext.GetAuthorizationInfo(requestContext); @@ -38,7 +38,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.RemoteIp, user); } - private AuthenticationInfo GetTokenInfo(IServiceRequest request) + private AuthenticationInfo GetTokenInfo(IRequest request) { object info; request.Items.TryGetValue("OriginalAuthenticationInfo", out info); @@ -47,11 +47,10 @@ namespace Emby.Server.Implementations.HttpServer.Security public Task<SessionInfo> GetSession(object requestContext) { - var req = new ServiceRequest((IRequest)requestContext); - return GetSession(req); + return GetSession((IRequest)requestContext); } - public async Task<User> GetUser(IServiceRequest requestContext) + public async Task<User> GetUser(IRequest requestContext) { var session = await GetSession(requestContext).ConfigureAwait(false); @@ -60,8 +59,7 @@ namespace Emby.Server.Implementations.HttpServer.Security public Task<User> GetUser(object requestContext) { - var req = new ServiceRequest((IRequest)requestContext); - return GetUser(req); + return GetUser((IRequest)requestContext); } } } diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs index 4fbe0ed94..4e8dd7362 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Text; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.HttpServer.SocketSharp { @@ -585,7 +586,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp WriteCharBytes(bytes, ch, e); } - byte[] buf = bytes.ToArray(); + byte[] buf = bytes.ToArray(bytes.Count); bytes = null; return e.GetString(buf, 0, buf.Length); diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs index 9823a2ff5..cc7a4557e 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs @@ -123,6 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index e648838b2..10aaa4032 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -4,6 +4,7 @@ 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; @@ -22,22 +23,20 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private HttpListener _listener; private readonly ILogger _logger; - private readonly ICertificate _certificate; + 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 IStreamFactory _streamFactory; private readonly IFileSystem _fileSystem; - private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory; private readonly bool _enableDualMode; private readonly IEnvironmentInfo _environment; private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationToken _disposeCancellationToken; - public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem, IEnvironmentInfo environment) + 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; @@ -46,9 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _networkManager = networkManager; _socketFactory = socketFactory; _cryptoProvider = cryptoProvider; - _streamFactory = streamFactory; _enableDualMode = enableDualMode; - _httpRequestFactory = httpRequestFactory; _fileSystem = fileSystem; _environment = environment; @@ -56,7 +53,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } public Action<Exception, IRequest, bool> ErrorHandler { get; set; } - public Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; } + public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; } public Action<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; } @@ -65,7 +62,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Start(IEnumerable<string> urlPrefixes) { if (_listener == null) - _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem, _environment); + _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem, _environment); _listener.EnableDualMode = _enableDualMode; @@ -117,7 +114,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return Task.FromResult(true); } - return RequestHandler(httpReq, request.Url, cancellationToken); + var uri = request.Url; + + return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); } private void ProcessWebSocketRequest(HttpListenerContext ctx) @@ -173,10 +172,14 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private IHttpRequest GetRequest(HttpListenerContext httpContext) { - return _httpRequestFactory(httpContext); + var operationName = httpContext.Request.GetOperationName(); + + var req = new WebSocketSharpRequest(httpContext, operationName, _logger, _memoryStreamProvider); + + return req; } - public void Stop() + public Task Stop() { _disposeCancellationTokenSource.Cancel(); @@ -189,6 +192,8 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _listener.Close(); } + + return Task.FromResult(true); } public void Dispose() diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs index 2dfe6a9e3..522377f0c 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -27,6 +27,20 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _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 @@ -108,7 +122,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return remoteIp ?? (remoteIp = (CheckBadChars(XForwardedFor)) ?? (NormalizeIp(CheckBadChars(XRealIp)) ?? - (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.IpAddress.ToString()) : null))); + (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); } } @@ -232,13 +246,12 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp set { this.responseContentType = value; - HasExplicitResponseContentType = true; } } public const string FormUrlEncoded = "application/x-www-form-urlencoded"; public const string MultiPartFormData = "multipart/form-data"; - private static string GetResponseContentType(IRequest httpReq) + public static string GetResponseContentType(IRequest httpReq) { var specifiedContentType = GetQueryStringContentType(httpReq); if (!string.IsNullOrEmpty(specifiedContentType)) return specifiedContentType; @@ -346,8 +359,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp : strVal.Substring(0, pos); } - public bool HasExplicitResponseContentType { get; private set; } - public static string HandlerFactoryPath; private string pathInfo; @@ -490,13 +501,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp get { return HttpMethod; } } - public string Param(string name) - { - return Headers[name] - ?? QueryString[name] - ?? FormData[name]; - } - public string ContentType { get { return request.ContentType; } @@ -584,18 +588,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return stream; } - public 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 static string NormalizePathInfo(string pathInfo, string handlerPath) { if (handlerPath != null && pathInfo.TrimStart('/').StartsWith( diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index d6762d94b..5b51c0cf1 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -29,7 +29,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } public IRequest Request { get; private set; } - public bool UseBufferedStream { get; set; } public Dictionary<string, object> Items { get; private set; } public object OriginalResponse { |
