aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations/HttpServer
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Server.Implementations/HttpServer')
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs56
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs77
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs21
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs285
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs78
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs9
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs4
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs151
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs87
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs17
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs47
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs2
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>