2013-12-07 15:52:38 +00:00
using System ;
using System.Collections.Generic ;
2019-02-25 17:26:17 +00:00
using System.Diagnostics ;
2017-09-30 17:40:42 +00:00
using System.Globalization ;
2013-12-07 15:52:38 +00:00
using System.IO ;
using System.Linq ;
2019-01-13 19:20:41 +00:00
using System.Net.Sockets ;
2013-12-07 15:52:38 +00:00
using System.Reflection ;
2016-11-12 07:14:04 +00:00
using System.Text ;
2017-05-22 04:54:02 +00:00
using System.Threading ;
2013-12-07 15:52:38 +00:00
using System.Threading.Tasks ;
2019-01-13 19:20:41 +00:00
using Emby.Server.Implementations.Net ;
2017-02-13 01:07:48 +00:00
using Emby.Server.Implementations.Services ;
2019-01-13 19:20:41 +00:00
using MediaBrowser.Common.Extensions ;
2015-12-14 14:45:42 +00:00
using MediaBrowser.Common.Net ;
2016-10-26 06:01:42 +00:00
using MediaBrowser.Controller ;
2019-01-13 19:20:41 +00:00
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Net ;
using MediaBrowser.Model.Events ;
2016-03-18 03:40:15 +00:00
using MediaBrowser.Model.Extensions ;
2016-11-10 14:41:24 +00:00
using MediaBrowser.Model.Serialization ;
2016-10-25 19:02:04 +00:00
using MediaBrowser.Model.Services ;
2019-02-17 10:54:47 +00:00
using Microsoft.Extensions.Configuration ;
2019-01-13 19:20:41 +00:00
using Microsoft.Extensions.Logging ;
2019-02-17 10:54:47 +00:00
using ServiceStack.Text.Jsv ;
2013-12-07 15:52:38 +00:00
2016-11-11 03:29:51 +00:00
namespace Emby.Server.Implementations.HttpServer
2013-12-07 15:52:38 +00:00
{
2017-02-13 02:06:54 +00:00
public class HttpListenerHost : IHttpServer , IDisposable
2013-12-07 15:52:38 +00:00
{
private string DefaultRedirectPath { get ; set ; }
private readonly ILogger _logger ;
2017-08-30 18:52:29 +00:00
public string [ ] UrlPrefixes { get ; private set ; }
2013-12-07 15:52:38 +00:00
2014-07-18 22:14:59 +00:00
private IHttpListener _listener ;
2013-12-07 15:52:38 +00:00
2018-09-12 17:26:21 +00:00
public event EventHandler < GenericEventArgs < IWebSocketConnection > > WebSocketConnected ;
2013-12-07 15:52:38 +00:00
2015-06-13 04:14:48 +00:00
private readonly IServerConfigurationManager _config ;
2015-12-14 14:45:42 +00:00
private readonly INetworkManager _networkManager ;
2016-10-26 06:01:42 +00:00
private readonly IServerApplicationHost _appHost ;
2016-11-10 14:41:24 +00:00
private readonly IJsonSerializer _jsonSerializer ;
private readonly IXmlSerializer _xmlSerializer ;
2016-11-11 03:29:51 +00:00
private readonly Func < Type , Func < string , object > > _funcParseFn ;
2016-11-10 14:41:24 +00:00
2017-08-30 18:52:29 +00:00
public Action < IRequest , IResponse , object > [ ] ResponseFilters { get ; set ; }
2017-02-13 02:06:54 +00:00
private readonly Dictionary < Type , Type > ServiceOperationsMap = new Dictionary < Type , Type > ( ) ;
public static HttpListenerHost Instance { get ; protected set ; }
2017-02-13 01:07:48 +00:00
2018-09-12 17:26:21 +00:00
private IWebSocketListener [ ] _webSocketListeners = Array . Empty < IWebSocketListener > ( ) ;
private readonly List < IWebSocketConnection > _webSocketConnections = new List < IWebSocketConnection > ( ) ;
2019-01-06 19:35:36 +00:00
public HttpListenerHost (
IServerApplicationHost applicationHost ,
2019-01-17 22:55:05 +00:00
ILoggerFactory loggerFactory ,
2015-06-13 04:14:48 +00:00
IServerConfigurationManager config ,
2019-02-17 10:54:47 +00:00
IConfiguration configuration ,
2019-01-06 19:35:36 +00:00
INetworkManager networkManager ,
IJsonSerializer jsonSerializer ,
2019-02-17 10:54:47 +00:00
IXmlSerializer xmlSerializer )
2013-12-07 15:52:38 +00:00
{
2016-10-26 06:01:42 +00:00
_appHost = applicationHost ;
2019-01-17 22:55:05 +00:00
_logger = loggerFactory . CreateLogger ( "HttpServer" ) ;
2019-01-06 19:35:36 +00:00
_config = config ;
2019-02-17 10:54:47 +00:00
DefaultRedirectPath = configuration [ "HttpListenerHost:DefaultRedirectPath" ] ;
2015-12-14 14:45:42 +00:00
_networkManager = networkManager ;
2016-11-10 14:41:24 +00:00
_jsonSerializer = jsonSerializer ;
_xmlSerializer = xmlSerializer ;
2019-02-17 10:54:47 +00:00
_funcParseFn = t = > s = > JsvReader . GetParseFn ( t ) ( s ) ;
2017-02-13 01:07:48 +00:00
2019-01-06 19:35:36 +00:00
Instance = this ;
ResponseFilters = Array . Empty < Action < IRequest , IResponse , object > > ( ) ;
2013-12-07 15:52:38 +00:00
}
2015-09-13 23:07:54 +00:00
public string GlobalResponse { get ; set ; }
2015-10-30 17:00:33 +00:00
2019-01-06 20:50:43 +00:00
protected ILogger Logger = > _logger ;
2016-11-08 18:44:23 +00:00
2017-02-13 02:06:54 +00:00
public object CreateInstance ( Type type )
2016-11-10 14:41:24 +00:00
{
2017-02-13 01:07:48 +00:00
return _appHost . CreateInstance ( type ) ;
2016-11-10 14:41:24 +00:00
}
2017-02-13 01:07:48 +00:00
/// <summary>
2019-01-07 23:27:46 +00:00
/// Applies the request filters. Returns whether or not the request has been handled
2017-02-13 01:07:48 +00:00
/// and no more processing should be done.
/// </summary>
/// <returns></returns>
public void ApplyRequestFilters ( IRequest req , IResponse res , object requestDto )
2016-11-11 01:37:20 +00:00
{
2017-02-13 01:07:48 +00:00
//Exec all RequestFilter attributes with Priority < 0
var attributes = GetRequestFilterAttributes ( requestDto . GetType ( ) ) ;
2017-08-30 18:52:29 +00:00
2019-01-06 19:35:36 +00:00
int count = attributes . Count ;
int i = 0 ;
2017-08-30 18:52:29 +00:00
for ( ; i < count & & attributes [ i ] . Priority < 0 ; i + + )
2017-02-13 01:07:48 +00:00
{
var attribute = attributes [ i ] ;
attribute . RequestFilter ( req , res , requestDto ) ;
}
//Exec remaining RequestFilter attributes with Priority >= 0
2017-08-30 18:52:29 +00:00
for ( ; i < count & & attributes [ i ] . Priority > = 0 ; i + + )
2017-02-13 01:07:48 +00:00
{
var attribute = attributes [ i ] ;
attribute . RequestFilter ( req , res , requestDto ) ;
}
2013-12-07 15:52:38 +00:00
}
2017-02-13 01:07:48 +00:00
public Type GetServiceTypeByRequest ( Type requestType )
2013-12-07 15:52:38 +00:00
{
2019-01-13 20:46:33 +00:00
ServiceOperationsMap . TryGetValue ( requestType , out var serviceType ) ;
2017-02-13 01:07:48 +00:00
return serviceType ;
}
2013-12-07 15:52:38 +00:00
2017-08-19 19:43:35 +00:00
public void AddServiceInfo ( Type serviceType , Type requestType )
2017-02-13 01:07:48 +00:00
{
ServiceOperationsMap [ requestType ] = serviceType ;
2013-12-07 15:52:38 +00:00
}
2017-08-30 18:52:29 +00:00
private List < IHasRequestFilter > GetRequestFilterAttributes ( Type requestDtoType )
2013-12-07 15:52:38 +00:00
{
2017-02-13 02:06:54 +00:00
var attributes = requestDtoType . GetTypeInfo ( ) . GetCustomAttributes ( true ) . OfType < IHasRequestFilter > ( ) . ToList ( ) ;
2017-02-13 01:07:48 +00:00
var serviceType = GetServiceTypeByRequest ( requestDtoType ) ;
if ( serviceType ! = null )
{
2017-02-13 02:06:54 +00:00
attributes . AddRange ( serviceType . GetTypeInfo ( ) . GetCustomAttributes ( true ) . OfType < IHasRequestFilter > ( ) ) ;
2017-02-13 01:07:48 +00:00
}
attributes . Sort ( ( x , y ) = > x . Priority - y . Priority ) ;
2017-08-30 18:52:29 +00:00
return attributes ;
2014-07-09 00:46:11 +00:00
}
2013-12-07 15:52:38 +00:00
2018-09-12 17:26:21 +00:00
private void OnWebSocketConnected ( WebSocketConnectEventArgs e )
2015-03-08 19:48:30 +00:00
{
2016-04-22 16:12:20 +00:00
if ( _disposed )
{
return ;
}
2019-01-16 19:50:40 +00:00
var connection = new WebSocketConnection ( e . WebSocket , e . Endpoint , _jsonSerializer , _logger )
2015-03-08 19:48:30 +00:00
{
2018-09-12 17:26:21 +00:00
OnReceive = ProcessWebSocketMessageReceived ,
Url = e . Url ,
QueryString = e . QueryString ? ? new QueryParamCollection ( )
} ;
2015-03-08 19:48:30 +00:00
2018-09-12 17:26:21 +00:00
connection . Closed + = Connection_Closed ;
lock ( _webSocketConnections )
2016-04-22 16:12:20 +00:00
{
2018-09-12 17:26:21 +00:00
_webSocketConnections . Add ( connection ) ;
2016-04-22 16:12:20 +00:00
}
2019-01-06 19:35:36 +00:00
WebSocketConnected ? . Invoke ( this , new GenericEventArgs < IWebSocketConnection > ( connection ) ) ;
2018-09-12 17:26:21 +00:00
}
private void Connection_Closed ( object sender , EventArgs e )
{
lock ( _webSocketConnections )
{
_webSocketConnections . Remove ( ( IWebSocketConnection ) sender ) ;
2014-07-09 00:46:11 +00:00
}
2013-12-07 15:52:38 +00:00
}
2019-01-06 20:50:43 +00:00
private static Exception GetActualException ( Exception ex )
2017-07-22 22:58:03 +00:00
{
2019-01-06 20:50:43 +00:00
if ( ex is AggregateException agg )
2017-07-22 22:58:03 +00:00
{
var inner = agg . InnerException ;
if ( inner ! = null )
{
return GetActualException ( inner ) ;
}
else
{
var inners = agg . InnerExceptions ;
if ( inners ! = null & & inners . Count > 0 )
{
return GetActualException ( inners [ 0 ] ) ;
}
}
}
return ex ;
}
2016-12-27 07:24:44 +00:00
private int GetStatusCode ( Exception ex )
{
2019-01-06 19:35:36 +00:00
switch ( ex )
2016-12-27 07:24:44 +00:00
{
2019-01-18 16:04:01 +00:00
case ArgumentException _ : return 400 ;
case SecurityException _ : return 401 ;
2019-01-06 19:35:36 +00:00
case DirectoryNotFoundException _ :
case FileNotFoundException _ :
2019-01-18 16:04:01 +00:00
case ResourceNotFoundException _ : return 404 ;
2019-01-06 19:35:36 +00:00
case RemoteServiceUnavailableException _ : return 502 ;
2019-01-18 16:04:01 +00:00
default : return 500 ;
2016-12-27 07:24:44 +00:00
}
}
2018-09-12 17:26:21 +00:00
private async Task ErrorHandler ( Exception ex , IRequest httpReq , bool logExceptionStackTrace , bool logExceptionMessage )
2013-12-07 15:52:38 +00:00
{
try
{
2017-07-22 22:58:03 +00:00
ex = GetActualException ( ex ) ;
2018-09-12 17:26:21 +00:00
if ( logExceptionStackTrace )
2016-12-26 17:38:12 +00:00
{
2018-12-20 12:11:26 +00:00
_logger . LogError ( ex , "Error processing request" ) ;
2016-12-26 17:38:12 +00:00
}
2018-09-12 17:26:21 +00:00
else if ( logExceptionMessage )
{
2018-12-13 13:18:25 +00:00
_logger . LogError ( ex . Message ) ;
2018-09-12 17:26:21 +00:00
}
2016-11-10 14:41:24 +00:00
2013-12-07 15:52:38 +00:00
var httpRes = httpReq . Response ;
2014-07-04 02:22:57 +00:00
if ( httpRes . IsClosed )
{
return ;
}
2015-01-17 19:30:23 +00:00
2016-12-27 07:24:44 +00:00
var statusCode = GetStatusCode ( ex ) ;
2016-11-12 06:58:50 +00:00
httpRes . StatusCode = statusCode ;
2013-12-07 15:52:38 +00:00
2016-11-10 14:41:24 +00:00
httpRes . ContentType = "text/html" ;
2018-09-12 17:26:21 +00:00
await Write ( httpRes , NormalizeExceptionMessage ( ex . Message ) ) . ConfigureAwait ( false ) ;
2013-12-07 15:52:38 +00:00
}
2018-12-20 12:11:26 +00:00
catch ( Exception errorEx )
2013-12-07 15:52:38 +00:00
{
2018-12-20 12:11:26 +00:00
_logger . LogError ( errorEx , "Error this.ProcessRequest(context)(Exception while writing error to the response)" ) ;
2013-12-07 15:52:38 +00:00
}
}
2018-09-12 17:26:21 +00:00
private string NormalizeExceptionMessage ( string msg )
{
if ( msg = = null )
{
return string . Empty ;
}
// Strip any information we don't want to reveal
msg = msg . Replace ( _config . ApplicationPaths . ProgramSystemPath , string . Empty , StringComparison . OrdinalIgnoreCase ) ;
msg = msg . Replace ( _config . ApplicationPaths . ProgramDataPath , string . Empty , StringComparison . OrdinalIgnoreCase ) ;
return msg ;
}
2013-12-07 15:52:38 +00:00
/// <summary>
/// Shut down the Web Service
/// </summary>
public void Stop ( )
{
2018-09-12 17:26:21 +00:00
List < IWebSocketConnection > connections ;
lock ( _webSocketConnections )
{
connections = _webSocketConnections . ToList ( ) ;
_webSocketConnections . Clear ( ) ;
}
foreach ( var connection in connections )
{
try
{
connection . Dispose ( ) ;
}
catch
{
}
}
2014-07-18 22:14:59 +00:00
if ( _listener ! = null )
2013-12-07 15:52:38 +00:00
{
2018-12-13 13:18:25 +00:00
_logger . LogInformation ( "Stopping HttpListener..." ) ;
2017-09-03 07:28:58 +00:00
var task = _listener . Stop ( ) ;
Task . WaitAll ( task ) ;
2018-12-13 13:18:25 +00:00
_logger . LogInformation ( "HttpListener stopped" ) ;
2013-12-07 15:52:38 +00:00
}
}
2016-03-15 19:11:53 +00:00
public static string RemoveQueryStringByKey ( string url , string key )
{
var uri = new Uri ( url ) ;
// this gets all the query string key value pairs as a collection
var newQueryString = MyHttpUtility . ParseQueryString ( uri . Query ) ;
2016-11-11 03:29:51 +00:00
var originalCount = newQueryString . Count ;
if ( originalCount = = 0 )
2016-03-15 19:11:53 +00:00
{
return url ;
}
// this removes the key if exists
newQueryString . Remove ( key ) ;
2016-11-11 03:29:51 +00:00
if ( originalCount = = newQueryString . Count )
{
return url ;
}
2016-03-15 19:11:53 +00:00
// this gets the page path from root without QueryString
2016-11-11 03:29:51 +00:00
string pagePathWithoutQueryString = url . Split ( new [ ] { '?' } , StringSplitOptions . RemoveEmptyEntries ) [ 0 ] ;
2016-03-15 19:11:53 +00:00
return newQueryString . Count > 0
2019-01-06 20:50:43 +00:00
? string . Format ( "{0}?{1}" , pagePathWithoutQueryString , newQueryString )
2016-03-15 19:11:53 +00:00
: pagePathWithoutQueryString ;
}
2019-01-06 20:50:43 +00:00
private static string GetUrlToLog ( string url )
2016-03-15 19:11:53 +00:00
{
url = RemoveQueryStringByKey ( url , "api_key" ) ;
return url ;
}
2019-01-06 20:50:43 +00:00
private static string NormalizeConfiguredLocalAddress ( string address )
2016-08-18 06:26:47 +00:00
{
var index = address . Trim ( '/' ) . IndexOf ( '/' ) ;
if ( index ! = - 1 )
{
address = address . Substring ( index + 1 ) ;
}
return address . Trim ( '/' ) ;
}
2017-09-03 07:28:58 +00:00
private bool ValidateHost ( string host )
2016-08-18 06:26:47 +00:00
{
var hosts = _config
. Configuration
. LocalNetworkAddresses
. Select ( NormalizeConfiguredLocalAddress )
. ToList ( ) ;
if ( hosts . Count = = 0 )
{
return true ;
}
2017-09-03 07:28:58 +00:00
host = host ? ? string . Empty ;
2016-08-18 06:26:47 +00:00
if ( _networkManager . IsInPrivateAddressSpace ( host ) )
{
hosts . Add ( "localhost" ) ;
hosts . Add ( "127.0.0.1" ) ;
return hosts . Any ( i = > host . IndexOf ( i , StringComparison . OrdinalIgnoreCase ) ! = - 1 ) ;
}
return true ;
}
2018-09-12 17:26:21 +00:00
private bool ValidateRequest ( string remoteIp , bool isLocal )
{
if ( isLocal )
{
return true ;
}
if ( _config . Configuration . EnableRemoteAccess )
{
var addressFilter = _config . Configuration . RemoteIPFilter . Where ( i = > ! string . IsNullOrWhiteSpace ( i ) ) . ToArray ( ) ;
if ( addressFilter . Length > 0 & & ! _networkManager . IsInLocalNetwork ( remoteIp ) )
{
if ( _config . Configuration . IsRemoteIPFilterBlacklist )
{
return ! _networkManager . IsAddressInSubnets ( remoteIp , addressFilter ) ;
}
else
{
return _networkManager . IsAddressInSubnets ( remoteIp , addressFilter ) ;
}
}
}
else
{
if ( ! _networkManager . IsInLocalNetwork ( remoteIp ) )
{
return false ;
}
}
return true ;
}
2017-09-30 17:40:42 +00:00
private bool ValidateSsl ( string remoteIp , string urlString )
2017-09-29 19:17:54 +00:00
{
2018-09-12 17:26:21 +00:00
if ( _config . Configuration . RequireHttps & & _appHost . EnableHttps & & ! _config . Configuration . IsBehindProxy )
2017-09-29 19:17:54 +00:00
{
2017-09-30 17:40:42 +00:00
if ( urlString . IndexOf ( "https://" , StringComparison . OrdinalIgnoreCase ) = = - 1 )
2017-09-29 19:17:54 +00:00
{
2018-09-12 17:26:21 +00:00
// These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected
if ( urlString . IndexOf ( "system/ping" , StringComparison . OrdinalIgnoreCase ) ! = - 1 | |
urlString . IndexOf ( "dlna/" , StringComparison . OrdinalIgnoreCase ) ! = - 1 )
{
return true ;
}
2017-09-30 17:40:42 +00:00
if ( ! _networkManager . IsInLocalNetwork ( remoteIp ) )
{
return false ;
}
2017-09-29 19:17:54 +00:00
}
}
return true ;
}
2013-12-07 15:52:38 +00:00
/// <summary>
/// Overridable method that can be used to implement a custom hnandler
/// </summary>
2017-09-03 07:28:58 +00:00
protected async Task RequestHandler ( IHttpRequest httpReq , string urlString , string host , string localPath , CancellationToken cancellationToken )
2013-12-07 15:52:38 +00:00
{
2019-02-25 17:26:17 +00:00
var stopWatch = new Stopwatch ( ) ;
stopWatch . Start ( ) ;
2014-07-18 22:14:59 +00:00
var httpRes = httpReq . Response ;
2016-11-08 18:44:23 +00:00
string urlToLog = null ;
2018-09-12 17:26:21 +00:00
string remoteIp = httpReq . RemoteIp ;
2014-07-09 00:46:11 +00:00
2016-11-08 18:44:23 +00:00
try
2016-04-22 16:12:20 +00:00
{
2016-11-08 18:44:23 +00:00
if ( _disposed )
{
httpRes . StatusCode = 503 ;
2016-11-13 21:04:21 +00:00
httpRes . ContentType = "text/plain" ;
2018-09-12 17:26:21 +00:00
await Write ( httpRes , "Server shutting down" ) . ConfigureAwait ( false ) ;
2016-11-08 18:44:23 +00:00
return ;
}
2016-04-22 16:12:20 +00:00
2017-09-03 07:28:58 +00:00
if ( ! ValidateHost ( host ) )
2016-11-08 18:44:23 +00:00
{
httpRes . StatusCode = 400 ;
httpRes . ContentType = "text/plain" ;
2018-09-12 17:26:21 +00:00
await Write ( httpRes , "Invalid host" ) . ConfigureAwait ( false ) ;
2016-11-08 18:44:23 +00:00
return ;
}
2016-08-18 06:26:47 +00:00
2018-09-12 17:26:21 +00:00
if ( ! ValidateRequest ( remoteIp , httpReq . IsLocal ) )
2017-09-29 19:17:54 +00:00
{
2018-09-12 17:26:21 +00:00
httpRes . StatusCode = 403 ;
httpRes . ContentType = "text/plain" ;
await Write ( httpRes , "Forbidden" ) . ConfigureAwait ( false ) ;
return ;
}
2017-09-30 17:40:42 +00:00
2018-09-12 17:26:21 +00:00
if ( ! ValidateSsl ( httpReq . RemoteIp , urlString ) )
{
RedirectToSecureUrl ( httpReq , httpRes , urlString ) ;
2017-09-29 19:17:54 +00:00
return ;
}
2016-11-08 18:44:23 +00:00
if ( string . Equals ( httpReq . Verb , "OPTIONS" , StringComparison . OrdinalIgnoreCase ) )
{
httpRes . StatusCode = 200 ;
httpRes . AddHeader ( "Access-Control-Allow-Origin" , "*" ) ;
httpRes . AddHeader ( "Access-Control-Allow-Methods" , "GET, POST, PUT, DELETE, PATCH, OPTIONS" ) ;
2016-11-27 00:40:15 +00:00
httpRes . AddHeader ( "Access-Control-Allow-Headers" , "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization" ) ;
httpRes . ContentType = "text/plain" ;
2018-09-12 17:26:21 +00:00
await Write ( httpRes , string . Empty ) . ConfigureAwait ( false ) ;
2016-11-08 18:44:23 +00:00
return ;
}
2016-08-18 06:26:47 +00:00
2019-02-25 17:26:17 +00:00
urlToLog = GetUrlToLog ( urlString ) ;
Logger . LogDebug ( "HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}" , urlToLog , httpReq . UserAgent ? ? string . Empty , httpReq . HttpMethod , httpReq . Headers ) ;
2016-01-23 03:10:21 +00:00
2016-11-08 18:44:23 +00:00
if ( string . Equals ( localPath , "/emby/" , StringComparison . OrdinalIgnoreCase ) | |
string . Equals ( localPath , "/mediabrowser/" , StringComparison . OrdinalIgnoreCase ) )
{
RedirectToUrl ( httpRes , DefaultRedirectPath ) ;
return ;
}
2019-02-25 17:26:17 +00:00
2016-11-08 18:44:23 +00:00
if ( string . Equals ( localPath , "/emby" , StringComparison . OrdinalIgnoreCase ) | |
string . Equals ( localPath , "/mediabrowser" , StringComparison . OrdinalIgnoreCase ) )
{
RedirectToUrl ( httpRes , "emby/" + DefaultRedirectPath ) ;
return ;
}
2016-03-18 03:40:15 +00:00
2019-01-06 19:35:36 +00:00
if ( localPath . IndexOf ( "mediabrowser/web" , StringComparison . OrdinalIgnoreCase ) ! = - 1 )
2016-11-08 18:44:23 +00:00
{
httpRes . StatusCode = 200 ;
httpRes . ContentType = "text/html" ;
var newUrl = urlString . Replace ( "mediabrowser" , "emby" , StringComparison . OrdinalIgnoreCase )
. Replace ( "/dashboard/" , "/web/" , StringComparison . OrdinalIgnoreCase ) ;
2016-04-06 02:18:56 +00:00
2016-11-08 18:44:23 +00:00
if ( ! string . Equals ( newUrl , urlString , StringComparison . OrdinalIgnoreCase ) )
{
2018-09-12 17:26:21 +00:00
await Write ( httpRes ,
2016-11-08 18:44:23 +00:00
"<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
2018-09-12 17:26:21 +00:00
newUrl + "\">" + newUrl + "</a></body></html>" ) . ConfigureAwait ( false ) ;
2016-11-08 18:44:23 +00:00
return ;
}
}
2016-08-22 18:28:24 +00:00
2016-11-08 18:44:23 +00:00
if ( localPath . IndexOf ( "dashboard/" , StringComparison . OrdinalIgnoreCase ) ! = - 1 & &
localPath . IndexOf ( "web/dashboard" , StringComparison . OrdinalIgnoreCase ) = = - 1 )
2016-08-22 18:28:24 +00:00
{
2016-11-08 18:44:23 +00:00
httpRes . StatusCode = 200 ;
httpRes . ContentType = "text/html" ;
var newUrl = urlString . Replace ( "mediabrowser" , "emby" , StringComparison . OrdinalIgnoreCase )
. Replace ( "/dashboard/" , "/web/" , StringComparison . OrdinalIgnoreCase ) ;
2016-08-22 18:28:24 +00:00
2016-11-08 18:44:23 +00:00
if ( ! string . Equals ( newUrl , urlString , StringComparison . OrdinalIgnoreCase ) )
{
2018-09-12 17:26:21 +00:00
await Write ( httpRes ,
2016-11-08 18:44:23 +00:00
"<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
2018-09-12 17:26:21 +00:00
newUrl + "\">" + newUrl + "</a></body></html>" ) . ConfigureAwait ( false ) ;
2016-11-08 18:44:23 +00:00
return ;
}
2016-08-22 18:28:24 +00:00
}
2016-11-08 18:44:23 +00:00
if ( string . Equals ( localPath , "/web" , StringComparison . OrdinalIgnoreCase ) )
{
RedirectToUrl ( httpRes , DefaultRedirectPath ) ;
return ;
}
2019-02-25 17:26:17 +00:00
2016-11-08 18:44:23 +00:00
if ( string . Equals ( localPath , "/web/" , StringComparison . OrdinalIgnoreCase ) )
{
RedirectToUrl ( httpRes , "../" + DefaultRedirectPath ) ;
return ;
}
2019-02-25 17:26:17 +00:00
2016-11-08 18:44:23 +00:00
if ( string . Equals ( localPath , "/" , StringComparison . OrdinalIgnoreCase ) )
{
RedirectToUrl ( httpRes , DefaultRedirectPath ) ;
return ;
}
2019-02-25 17:26:17 +00:00
2016-11-08 18:44:23 +00:00
if ( string . IsNullOrEmpty ( localPath ) )
{
RedirectToUrl ( httpRes , "/" + DefaultRedirectPath ) ;
return ;
}
2016-03-18 03:40:15 +00:00
2018-09-12 17:26:21 +00:00
if ( ! string . Equals ( httpReq . QueryString [ "r" ] , "0" , StringComparison . OrdinalIgnoreCase ) )
2016-03-25 17:48:18 +00:00
{
2018-09-12 17:26:21 +00:00
if ( localPath . EndsWith ( "web/dashboard.html" , StringComparison . OrdinalIgnoreCase ) )
{
RedirectToUrl ( httpRes , "index.html#!/dashboard.html" ) ;
}
if ( localPath . EndsWith ( "web/home.html" , StringComparison . OrdinalIgnoreCase ) )
{
RedirectToUrl ( httpRes , "index.html" ) ;
}
2016-11-08 18:44:23 +00:00
}
2016-03-18 03:40:15 +00:00
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrEmpty ( GlobalResponse ) )
2016-11-08 18:44:23 +00:00
{
2018-09-12 17:26:21 +00:00
// We don't want the address pings in ApplicationHost to fail
if ( localPath . IndexOf ( "system/ping" , StringComparison . OrdinalIgnoreCase ) = = - 1 )
{
httpRes . StatusCode = 503 ;
httpRes . ContentType = "text/html" ;
await Write ( httpRes , GlobalResponse ) . ConfigureAwait ( false ) ;
return ;
}
2016-03-25 17:48:18 +00:00
}
2016-03-18 06:36:58 +00:00
2017-02-13 01:07:48 +00:00
var handler = GetServiceHandler ( httpReq ) ;
2014-07-09 00:46:11 +00:00
2016-11-08 18:44:23 +00:00
if ( handler ! = null )
{
2019-02-25 17:26:17 +00:00
await handler . ProcessRequestAsync ( this , httpReq , httpRes , Logger , httpReq . OperationName , cancellationToken ) . ConfigureAwait ( false ) ;
2016-11-08 18:44:23 +00:00
}
2016-11-12 06:58:50 +00:00
else
{
2018-09-12 17:26:21 +00:00
await ErrorHandler ( new FileNotFoundException ( ) , httpReq , false , false ) . ConfigureAwait ( false ) ;
2016-11-12 06:58:50 +00:00
}
2016-02-21 06:25:25 +00:00
}
2019-02-25 17:26:17 +00:00
catch ( Exception ex ) when ( ex is SocketException | | ex is IOException | | ex is OperationCanceledException )
2018-09-12 17:26:21 +00:00
{
await ErrorHandler ( ex , httpReq , false , false ) . ConfigureAwait ( false ) ;
}
catch ( SecurityException ex )
{
await ErrorHandler ( ex , httpReq , false , true ) . ConfigureAwait ( false ) ;
2016-12-26 17:38:12 +00:00
}
2016-11-08 18:44:23 +00:00
catch ( Exception ex )
2015-09-13 23:07:54 +00:00
{
2017-09-24 20:24:12 +00:00
var logException = ! string . Equals ( ex . GetType ( ) . Name , "SocketException" , StringComparison . OrdinalIgnoreCase ) ;
2018-09-12 17:26:21 +00:00
await ErrorHandler ( ex , httpReq , logException , false ) . ConfigureAwait ( false ) ;
2015-09-13 23:07:54 +00:00
}
2016-11-08 18:44:23 +00:00
finally
2013-12-07 15:52:38 +00:00
{
2016-11-08 18:44:23 +00:00
httpRes . Close ( ) ;
2013-12-07 15:52:38 +00:00
2019-02-25 17:26:17 +00:00
stopWatch . Stop ( ) ;
var elapsed = stopWatch . Elapsed ;
if ( elapsed . Milliseconds > 500 )
2014-07-09 00:46:11 +00:00
{
2019-02-25 17:26:17 +00:00
_logger . LogWarning ( "HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:ss.fff}. {Url}" , httpRes . StatusCode , remoteIp , stopWatch . Elapsed , urlToLog ) ;
}
else
{
_logger . LogDebug ( "HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:ss.fff}. {Url}" , httpRes . StatusCode , remoteIp , stopWatch . Elapsed , urlToLog ) ;
2016-07-14 19:13:52 +00:00
}
2013-12-07 15:52:38 +00:00
}
}
2017-02-13 01:07:48 +00:00
// Entry point for HttpListener
public ServiceHandler GetServiceHandler ( IHttpRequest httpReq )
{
var pathInfo = httpReq . PathInfo ;
var pathParts = pathInfo . TrimStart ( '/' ) . Split ( '/' ) ;
if ( pathParts . Length = = 0 )
{
2019-02-25 17:26:17 +00:00
_logger . LogError ( "Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}" , pathInfo , httpReq . RawUrl ) ;
2017-02-13 01:07:48 +00:00
return null ;
}
2017-02-13 02:06:54 +00:00
2019-01-17 17:47:41 +00:00
var restPath = ServiceHandler . FindMatchingRestPath ( httpReq . HttpMethod , pathInfo , out string contentType ) ;
2017-02-13 01:07:48 +00:00
if ( restPath ! = null )
{
return new ServiceHandler
{
RestPath = restPath ,
ResponseContentType = contentType
} ;
}
2019-01-06 19:35:36 +00:00
_logger . LogError ( "Could not find handler for {PathInfo}" , pathInfo ) ;
2017-02-13 01:07:48 +00:00
return null ;
}
2019-01-06 20:50:43 +00:00
private static Task Write ( IResponse response , string text )
2016-11-12 07:14:04 +00:00
{
var bOutput = Encoding . UTF8 . GetBytes ( text ) ;
response . SetContentLength ( bOutput . Length ) ;
2018-09-12 17:26:21 +00:00
return response . OutputStream . WriteAsync ( bOutput , 0 , bOutput . Length ) ;
}
private void RedirectToSecureUrl ( IHttpRequest httpReq , IResponse httpRes , string url )
{
2019-01-06 19:35:36 +00:00
if ( Uri . TryCreate ( url , UriKind . Absolute , out Uri uri ) )
2018-09-12 17:26:21 +00:00
{
2019-01-06 19:35:36 +00:00
var builder = new UriBuilder ( uri )
{
Port = _config . Configuration . PublicHttpsPort ,
Scheme = "https"
} ;
2018-09-12 17:26:21 +00:00
url = builder . Uri . ToString ( ) ;
RedirectToUrl ( httpRes , url ) ;
}
else
{
var httpsUrl = url
. Replace ( "http://" , "https://" , StringComparison . OrdinalIgnoreCase )
. Replace ( ":" + _config . Configuration . PublicPort . ToString ( CultureInfo . InvariantCulture ) , ":" + _config . Configuration . PublicHttpsPort . ToString ( CultureInfo . InvariantCulture ) , StringComparison . OrdinalIgnoreCase ) ;
RedirectToUrl ( httpRes , url ) ;
}
2016-11-12 07:14:04 +00:00
}
2016-11-08 18:44:23 +00:00
public static void RedirectToUrl ( IResponse httpRes , string url )
{
httpRes . StatusCode = 302 ;
2016-11-10 14:41:24 +00:00
httpRes . AddHeader ( "Location" , url ) ;
2016-11-08 18:44:23 +00:00
}
2017-02-13 01:07:48 +00:00
public ServiceController ServiceController { get ; private set ; }
2016-11-08 18:44:23 +00:00
2013-12-07 15:52:38 +00:00
/// <summary>
/// Adds the rest handlers.
/// </summary>
/// <param name="services">The services.</param>
2018-09-12 17:26:21 +00:00
public void Init ( IEnumerable < IService > services , IEnumerable < IWebSocketListener > listeners )
2013-12-07 15:52:38 +00:00
{
2018-09-12 17:26:21 +00:00
_webSocketListeners = listeners . ToArray ( ) ;
2013-12-07 15:52:38 +00:00
2017-08-09 19:56:38 +00:00
ServiceController = new ServiceController ( ) ;
2013-12-07 15:52:38 +00:00
2018-12-13 13:18:25 +00:00
_logger . LogInformation ( "Calling ServiceStack AppHost.Init" ) ;
2013-12-09 02:24:48 +00:00
2018-09-12 17:26:21 +00:00
var types = services . Select ( r = > r . GetType ( ) ) . ToArray ( ) ;
2017-08-09 19:56:38 +00:00
ServiceController . Init ( this , types ) ;
2017-02-13 01:07:48 +00:00
2017-08-30 18:52:29 +00:00
ResponseFilters = new Action < IRequest , IResponse , object > [ ]
{
new ResponseFilter ( _logger ) . FilterResponse
} ;
2013-12-07 15:52:38 +00:00
}
2017-02-13 02:06:54 +00:00
public RouteAttribute [ ] GetRouteAttributes ( Type requestType )
2015-01-17 19:30:23 +00:00
{
2017-02-13 02:06:54 +00:00
var routes = requestType . GetTypeInfo ( ) . GetCustomAttributes < RouteAttribute > ( true ) . ToList ( ) ;
2015-01-17 19:30:23 +00:00
var clone = routes . ToList ( ) ;
foreach ( var route in clone )
{
2016-11-11 03:29:51 +00:00
routes . Add ( new RouteAttribute ( NormalizeEmbyRoutePath ( route . Path ) , route . Verbs )
2015-04-11 21:34:05 +00:00
{
Notes = route . Notes ,
Priority = route . Priority ,
Summary = route . Summary
} ) ;
2016-03-18 03:40:15 +00:00
2017-09-03 01:44:02 +00:00
routes . Add ( new RouteAttribute ( NormalizeMediaBrowserRoutePath ( route . Path ) , route . Verbs )
2015-04-11 21:34:05 +00:00
{
Notes = route . Notes ,
Priority = route . Priority ,
Summary = route . Summary
} ) ;
2017-09-03 01:44:02 +00:00
2017-10-07 06:13:26 +00:00
// needed because apps add /emby, and some users also add /emby, thereby double prefixing
routes . Add ( new RouteAttribute ( DoubleNormalizeEmbyRoutePath ( route . Path ) , route . Verbs )
{
Notes = route . Notes ,
Priority = route . Priority ,
Summary = route . Summary
} ) ;
2015-01-17 19:30:23 +00:00
}
2018-12-28 15:48:26 +00:00
return routes . ToArray ( ) ;
2015-01-17 19:30:23 +00:00
}
2017-02-13 02:06:54 +00:00
public Func < string , object > GetParseFn ( Type propertyType )
2016-11-11 01:37:20 +00:00
{
2016-11-11 03:29:51 +00:00
return _funcParseFn ( propertyType ) ;
2016-11-11 01:37:20 +00:00
}
2017-02-13 02:06:54 +00:00
public void SerializeToJson ( object o , Stream stream )
2016-11-10 14:41:24 +00:00
{
_jsonSerializer . SerializeToStream ( o , stream ) ;
}
2017-02-13 02:06:54 +00:00
public void SerializeToXml ( object o , Stream stream )
2016-11-10 14:41:24 +00:00
{
_xmlSerializer . SerializeToStream ( o , stream ) ;
}
2018-09-12 17:26:21 +00:00
public Task < object > DeserializeXml ( Type type , Stream stream )
2016-11-10 14:41:24 +00:00
{
2018-09-12 17:26:21 +00:00
return Task . FromResult ( _xmlSerializer . DeserializeFromStream ( type , stream ) ) ;
2016-11-10 14:41:24 +00:00
}
2018-09-12 17:26:21 +00:00
public Task < object > DeserializeJson ( Type type , Stream stream )
2016-11-10 14:41:24 +00:00
{
2018-09-12 17:26:21 +00:00
return _jsonSerializer . DeserializeFromStreamAsync ( stream , type ) ;
2016-11-10 14:41:24 +00:00
}
2019-01-06 20:50:43 +00:00
//TODO Add Jellyfin Route Path Normalizer
private static string NormalizeEmbyRoutePath ( string path )
2015-04-11 21:34:05 +00:00
{
if ( path . StartsWith ( "/" , StringComparison . OrdinalIgnoreCase ) )
{
return "/emby" + path ;
}
return "emby/" + path ;
}
2019-01-06 20:50:43 +00:00
private static string NormalizeMediaBrowserRoutePath ( string path )
2017-09-03 01:44:02 +00:00
{
if ( path . StartsWith ( "/" , StringComparison . OrdinalIgnoreCase ) )
{
return "/mediabrowser" + path ;
}
return "mediabrowser/" + path ;
}
2019-01-06 20:50:43 +00:00
private static string DoubleNormalizeEmbyRoutePath ( string path )
2015-04-11 21:34:05 +00:00
{
if ( path . StartsWith ( "/" , StringComparison . OrdinalIgnoreCase ) )
{
return "/emby/emby" + path ;
}
return "emby/emby/" + path ;
}
2013-12-07 15:52:38 +00:00
private bool _disposed ;
private readonly object _disposeLock = new object ( ) ;
protected virtual void Dispose ( bool disposing )
{
if ( _disposed ) return ;
2016-12-28 06:08:18 +00:00
2013-12-07 15:52:38 +00:00
lock ( _disposeLock )
{
if ( _disposed ) return ;
2016-12-28 06:08:18 +00:00
_disposed = true ;
2013-12-07 15:52:38 +00:00
if ( disposing )
{
Stop ( ) ;
}
}
}
2018-09-12 17:26:21 +00:00
/// <summary>
/// Processes the web socket message received.
/// </summary>
/// <param name="result">The result.</param>
private Task ProcessWebSocketMessageReceived ( WebSocketMessageInfo result )
{
if ( _disposed )
{
return Task . CompletedTask ;
}
2018-12-20 12:11:26 +00:00
_logger . LogDebug ( "Websocket message received: {0}" , result . MessageType ) ;
2018-09-12 17:26:21 +00:00
var tasks = _webSocketListeners . Select ( i = > Task . Run ( async ( ) = >
{
try
{
await i . ProcessMessage ( result ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
2018-12-20 12:11:26 +00:00
_logger . LogError ( ex , "{0} failed processing WebSocket message {1}" , i . GetType ( ) . Name , result . MessageType ? ? string . Empty ) ;
2018-09-12 17:26:21 +00:00
}
} ) ) ;
return Task . WhenAll ( tasks ) ;
}
2017-02-13 02:06:54 +00:00
public void Dispose ( )
2013-12-07 15:52:38 +00:00
{
Dispose ( true ) ;
}
2018-09-12 17:26:21 +00:00
public void StartServer ( string [ ] urlPrefixes , IHttpListener httpListener )
2013-12-07 15:52:38 +00:00
{
2017-08-30 18:52:29 +00:00
UrlPrefixes = urlPrefixes ;
2018-09-12 17:26:21 +00:00
_listener = httpListener ;
2017-08-30 18:52:29 +00:00
_listener . WebSocketConnected = OnWebSocketConnected ;
_listener . ErrorHandler = ErrorHandler ;
_listener . RequestHandler = RequestHandler ;
_listener . Start ( UrlPrefixes ) ;
2013-12-07 15:52:38 +00:00
}
}
2018-12-28 15:48:26 +00:00
}