diff options
Diffstat (limited to 'MediaBrowser.Server.Implementations/HttpServer')
13 files changed, 408 insertions, 428 deletions
diff --git a/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs new file mode 100644 index 000000000..5aa01c706 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using ServiceStack; +using ServiceStack.Web; + +namespace MediaBrowser.Server.Implementations.HttpServer +{ + public class AsyncStreamWriterFunc : IStreamWriter, IAsyncStreamWriter, IHasOptions + { + /// <summary> + /// Gets or sets the source stream. + /// </summary> + /// <value>The source stream.</value> + private Func<Stream, Task> Writer { get; set; } + + /// <summary> + /// Gets the options. + /// </summary> + /// <value>The options.</value> + public IDictionary<string, string> Options { get; private set; } + + public Action OnComplete { get; set; } + public Action OnError { get; set; } + + /// <summary> + /// Initializes a new instance of the <see cref="StreamWriter" /> class. + /// </summary> + public AsyncStreamWriterFunc(Func<Stream, Task> writer, IDictionary<string, string> headers) + { + Writer = writer; + + if (headers == null) + { + headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + } + Options = headers; + } + + /// <summary> + /// Writes to. + /// </summary> + /// <param name="responseStream">The response stream.</param> + public void WriteTo(Stream responseStream) + { + var task = Writer(responseStream); + Task.WaitAll(task); + } + + public async Task WriteToAsync(Stream responseStream) + { + await Writer(responseStream).ConfigureAwait(false); + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 3e4f4a70c..17e4793cb 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -106,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer } }); - HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger, () => _config.Configuration.DenyIFrameEmbedding).FilterResponse); + HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); } public override void OnAfterInit() @@ -179,6 +179,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer private void OnWebSocketConnecting(WebSocketConnectingEventArgs args) { + if (_disposed) + { + return; + } + if (WebSocketConnecting != null) { WebSocketConnecting(this, args); @@ -187,6 +192,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer private void OnWebSocketConnected(WebSocketConnectEventArgs args) { + if (_disposed) + { + return; + } + if (WebSocketConnected != null) { WebSocketConnected(this, args); @@ -325,12 +335,19 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="httpReq">The HTTP req.</param> /// <param name="url">The URL.</param> /// <returns>Task.</returns> - protected Task RequestHandler(IHttpRequest httpReq, Uri url) + protected async Task RequestHandler(IHttpRequest httpReq, Uri url) { var date = DateTime.Now; var httpRes = httpReq.Response; + if (_disposed) + { + httpRes.StatusCode = 503; + httpRes.Close(); + return ; + } + var operationName = httpReq.OperationName; var localPath = url.LocalPath; @@ -344,6 +361,19 @@ namespace MediaBrowser.Server.Implementations.HttpServer LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent); } + if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) || + string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) + { + httpRes.RedirectToUrl(DefaultRedirectPath); + return; + } + if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) || + string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) + { + httpRes.RedirectToUrl("emby/" + DefaultRedirectPath); + return; + } + if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase) || localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1 || @@ -359,45 +389,35 @@ namespace MediaBrowser.Server.Implementations.HttpServer httpRes.Write("<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>"); httpRes.Close(); - return Task.FromResult(true); + return; } } - if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase)) - { - httpRes.RedirectToUrl(DefaultRedirectPath); - return Task.FromResult(true); - } - if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase)) - { - httpRes.RedirectToUrl("emby/" + DefaultRedirectPath); - return Task.FromResult(true); - } if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl(DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl("../" + DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl(DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.IsNullOrEmpty(localPath)) { httpRes.RedirectToUrl("/" + DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl("web/pin.html"); - return Task.FromResult(true); + return; } if (!string.IsNullOrWhiteSpace(GlobalResponse)) @@ -407,7 +427,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer httpRes.Write(GlobalResponse); httpRes.Close(); - return Task.FromResult(true); + return; } var handler = HttpHandlerFactory.GetHandler(httpReq); @@ -423,13 +443,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName(); } - var task = serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName); - - task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); - //Matches Exceptions handled in HttpListenerBase.InitTask() - - task.ContinueWith(x => + try + { + await serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false); + } + finally { + httpRes.Close(); var statusCode = httpRes.StatusCode; var duration = DateTime.Now - date; @@ -438,13 +458,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer { LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration); } - - }, TaskContinuationOptions.None); - return task; + } } - return new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo) - .AsTaskException(); + throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 6cedaa6a9..c0a2a5eb3 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -294,7 +294,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer return null; } - public object GetStaticFileResult(IRequest requestContext, + public Task<object> GetStaticFileResult(IRequest requestContext, string path, FileShare fileShare = FileShare.Read) { @@ -310,7 +310,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer }); } - public object GetStaticFileResult(IRequest requestContext, + public Task<object> GetStaticFileResult(IRequest requestContext, StaticFileResultOptions options) { var path = options.Path; @@ -331,7 +331,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer options.ContentType = MimeTypes.GetMimeType(path); } - options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path); + if (!options.DateLastModified.HasValue) + { + options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path); + } + var cacheKey = path + options.DateLastModified.Value.Ticks; options.CacheKey = cacheKey.GetMD5(); @@ -351,7 +355,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer return _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, fileShare); } - public object GetStaticResult(IRequest requestContext, + public Task<object> GetStaticResult(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, @@ -372,7 +376,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer }); } - public object GetStaticResult(IRequest requestContext, StaticResultOptions options) + public async Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options) { var cacheKey = options.CacheKey; options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); @@ -398,7 +402,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer } var compress = ShouldCompressResponse(requestContext, contentType); - var hasOptions = GetStaticResult(requestContext, options, compress).Result; + var hasOptions = await GetStaticResult(requestContext, options, compress).ConfigureAwait(false); AddResponseHeaders(hasOptions, options.ResponseHeaders); return hasOptions; @@ -699,5 +703,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer throw error; } + + public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null) + { + return new AsyncStreamWriterFunc(streamWriter, responseHeaders); + } } }
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs index ce8100025..bfbb228ed 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration) { var durationMs = duration.TotalMilliseconds; - var logSuffix = durationMs >= 1000 ? "ms (slow)" : "ms"; + var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs deleted file mode 100644 index 31c0e87b3..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs +++ /dev/null @@ -1,285 +0,0 @@ -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Logging; -using ServiceStack; -using ServiceStack.Host.HttpListener; -using ServiceStack.Web; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.HttpServer.NetListener -{ - public class HttpListenerServer : IHttpListener - { - private readonly ILogger _logger; - private HttpListener _listener; - private readonly ManualResetEventSlim _listenForNextRequest = new ManualResetEventSlim(false); - - public Action<Exception, IRequest> ErrorHandler { get; set; } - public Action<WebSocketConnectEventArgs> WebSocketHandler { get; set; } - public Func<IHttpRequest, Uri, Task> RequestHandler { get; set; } - - private readonly Action<string> _endpointListener; - - public HttpListenerServer(ILogger logger, Action<string> endpointListener) - { - _logger = logger; - _endpointListener = endpointListener; - } - - private List<string> UrlPrefixes { get; set; } - - public void Start(IEnumerable<string> urlPrefixes) - { - UrlPrefixes = urlPrefixes.ToList(); - - if (_listener == null) - _listener = new HttpListener(); - - //HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); - - foreach (var prefix in UrlPrefixes) - { - _logger.Info("Adding HttpListener prefix " + prefix); - _listener.Prefixes.Add(prefix); - } - - _listener.Start(); - - Task.Factory.StartNew(Listen, TaskCreationOptions.LongRunning); - } - - private bool IsListening - { - get { return _listener != null && _listener.IsListening; } - } - - // Loop here to begin processing of new requests. - private void Listen() - { - while (IsListening) - { - if (_listener == null) return; - _listenForNextRequest.Reset(); - - try - { - _listener.BeginGetContext(ListenerCallback, _listener); - _listenForNextRequest.Wait(); - } - catch (Exception ex) - { - _logger.Error("Listen()", ex); - return; - } - if (_listener == null) return; - } - } - - // Handle the processing of a request in here. - private void ListenerCallback(IAsyncResult asyncResult) - { - _listenForNextRequest.Set(); - - var listener = asyncResult.AsyncState as HttpListener; - HttpListenerContext context; - - if (listener == null) return; - var isListening = listener.IsListening; - - try - { - if (!isListening) - { - _logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); return; - } - // The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework, - // blocks until there is a request to be processed or some type of data is available. - context = listener.EndGetContext(asyncResult); - } - catch (Exception ex) - { - // You will get an exception when httpListener.Stop() is called - // because there will be a thread stopped waiting on the .EndGetContext() - // method, and again, that is just the way most Begin/End asynchronous - // methods of the .NET Framework work. - var errMsg = ex + ": " + IsListening; - _logger.Warn(errMsg); - return; - } - - Task.Factory.StartNew(() => InitTask(context)); - } - - private void InitTask(HttpListenerContext context) - { - try - { - var task = this.ProcessRequestAsync(context); - task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); - - if (task.Status == TaskStatus.Created) - { - task.RunSynchronously(); - } - } - catch (Exception ex) - { - HandleError(ex, context); - } - } - - private Task ProcessRequestAsync(HttpListenerContext context) - { - var request = context.Request; - - LogHttpRequest(request); - - if (request.IsWebSocketRequest) - { - return ProcessWebSocketRequest(context); - } - - if (string.IsNullOrEmpty(context.Request.RawUrl)) - return ((object)null).AsTaskResult(); - - var operationName = context.Request.GetOperationName(); - - var httpReq = GetRequest(context, operationName); - - return RequestHandler(httpReq, request.Url); - } - - /// <summary> - /// Processes the web socket request. - /// </summary> - /// <param name="ctx">The CTX.</param> - /// <returns>Task.</returns> - private async Task ProcessWebSocketRequest(HttpListenerContext ctx) - { -#if !__MonoCS__ - try - { - var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); - - if (WebSocketHandler != null) - { - WebSocketHandler(new WebSocketConnectEventArgs - { - WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger), - Endpoint = ctx.Request.RemoteEndPoint.ToString() - }); - } - } - catch (Exception ex) - { - _logger.ErrorException("AcceptWebSocketAsync error", ex); - ctx.Response.StatusCode = 500; - ctx.Response.Close(); - } -#endif - } - - private void HandleError(Exception ex, HttpListenerContext context) - { - var operationName = context.Request.GetOperationName(); - var httpReq = GetRequest(context, operationName); - - if (ErrorHandler != null) - { - ErrorHandler(ex, httpReq); - } - } - - private static ListenerRequest GetRequest(HttpListenerContext httpContext, string operationName) - { - var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None); - req.RequestAttributes = req.GetAttributes(); - - return req; - } - - /// <summary> - /// Logs the HTTP request. - /// </summary> - /// <param name="request">The request.</param> - private void LogHttpRequest(HttpListenerRequest request) - { - var endpoint = request.LocalEndPoint; - - if (endpoint != null) - { - var address = endpoint.ToString(); - - _endpointListener(address); - } - - LogRequest(_logger, request); - } - - /// <summary> - /// Logs the request. - /// </summary> - /// <param name="logger">The logger.</param> - /// <param name="request">The request.</param> - private static void LogRequest(ILogger logger, HttpListenerRequest request) - { - var log = new StringBuilder(); - - var logHeaders = true; - - if (logHeaders) - { - var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k])); - - log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers); - } - - var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod; - - logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log); - } - - public void Stop() - { - if (_listener != null) - { - foreach (var prefix in UrlPrefixes) - { - _listener.Prefixes.Remove(prefix); - } - - _listener.Close(); - } - } - - 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; - } - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index 020856886..7ac92408b 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -5,10 +5,12 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; +using System.Threading.Tasks; +using ServiceStack; namespace MediaBrowser.Server.Implementations.HttpServer { - public class RangeRequestWriter : IStreamWriter, IHttpResult + public class RangeRequestWriter : IStreamWriter, IAsyncStreamWriter, IHttpResult { /// <summary> /// Gets or sets the source stream. @@ -39,6 +41,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// </summary> private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + public Func<IDisposable> ResultScope { get; set; } + public List<Cookie> Cookies { get; private set; } + /// <summary> /// Additional HTTP Headers /// </summary> @@ -81,6 +86,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer Options["Accept-Ranges"] = "bytes"; StatusCode = HttpStatusCode.PartialContent; + Cookies = new List<Cookie>(); SetRangeValues(); } @@ -165,16 +171,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="responseStream">The response stream.</param> public void WriteTo(Stream responseStream) { - WriteToInternal(responseStream); - } - - /// <summary> - /// Writes to async. - /// </summary> - /// <param name="responseStream">The response stream.</param> - /// <returns>Task.</returns> - private void WriteToInternal(Stream responseStream) - { try { // Headers only @@ -233,6 +229,66 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } + public async Task WriteToAsync(Stream responseStream) + { + try + { + // Headers only + if (IsHeadRequest) + { + return; + } + + using (var source = SourceStream) + { + // If the requested range is "0-", we can optimize by just doing a stream copy + if (RangeEnd >= TotalContentLength - 1) + { + await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); + } + else + { + await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false); + } + } + } + catch (IOException ex) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error in range request writer", ex); + throw; + } + finally + { + if (OnComplete != null) + { + OnComplete(); + } + } + } + + private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength) + { + var array = new byte[BufferSize]; + int count; + while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0) + { + var bytesToCopy = Math.Min(count, copyLength); + + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false); + + copyLength -= bytesToCopy; + + if (copyLength <= 0) + { + break; + } + } + } + public string ContentType { get; set; } public IRequest RequestContext { get; set; } diff --git a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs b/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs index f993d4437..ee05702f4 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs @@ -12,12 +12,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer { private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private readonly ILogger _logger; - private readonly Func<bool> _denyIframeEmbedding; - public ResponseFilter(ILogger logger, Func<bool> denyIframeEmbedding) + public ResponseFilter(ILogger logger) { _logger = logger; - _denyIframeEmbedding = denyIframeEmbedding; } /// <summary> @@ -31,11 +29,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer // Try to prevent compatibility view res.AddHeader("X-UA-Compatible", "IE=Edge"); - if (_denyIframeEmbedding()) - { - res.AddHeader("X-Frame-Options", "SAMEORIGIN"); - } - var exception = dto as Exception; if (exception != null) diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 357f5c976..bc3e7b163 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -104,6 +104,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security { info.DeviceId = tokenInfo.DeviceId; } + if (string.IsNullOrWhiteSpace(info.Version)) + { + info.Version = tokenInfo.AppVersion; + } } else { diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs index ed9e17b6b..bfa65ac6b 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs @@ -3,7 +3,9 @@ using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Text; +using System.Threading.Tasks; using System.Web; +using ServiceStack; using ServiceStack.Web; namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp @@ -31,53 +33,54 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp return header.Substring(ap + 1, end - ap - 1); } - void LoadMultiPart() + async Task LoadMultiPart() { string boundary = GetParameter(ContentType, "; boundary="); if (boundary == null) return; - var input = GetSubStream(InputStream); + using (var requestStream = GetSubStream(InputStream)) + { + //DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request + //Not ending with \r\n? + var ms = new MemoryStream(32 * 1024); + await requestStream.CopyToAsync(ms).ConfigureAwait(false); - //DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request - //Not ending with \r\n? - var ms = new MemoryStream(32 * 1024); - input.CopyTo(ms); - input = ms; - ms.WriteByte((byte)'\r'); - ms.WriteByte((byte)'\n'); + var input = ms; + ms.WriteByte((byte)'\r'); + ms.WriteByte((byte)'\n'); - input.Position = 0; + input.Position = 0; - //Uncomment to debug - //var content = new StreamReader(ms).ReadToEnd(); - //Console.WriteLine(boundary + "::" + content); - //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); + var multi_part = new HttpMultipart(input, boundary, ContentEncoding); - HttpMultipart.Element e; - while ((e = multi_part.ReadNextElement()) != null) - { - if (e.Filename == null) + HttpMultipart.Element e; + while ((e = multi_part.ReadNextElement()) != null) { - byte[] copy = new byte[e.Length]; + if (e.Filename == null) + { + byte[] copy = new byte[e.Length]; - input.Position = e.Start; - input.Read(copy, 0, (int)e.Length); + input.Position = e.Start; + input.Read(copy, 0, (int)e.Length); - form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy)); - } - 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.AddFile(e.Name, sub); + form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy)); + } + 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.AddFile(e.Name, sub); + } } } - EndSubStream(input); } public NameValueCollection Form @@ -90,10 +93,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp files = new HttpFileCollection(); if (IsContentType("multipart/form-data", true)) - LoadMultiPart(); - else if ( - IsContentType("application/x-www-form-urlencoded", true)) - LoadWwwForm(); + { + var task = LoadMultiPart(); + Task.WaitAll(task); + } + else if (IsContentType("application/x-www-form-urlencoded", true)) + { + var task = LoadWwwForm(); + Task.WaitAll(task); + } form.Protect(); } @@ -116,6 +124,21 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } } + public string Accept + { + get + { + return string.IsNullOrEmpty(request.Headers[HttpHeaders.Accept]) ? null : request.Headers[HttpHeaders.Accept]; + } + } + + public string Authorization + { + get + { + return string.IsNullOrEmpty(request.Headers[HttpHeaders.Authorization]) ? null : request.Headers[HttpHeaders.Authorization]; + } + } protected bool validate_cookies, validate_query_string, validate_form; protected bool checked_cookies, checked_query_string, checked_form; @@ -204,50 +227,50 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0; } - - - - - void LoadWwwForm() + async Task LoadWwwForm() { using (Stream input = GetSubStream(InputStream)) { - using (StreamReader s = new StreamReader(input, ContentEncoding)) + using (var ms = new MemoryStream()) { - StringBuilder key = new StringBuilder(); - StringBuilder value = new StringBuilder(); - int c; + await input.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; - while ((c = s.Read()) != -1) + using (StreamReader s = new StreamReader(ms, ContentEncoding)) { - if (c == '=') + StringBuilder key = new StringBuilder(); + StringBuilder value = new StringBuilder(); + int c; + + while ((c = s.Read()) != -1) { - value.Length = 0; - while ((c = s.Read()) != -1) + if (c == '=') { - 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); - break; + return; } - else - value.Append((char)c); } - if (c == -1) - { + else if (c == '&') AddRawKeyValue(key, value); - return; - } + else + key.Append((char)c); } - else if (c == '&') + if (c == -1) AddRawKeyValue(key, value); - else - key.Append((char)c); } - if (c == -1) - AddRawKeyValue(key, value); - - EndSubStream(input); } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs index 30849d441..dc2aec3e1 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp this.OperationName = operationName; this.RequestAttributes = requestAttributes; this.request = httpContext.Request; - this.response = new WebSocketSharpResponse(logger, httpContext.Response); + this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); this.RequestPreferences = new RequestPreferences(this); } @@ -134,12 +134,89 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp get { return remoteIp ?? - (remoteIp = XForwardedFor ?? - (NormalizeIp(XRealIp) ?? + (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)) @@ -388,10 +465,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp return stream; } - static void EndSubStream(Stream stream) - { - } - public static string GetHandlerPathIfAny(string listenerUrl) { if (listenerUrl == null) return null; diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index 171dacb22..e08be8bd1 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Net; using MediaBrowser.Model.Logging; @@ -14,14 +15,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp private readonly ILogger _logger; private readonly HttpListenerResponse response; - public WebSocketSharpResponse(ILogger logger, 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 bool UseBufferedStream { get; set; } - + public Dictionary<string, object> Items { get; private set; } public object OriginalResponse { get { return response; } @@ -58,6 +62,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp response.AddHeader(name, value); } + public string GetHeader(string name) + { + return response.Headers[name]; + } + public void Redirect(string url) { response.Redirect(url); @@ -142,5 +151,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } public bool KeepAlive { get; set; } + + public void ClearCookies() + { + } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs index a756f4aa8..f5906f6b7 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs @@ -4,13 +4,15 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Threading.Tasks; +using ServiceStack; namespace MediaBrowser.Server.Implementations.HttpServer { /// <summary> /// Class StreamWriter /// </summary> - public class StreamWriter : IStreamWriter, IHasOptions + public class StreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions { private ILogger Logger { get; set; } @@ -73,30 +75,49 @@ namespace MediaBrowser.Server.Implementations.HttpServer { } + // 256k + private const int BufferSize = 262144; + /// <summary> /// Writes to. /// </summary> /// <param name="responseStream">The response stream.</param> public void WriteTo(Stream responseStream) { - WriteToInternal(responseStream); + try + { + using (var src = SourceStream) + { + src.CopyTo(responseStream, BufferSize); + } + } + catch (Exception ex) + { + Logger.ErrorException("Error streaming data", ex); + + if (OnError != null) + { + OnError(); + } + + throw; + } + finally + { + if (OnComplete != null) + { + OnComplete(); + } + } } - // 256k - private const int BufferSize = 262144; - - /// <summary> - /// Writes to async. - /// </summary> - /// <param name="responseStream">The response stream.</param> - /// <returns>Task.</returns> - private void WriteToInternal(Stream responseStream) + public async Task WriteToAsync(Stream responseStream) { try { using (var src = SourceStream) { - src.CopyTo(responseStream, BufferSize); + await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); } } catch (Exception ex) @@ -107,7 +128,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer { OnError(); } - + throw; } finally diff --git a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs index aeaac80e8..d91f316d6 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', Path.DirectorySeparatorChar)); - return ResultFactory.GetStaticFileResult(Request, requestedFile); + return ResultFactory.GetStaticFileResult(Request, requestedFile).Result; } /// <summary> |
