aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2017-02-13 16:05:04 -0500
committerGitHub <noreply@github.com>2017-02-13 16:05:04 -0500
commit252debd2813484dc67ce4a78558f80d16959d604 (patch)
tree0dc8b9a432b167e7db1fd5be16c20b10fec3b0cf /Emby.Server.Implementations
parent273aa822cfc37558883dbdd17647829abcf34758 (diff)
parent5508c388f54e2abe8abab8c0c2453c26a9670a0c (diff)
Merge pull request #2467 from MediaBrowser/dev
Dev
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj7
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs41
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs3
-rw-r--r--Emby.Server.Implementations/Services/HttpResult.cs1
-rw-r--r--Emby.Server.Implementations/Services/RequestHelper.cs14
-rw-r--r--Emby.Server.Implementations/Services/ResponseHelper.cs3
-rw-r--r--Emby.Server.Implementations/Services/ServiceController.cs51
-rw-r--r--Emby.Server.Implementations/Services/ServiceExec.cs5
-rw-r--r--Emby.Server.Implementations/Services/ServiceHandler.cs25
-rw-r--r--Emby.Server.Implementations/Services/ServicePath.cs563
-rw-r--r--Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs124
-rw-r--r--Emby.Server.Implementations/Services/UrlExtensions.cs33
12 files changed, 809 insertions, 61 deletions
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index b1601df05..fec0c2294 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -215,6 +215,7 @@
<Compile Include="Security\RegRecord.cs" />
<Compile Include="ServerManager\ServerManager.cs" />
<Compile Include="ServerManager\WebSocketConnection.cs" />
+ <Compile Include="Services\ServicePath.cs" />
<Compile Include="Services\ServiceMethod.cs" />
<Compile Include="Services\ResponseHelper.cs" />
<Compile Include="Services\HttpResult.cs" />
@@ -222,6 +223,8 @@
<Compile Include="Services\ServiceHandler.cs" />
<Compile Include="Services\ServiceController.cs" />
<Compile Include="Services\ServiceExec.cs" />
+ <Compile Include="Services\StringMapTypeDeserializer.cs" />
+ <Compile Include="Services\UrlExtensions.cs" />
<Compile Include="Session\HttpSessionController.cs" />
<Compile Include="Session\SessionManager.cs" />
<Compile Include="Session\SessionWebSocketListener.cs" />
@@ -308,10 +311,6 @@
<Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
<Name>MediaBrowser.Server.Implementations</Name>
</ProjectReference>
- <ProjectReference Include="..\ServiceStack\ServiceStack.csproj">
- <Project>{680a1709-25eb-4d52-a87f-ee03ffd94baa}</Project>
- <Name>ServiceStack</Name>
- </ProjectReference>
<ProjectReference Include="..\SocketHttpListener.Portable\SocketHttpListener.Portable.csproj">
<Project>{4f26d5d8-a7b0-42b3-ba42-7cb7d245934e}</Project>
<Name>SocketHttpListener.Portable</Name>
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 99ec146d7..c65289e13 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -2,7 +2,6 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
-using ServiceStack;
using System;
using System.Collections.Generic;
using System.IO;
@@ -29,7 +28,7 @@ using SocketHttpListener.Primitives;
namespace Emby.Server.Implementations.HttpServer
{
- public class HttpListenerHost : ServiceStackHost, IHttpServer
+ public class HttpListenerHost : IHttpServer, IDisposable
{
private string DefaultRedirectPath { get; set; }
@@ -62,7 +61,10 @@ namespace Emby.Server.Implementations.HttpServer
private readonly bool _enableDualModeSockets;
public List<Action<IRequest, IResponse, object>> RequestFilters { get; set; }
- private Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
+ public List<Action<IRequest, IResponse, object>> ResponseFilters { get; set; }
+
+ private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
+ public static HttpListenerHost Instance { get; protected set; }
public HttpListenerHost(IServerApplicationHost applicationHost,
ILogger logger,
@@ -71,6 +73,8 @@ namespace Emby.Server.Implementations.HttpServer
string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets)
: base()
{
+ Instance = this;
+
_appHost = applicationHost;
DefaultRedirectPath = defaultRedirectPath;
_networkManager = networkManager;
@@ -90,6 +94,7 @@ namespace Emby.Server.Implementations.HttpServer
_logger = logger;
RequestFilters = new List<Action<IRequest, IResponse, object>>();
+ ResponseFilters = new List<Action<IRequest, IResponse, object>>();
}
public string GlobalResponse { get; set; }
@@ -112,7 +117,7 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- public override object CreateInstance(Type type)
+ public object CreateInstance(Type type)
{
return _appHost.CreateInstance(type);
}
@@ -168,12 +173,12 @@ namespace Emby.Server.Implementations.HttpServer
private IHasRequestFilter[] GetRequestFilterAttributes(Type requestDtoType)
{
- var attributes = requestDtoType.AllAttributes().OfType<IHasRequestFilter>().ToList();
+ var attributes = requestDtoType.GetTypeInfo().GetCustomAttributes(true).OfType<IHasRequestFilter>().ToList();
var serviceType = GetServiceTypeByRequest(requestDtoType);
if (serviceType != null)
{
- attributes.AddRange(serviceType.AllAttributes().OfType<IHasRequestFilter>());
+ attributes.AddRange(serviceType.GetTypeInfo().GetCustomAttributes(true).OfType<IHasRequestFilter>());
}
attributes.Sort((x, y) => x.Priority - y.Priority);
@@ -611,7 +616,7 @@ namespace Emby.Server.Implementations.HttpServer
_logger.Error("Path parts empty for PathInfo: {0}, Url: {1}", pathInfo, httpReq.RawUrl);
return null;
}
-
+
string contentType;
var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, _logger, out contentType);
@@ -658,8 +663,6 @@ namespace Emby.Server.Implementations.HttpServer
_logger.Info("Calling ServiceStack AppHost.Init");
- Instance = this;
-
ServiceController.Init(this);
var requestFilters = _appHost.GetExports<IRequestFilter>().ToList();
@@ -668,12 +671,12 @@ namespace Emby.Server.Implementations.HttpServer
RequestFilters.Add(filter.Filter);
}
- GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse);
+ ResponseFilters.Add(new ResponseFilter(_logger).FilterResponse);
}
- public override RouteAttribute[] GetRouteAttributes(Type requestType)
+ public RouteAttribute[] GetRouteAttributes(Type requestType)
{
- var routes = requestType.AllAttributes<RouteAttribute>();
+ var routes = requestType.GetTypeInfo().GetCustomAttributes<RouteAttribute>(true).ToList();
var clone = routes.ToList();
foreach (var route in clone)
@@ -703,27 +706,27 @@ namespace Emby.Server.Implementations.HttpServer
return routes.ToArray();
}
- public override Func<string, object> GetParseFn(Type propertyType)
+ public Func<string, object> GetParseFn(Type propertyType)
{
return _funcParseFn(propertyType);
}
- public override void SerializeToJson(object o, Stream stream)
+ public void SerializeToJson(object o, Stream stream)
{
_jsonSerializer.SerializeToStream(o, stream);
}
- public override void SerializeToXml(object o, Stream stream)
+ public void SerializeToXml(object o, Stream stream)
{
_xmlSerializer.SerializeToStream(o, stream);
}
- public override object DeserializeXml(Type type, Stream stream)
+ public object DeserializeXml(Type type, Stream stream)
{
return _xmlSerializer.DeserializeFromStream(type, stream);
}
- public override object DeserializeJson(Type type, Stream stream)
+ public object DeserializeJson(Type type, Stream stream)
{
return _jsonSerializer.DeserializeFromStream(stream, type);
}
@@ -764,7 +767,7 @@ namespace Emby.Server.Implementations.HttpServer
{
if (_disposed) return;
- base.Dispose();
+ Dispose();
lock (_disposeLock)
{
@@ -779,7 +782,7 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- public override void Dispose()
+ public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index 3f756fc7a..6bfd83110 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -16,7 +16,6 @@ using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.Services;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
-using ServiceStack;
using IRequest = MediaBrowser.Model.Services.IRequest;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
using StreamWriter = Emby.Server.Implementations.HttpServer.StreamWriter;
@@ -204,7 +203,7 @@ namespace Emby.Server.Implementations.HttpServer
using (var ms = new MemoryStream())
{
var contentType = request.ResponseContentType;
- var writerFn = RequestHelper.GetResponseWriter(contentType);
+ var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
writerFn(dto, ms);
diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs
index 585c3e4f8..dfad09f7b 100644
--- a/Emby.Server.Implementations/Services/HttpResult.cs
+++ b/Emby.Server.Implementations/Services/HttpResult.cs
@@ -4,7 +4,6 @@ using System.Net;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
-using ServiceStack;
namespace Emby.Server.Implementations.Services
{
diff --git a/Emby.Server.Implementations/Services/RequestHelper.cs b/Emby.Server.Implementations/Services/RequestHelper.cs
index 8cfc3d089..7538d3102 100644
--- a/Emby.Server.Implementations/Services/RequestHelper.cs
+++ b/Emby.Server.Implementations/Services/RequestHelper.cs
@@ -1,40 +1,40 @@
using System;
using System.IO;
-using ServiceStack;
+using Emby.Server.Implementations.HttpServer;
namespace Emby.Server.Implementations.Services
{
public class RequestHelper
{
- public static Func<Type, Stream, object> GetRequestReader(string contentType)
+ public static Func<Type, Stream, object> GetRequestReader(HttpListenerHost host, string contentType)
{
switch (GetContentTypeWithoutEncoding(contentType))
{
case "application/xml":
case "text/xml":
case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
- return ServiceStackHost.Instance.DeserializeXml;
+ return host.DeserializeXml;
case "application/json":
case "text/json":
- return ServiceStackHost.Instance.DeserializeJson;
+ return host.DeserializeJson;
}
return null;
}
- public static Action<object, Stream> GetResponseWriter(string contentType)
+ public static Action<object, Stream> GetResponseWriter(HttpListenerHost host, string contentType)
{
switch (GetContentTypeWithoutEncoding(contentType))
{
case "application/xml":
case "text/xml":
case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
- return (o, s) => ServiceStackHost.Instance.SerializeToXml(o, s);
+ return host.SerializeToXml;
case "application/json":
case "text/json":
- return (o, s) => ServiceStackHost.Instance.SerializeToJson(o, s);
+ return host.SerializeToJson;
}
return null;
diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs
index 1af70ad7f..d4ce1cabf 100644
--- a/Emby.Server.Implementations/Services/ResponseHelper.cs
+++ b/Emby.Server.Implementations/Services/ResponseHelper.cs
@@ -5,6 +5,7 @@ using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Model.Services;
namespace Emby.Server.Implementations.Services
@@ -161,7 +162,7 @@ namespace Emby.Server.Implementations.Services
public static async Task WriteObject(IRequest request, object result, IResponse response)
{
var contentType = request.ResponseContentType;
- var serializer = RequestHelper.GetResponseWriter(contentType);
+ var serializer = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
using (var ms = new MemoryStream())
{
diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs
index 714a16df5..d283bf81f 100644
--- a/Emby.Server.Implementations/Services/ServiceController.cs
+++ b/Emby.Server.Implementations/Services/ServiceController.cs
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Services;
-using ServiceStack;
namespace Emby.Server.Implementations.Services
{
@@ -53,32 +52,62 @@ namespace Emby.Server.Implementations.Services
ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions);
- var returnMarker = requestType.GetTypeWithGenericTypeDefinitionOf(typeof(IReturn<>));
+ var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>));
var responseType = returnMarker != null ?
GetGenericArguments(returnMarker)[0]
: mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ?
mi.ReturnType
: Type.GetType(requestType.FullName + "Response");
- RegisterRestPaths(requestType);
+ RegisterRestPaths(appHost, requestType);
appHost.AddServiceInfo(serviceType, requestType, responseType);
}
}
+ private static Type GetTypeWithGenericTypeDefinitionOf(Type type, Type genericTypeDefinition)
+ {
+ foreach (var t in type.GetTypeInfo().ImplementedInterfaces)
+ {
+ if (t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == genericTypeDefinition)
+ {
+ return t;
+ }
+ }
+
+ var genericType = FirstGenericType(type);
+ if (genericType != null && genericType.GetGenericTypeDefinition() == genericTypeDefinition)
+ {
+ return genericType;
+ }
+
+ return null;
+ }
+
+ public static Type FirstGenericType(Type type)
+ {
+ while (type != null)
+ {
+ if (type.GetTypeInfo().IsGenericType)
+ return type;
+
+ type = type.GetTypeInfo().BaseType;
+ }
+ return null;
+ }
+
public readonly Dictionary<string, List<RestPath>> RestPathMap = new Dictionary<string, List<RestPath>>(StringComparer.OrdinalIgnoreCase);
- public void RegisterRestPaths(Type requestType)
+ public void RegisterRestPaths(HttpListenerHost appHost, Type requestType)
{
- var appHost = ServiceStackHost.Instance;
var attrs = appHost.GetRouteAttributes(requestType);
- foreach (MediaBrowser.Model.Services.RouteAttribute attr in attrs)
+ foreach (RouteAttribute attr in attrs)
{
- var restPath = new RestPath(requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes);
+ var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes);
if (!restPath.IsValid)
throw new NotSupportedException(string.Format(
- "RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetOperationName()));
+ "RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetMethodName()));
RegisterRestPath(restPath);
}
@@ -89,10 +118,10 @@ namespace Emby.Server.Implementations.Services
public void RegisterRestPath(RestPath restPath)
{
if (!restPath.Path.StartsWith("/"))
- throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetOperationName()));
+ throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName()));
if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. " +
- "See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetOperationName()));
+ "See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetMethodName()));
List<RestPath> pathsAtFirstMatch;
if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch))
@@ -180,7 +209,7 @@ namespace Emby.Server.Implementations.Services
req.Dto = requestDto;
//Executes the service and returns the result
- var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetOperationName()).ConfigureAwait(false);
+ var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()).ConfigureAwait(false);
return response;
}
diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs
index 59af3078f..e0b5e69c0 100644
--- a/Emby.Server.Implementations/Services/ServiceExec.cs
+++ b/Emby.Server.Implementations/Services/ServiceExec.cs
@@ -5,7 +5,6 @@ using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
-using ServiceStack;
namespace Emby.Server.Implementations.Services
{
@@ -84,7 +83,7 @@ namespace Emby.Server.Implementations.Services
}
var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLower();
- throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetOperationName(), expectedMethodName, serviceType.GetOperationName()));
+ throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetMethodName(), expectedMethodName, serviceType.GetMethodName()));
}
public static List<ServiceMethod> Reset(Type serviceType)
@@ -99,7 +98,7 @@ namespace Emby.Server.Implementations.Services
var requestType = args[0].ParameterType;
var actionCtx = new ServiceMethod
{
- Id = ServiceMethod.Key(serviceType, actionName, requestType.GetOperationName())
+ Id = ServiceMethod.Key(serviceType, actionName, requestType.GetMethodName())
};
try
diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs
index 003776f9c..8b59b4843 100644
--- a/Emby.Server.Implementations/Services/ServiceHandler.cs
+++ b/Emby.Server.Implementations/Services/ServiceHandler.cs
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Services;
-using ServiceStack;
namespace Emby.Server.Implementations.Services
{
@@ -59,17 +58,17 @@ namespace Emby.Server.Implementations.Services
}
}
- protected static object CreateContentTypeRequest(IRequest httpReq, Type requestType, string contentType)
+ protected static object CreateContentTypeRequest(HttpListenerHost host, IRequest httpReq, Type requestType, string contentType)
{
if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0)
{
- var deserializer = RequestHelper.GetRequestReader(contentType);
+ var deserializer = RequestHelper.GetRequestReader(host, contentType);
if (deserializer != null)
{
return deserializer(requestType, httpReq.InputStream);
}
}
- return ServiceStackHost.Instance.CreateInstance(requestType); //Return an empty DTO, even for empty request bodies
+ return host.CreateInstance(requestType);
}
public static RestPath FindMatchingRestPath(string httpMethod, string pathInfo, ILogger logger, out string contentType)
@@ -137,7 +136,7 @@ namespace Emby.Server.Implementations.Services
if (ResponseContentType != null)
httpReq.ResponseContentType = ResponseContentType;
- var request = httpReq.Dto = CreateRequest(httpReq, restPath, logger);
+ var request = httpReq.Dto = CreateRequest(appHost, httpReq, restPath, logger);
appHost.ApplyRequestFilters(httpReq, httpRes, request);
@@ -146,7 +145,7 @@ namespace Emby.Server.Implementations.Services
var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false);
// Apply response filters
- foreach (var responseFilter in appHost.GlobalResponseFilters)
+ foreach (var responseFilter in appHost.ResponseFilters)
{
responseFilter(httpReq, httpRes, response);
}
@@ -154,18 +153,18 @@ namespace Emby.Server.Implementations.Services
await ResponseHelper.WriteToResponse(httpRes, httpReq, response).ConfigureAwait(false);
}
- public static object CreateRequest(IRequest httpReq, RestPath restPath, ILogger logger)
+ public static object CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger)
{
var requestType = restPath.RequestType;
if (RequireqRequestStream(requestType))
{
// Used by IRequiresRequestStream
- return CreateRequiresRequestStreamRequest(httpReq, requestType);
+ return CreateRequiresRequestStreamRequest(host, httpReq, requestType);
}
var requestParams = GetFlattenedRequestParams(httpReq);
- return CreateRequest(httpReq, restPath, requestParams);
+ return CreateRequest(host, httpReq, restPath, requestParams);
}
private static bool RequireqRequestStream(Type requestType)
@@ -175,19 +174,19 @@ namespace Emby.Server.Implementations.Services
return requiresRequestStreamTypeInfo.IsAssignableFrom(requestType.GetTypeInfo());
}
- private static IRequiresRequestStream CreateRequiresRequestStreamRequest(IRequest req, Type requestType)
+ private static IRequiresRequestStream CreateRequiresRequestStreamRequest(HttpListenerHost host, IRequest req, Type requestType)
{
var restPath = GetRoute(req);
- var request = ServiceHandler.CreateRequest(req, restPath, GetRequestParams(req), ServiceStackHost.Instance.CreateInstance(requestType));
+ var request = ServiceHandler.CreateRequest(req, restPath, GetRequestParams(req), host.CreateInstance(requestType));
var rawReq = (IRequiresRequestStream)request;
rawReq.RequestStream = req.InputStream;
return rawReq;
}
- public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams)
+ public static object CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams)
{
- var requestDto = CreateContentTypeRequest(httpReq, restPath.RequestType, httpReq.ContentType);
+ var requestDto = CreateContentTypeRequest(host, httpReq, restPath.RequestType, httpReq.ContentType);
return CreateRequest(httpReq, restPath, requestParams, requestDto);
}
diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs
new file mode 100644
index 000000000..255b20919
--- /dev/null
+++ b/Emby.Server.Implementations/Services/ServicePath.cs
@@ -0,0 +1,563 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using MediaBrowser.Model.Logging;
+
+namespace Emby.Server.Implementations.Services
+{
+ public class RestPath
+ {
+ private const string WildCard = "*";
+ private const char WildCardChar = '*';
+ private const string PathSeperator = "/";
+ private const char PathSeperatorChar = '/';
+ private const char ComponentSeperator = '.';
+ private const string VariablePrefix = "{";
+
+ readonly bool[] componentsWithSeparators;
+
+ private readonly string restPath;
+ private readonly string allowedVerbs;
+ private readonly bool allowsAllVerbs;
+ public bool IsWildCardPath { get; private set; }
+
+ private readonly string[] literalsToMatch;
+
+ private readonly string[] variablesNames;
+
+ private readonly bool[] isWildcard;
+ private readonly int wildcardCount = 0;
+
+ public int VariableArgsCount { get; set; }
+
+ /// <summary>
+ /// The number of segments separated by '/' determinable by path.Split('/').Length
+ /// e.g. /path/to/here.ext == 3
+ /// </summary>
+ public int PathComponentsCount { get; set; }
+
+ /// <summary>
+ /// The total number of segments after subparts have been exploded ('.')
+ /// e.g. /path/to/here.ext == 4
+ /// </summary>
+ public int TotalComponentsCount { get; set; }
+
+ public string[] Verbs
+ {
+ get
+ {
+ return allowsAllVerbs
+ ? new[] { "ANY" }
+ : AllowedVerbs.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+ }
+
+ public Type RequestType { get; private set; }
+
+ public string Path { get { return this.restPath; } }
+
+ public string Summary { get; private set; }
+
+ public string Notes { get; private set; }
+
+ public bool AllowsAllVerbs { get { return this.allowsAllVerbs; } }
+
+ public string AllowedVerbs { get { return this.allowedVerbs; } }
+
+ public int Priority { get; set; } //passed back to RouteAttribute
+
+ public static string[] GetPathPartsForMatching(string pathInfo)
+ {
+ var parts = pathInfo.ToLower().Split(PathSeperatorChar)
+ .Where(x => !String.IsNullOrEmpty(x)).ToArray();
+ return parts;
+ }
+
+ public static List<string> GetFirstMatchHashKeys(string[] pathPartsForMatching)
+ {
+ var hashPrefix = pathPartsForMatching.Length + PathSeperator;
+ return GetPotentialMatchesWithPrefix(hashPrefix, pathPartsForMatching);
+ }
+
+ public static List<string> GetFirstMatchWildCardHashKeys(string[] pathPartsForMatching)
+ {
+ const string hashPrefix = WildCard + PathSeperator;
+ return GetPotentialMatchesWithPrefix(hashPrefix, pathPartsForMatching);
+ }
+
+ private static List<string> GetPotentialMatchesWithPrefix(string hashPrefix, string[] pathPartsForMatching)
+ {
+ var list = new List<string>();
+
+ foreach (var part in pathPartsForMatching)
+ {
+ list.Add(hashPrefix + part);
+
+ var subParts = part.Split(ComponentSeperator);
+ if (subParts.Length == 1) continue;
+
+ foreach (var subPart in subParts)
+ {
+ list.Add(hashPrefix + subPart);
+ }
+ }
+
+ return list;
+ }
+
+ public RestPath(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type requestType, string path, string verbs, string summary = null, string notes = null)
+ {
+ this.RequestType = requestType;
+ this.Summary = summary;
+ this.Notes = notes;
+ this.restPath = path;
+
+ this.allowsAllVerbs = verbs == null || String.Equals(verbs, WildCard, StringComparison.OrdinalIgnoreCase);
+ if (!this.allowsAllVerbs)
+ {
+ this.allowedVerbs = verbs.ToUpper();
+ }
+
+ var componentsList = new List<string>();
+
+ //We only split on '.' if the restPath has them. Allows for /{action}.{type}
+ var hasSeparators = new List<bool>();
+ foreach (var component in this.restPath.Split(PathSeperatorChar))
+ {
+ if (String.IsNullOrEmpty(component)) continue;
+
+ if (StringContains(component, VariablePrefix)
+ && component.IndexOf(ComponentSeperator) != -1)
+ {
+ hasSeparators.Add(true);
+ componentsList.AddRange(component.Split(ComponentSeperator));
+ }
+ else
+ {
+ hasSeparators.Add(false);
+ componentsList.Add(component);
+ }
+ }
+
+ var components = componentsList.ToArray();
+ this.TotalComponentsCount = components.Length;
+
+ this.literalsToMatch = new string[this.TotalComponentsCount];
+ this.variablesNames = new string[this.TotalComponentsCount];
+ this.isWildcard = new bool[this.TotalComponentsCount];
+ this.componentsWithSeparators = hasSeparators.ToArray();
+ this.PathComponentsCount = this.componentsWithSeparators.Length;
+ string firstLiteralMatch = null;
+
+ var sbHashKey = new StringBuilder();
+ for (var i = 0; i < components.Length; i++)
+ {
+ var component = components[i];
+
+ if (component.StartsWith(VariablePrefix))
+ {
+ var variableName = component.Substring(1, component.Length - 2);
+ if (variableName[variableName.Length - 1] == WildCardChar)
+ {
+ this.isWildcard[i] = true;
+ variableName = variableName.Substring(0, variableName.Length - 1);
+ }
+ this.variablesNames[i] = variableName;
+ this.VariableArgsCount++;
+ }
+ else
+ {
+ this.literalsToMatch[i] = component.ToLower();
+ sbHashKey.Append(i + PathSeperatorChar.ToString() + this.literalsToMatch);
+
+ if (firstLiteralMatch == null)
+ {
+ firstLiteralMatch = this.literalsToMatch[i];
+ }
+ }
+ }
+
+ for (var i = 0; i < components.Length - 1; i++)
+ {
+ if (!this.isWildcard[i]) continue;
+ if (this.literalsToMatch[i + 1] == null)
+ {
+ throw new ArgumentException(
+ "A wildcard path component must be at the end of the path or followed by a literal path component.");
+ }
+ }
+
+ this.wildcardCount = this.isWildcard.Count(x => x);
+ this.IsWildCardPath = this.wildcardCount > 0;
+
+ this.FirstMatchHashKey = !this.IsWildCardPath
+ ? this.PathComponentsCount + PathSeperator + firstLiteralMatch
+ : WildCardChar + PathSeperator + firstLiteralMatch;
+
+ this.IsValid = sbHashKey.Length > 0;
+ this.UniqueMatchHashKey = sbHashKey.ToString();
+
+ this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType);
+ RegisterCaseInsenstivePropertyNameMappings();
+ }
+
+ private void RegisterCaseInsenstivePropertyNameMappings()
+ {
+ foreach (var propertyInfo in GetSerializableProperties(RequestType))
+ {
+ var propertyName = propertyInfo.Name;
+ propertyNamesMap.Add(propertyName.ToLower(), propertyName);
+ }
+ }
+
+ internal static string[] IgnoreAttributesNamed = new[] {
+ "IgnoreDataMemberAttribute",
+ "JsonIgnoreAttribute"
+ };
+
+
+ private static List<Type> _excludeTypes = new List<Type> { typeof(Stream) };
+
+ internal static PropertyInfo[] GetSerializableProperties(Type type)
+ {
+ var properties = GetPublicProperties(type);
+ var readableProperties = properties.Where(x => x.GetMethod != null);
+
+ // else return those properties that are not decorated with IgnoreDataMember
+ return readableProperties
+ .Where(prop => prop.GetCustomAttributes(true)
+ .All(attr =>
+ {
+ var name = attr.GetType().Name;
+ return !IgnoreAttributesNamed.Contains(name);
+ }))
+ .Where(prop => !_excludeTypes.Contains(prop.PropertyType))
+ .ToArray();
+ }
+
+ private static PropertyInfo[] GetPublicProperties(Type type)
+ {
+ if (type.GetTypeInfo().IsInterface)
+ {
+ var propertyInfos = new List<PropertyInfo>();
+
+ var considered = new List<Type>();
+ var queue = new Queue<Type>();
+ considered.Add(type);
+ queue.Enqueue(type);
+
+ while (queue.Count > 0)
+ {
+ var subType = queue.Dequeue();
+ foreach (var subInterface in subType.GetTypeInfo().ImplementedInterfaces)
+ {
+ if (considered.Contains(subInterface)) continue;
+
+ considered.Add(subInterface);
+ queue.Enqueue(subInterface);
+ }
+
+ var typeProperties = GetTypesPublicProperties(subType);
+
+ var newPropertyInfos = typeProperties
+ .Where(x => !propertyInfos.Contains(x));
+
+ propertyInfos.InsertRange(0, newPropertyInfos);
+ }
+
+ return propertyInfos.ToArray();
+ }
+
+ return GetTypesPublicProperties(type)
+ .Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties
+ .ToArray();
+ }
+
+ private static PropertyInfo[] GetTypesPublicProperties(Type subType)
+ {
+ var pis = new List<PropertyInfo>();
+ foreach (var pi in subType.GetRuntimeProperties())
+ {
+ var mi = pi.GetMethod ?? pi.SetMethod;
+ if (mi != null && mi.IsStatic) continue;
+ pis.Add(pi);
+ }
+ return pis.ToArray();
+ }
+
+
+ public bool IsValid { get; set; }
+
+ /// <summary>
+ /// Provide for quick lookups based on hashes that can be determined from a request url
+ /// </summary>
+ public string FirstMatchHashKey { get; private set; }
+
+ public string UniqueMatchHashKey { get; private set; }
+
+ private readonly StringMapTypeDeserializer typeDeserializer;
+
+ private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>();
+
+ public int MatchScore(string httpMethod, string[] withPathInfoParts, ILogger logger)
+ {
+ int wildcardMatchCount;
+ var isMatch = IsMatch(httpMethod, withPathInfoParts, logger, out wildcardMatchCount);
+ if (!isMatch)
+ {
+ return -1;
+ }
+
+ var score = 0;
+
+ //Routes with least wildcard matches get the highest score
+ score += Math.Max((100 - wildcardMatchCount), 1) * 1000;
+
+ //Routes with less variable (and more literal) matches
+ score += Math.Max((10 - VariableArgsCount), 1) * 100;
+
+ //Exact verb match is better than ANY
+ var exactVerb = String.Equals(httpMethod, AllowedVerbs, StringComparison.OrdinalIgnoreCase);
+ score += exactVerb ? 10 : 1;
+
+ return score;
+ }
+
+ private bool StringContains(string str1, string str2)
+ {
+ return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) != -1;
+ }
+
+ /// <summary>
+ /// For performance withPathInfoParts should already be a lower case string
+ /// to minimize redundant matching operations.
+ /// </summary>
+ public bool IsMatch(string httpMethod, string[] withPathInfoParts, ILogger logger, out int wildcardMatchCount)
+ {
+ wildcardMatchCount = 0;
+
+ if (withPathInfoParts.Length != this.PathComponentsCount && !this.IsWildCardPath)
+ {
+ //logger.Info("withPathInfoParts mismatch for {0} for {1}", httpMethod, string.Join("/", withPathInfoParts));
+ return false;
+ }
+
+ if (!this.allowsAllVerbs && !StringContains(this.allowedVerbs, httpMethod))
+ {
+ //logger.Info("allowsAllVerbs mismatch for {0} for {1} allowedverbs {2}", httpMethod, string.Join("/", withPathInfoParts), this.allowedVerbs);
+ return false;
+ }
+
+ if (!ExplodeComponents(ref withPathInfoParts))
+ {
+ //logger.Info("ExplodeComponents mismatch for {0} for {1}", httpMethod, string.Join("/", withPathInfoParts));
+ return false;
+ }
+
+ if (this.TotalComponentsCount != withPathInfoParts.Length && !this.IsWildCardPath)
+ {
+ //logger.Info("TotalComponentsCount mismatch for {0} for {1}", httpMethod, string.Join("/", withPathInfoParts));
+ return false;
+ }
+
+ int pathIx = 0;
+ for (var i = 0; i < this.TotalComponentsCount; i++)
+ {
+ if (this.isWildcard[i])
+ {
+ if (i < this.TotalComponentsCount - 1)
+ {
+ // Continue to consume up until a match with the next literal
+ while (pathIx < withPathInfoParts.Length && !LiteralsEqual(withPathInfoParts[pathIx], this.literalsToMatch[i + 1]))
+ {
+ pathIx++;
+ wildcardMatchCount++;
+ }
+
+ // Ensure there are still enough parts left to match the remainder
+ if ((withPathInfoParts.Length - pathIx) < (this.TotalComponentsCount - i - 1))
+ {
+ //logger.Info("withPathInfoParts length mismatch for {0} for {1}", httpMethod, string.Join("/", withPathInfoParts));
+ return false;
+ }
+ }
+ else
+ {
+ // A wildcard at the end matches the remainder of path
+ wildcardMatchCount += withPathInfoParts.Length - pathIx;
+ pathIx = withPathInfoParts.Length;
+ }
+ }
+ else
+ {
+ var literalToMatch = this.literalsToMatch[i];
+ if (literalToMatch == null)
+ {
+ // Matching an ordinary (non-wildcard) variable consumes a single part
+ pathIx++;
+ continue;
+ }
+
+ if (withPathInfoParts.Length <= pathIx || !LiteralsEqual(withPathInfoParts[pathIx], literalToMatch))
+ {
+ //logger.Info("withPathInfoParts2 length mismatch for {0} for {1}. not equals: {2} != {3}.", httpMethod, string.Join("/", withPathInfoParts), withPathInfoParts[pathIx], literalToMatch);
+ return false;
+ }
+ pathIx++;
+ }
+ }
+
+ return pathIx == withPathInfoParts.Length;
+ }
+
+ private bool LiteralsEqual(string str1, string str2)
+ {
+ // Most cases
+ if (String.Equals(str1, str2, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ // Handle turkish i
+ str1 = str1.ToUpperInvariant();
+ str2 = str2.ToUpperInvariant();
+
+ // Invariant IgnoreCase would probably be better but it's not available in PCL
+ return String.Equals(str1, str2, StringComparison.CurrentCultureIgnoreCase);
+ }
+
+ private bool ExplodeComponents(ref string[] withPathInfoParts)
+ {
+ var totalComponents = new List<string>();
+ for (var i = 0; i < withPathInfoParts.Length; i++)
+ {
+ var component = withPathInfoParts[i];
+ if (String.IsNullOrEmpty(component)) continue;
+
+ if (this.PathComponentsCount != this.TotalComponentsCount
+ && this.componentsWithSeparators[i])
+ {
+ var subComponents = component.Split(ComponentSeperator);
+ if (subComponents.Length < 2) return false;
+ totalComponents.AddRange(subComponents);
+ }
+ else
+ {
+ totalComponents.Add(component);
+ }
+ }
+
+ withPathInfoParts = totalComponents.ToArray();
+ return true;
+ }
+
+ public object CreateRequest(string pathInfo, Dictionary<string, string> queryStringAndFormData, object fromInstance)
+ {
+ var requestComponents = pathInfo.Split(PathSeperatorChar)
+ .Where(x => !String.IsNullOrEmpty(x)).ToArray();
+
+ ExplodeComponents(ref requestComponents);
+
+ if (requestComponents.Length != this.TotalComponentsCount)
+ {
+ var isValidWildCardPath = this.IsWildCardPath
+ && requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
+
+ if (!isValidWildCardPath)
+ throw new ArgumentException(String.Format(
+ "Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
+ pathInfo, this.restPath));
+ }
+
+ var requestKeyValuesMap = new Dictionary<string, string>();
+ var pathIx = 0;
+ for (var i = 0; i < this.TotalComponentsCount; i++)
+ {
+ var variableName = this.variablesNames[i];
+ if (variableName == null)
+ {
+ pathIx++;
+ continue;
+ }
+
+ string propertyNameOnRequest;
+ if (!this.propertyNamesMap.TryGetValue(variableName.ToLower(), out propertyNameOnRequest))
+ {
+ if (String.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase))
+ {
+ pathIx++;
+ continue;
+ }
+
+ throw new ArgumentException("Could not find property "
+ + variableName + " on " + RequestType.GetMethodName());
+ }
+
+ var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; //wildcard has arg mismatch
+ if (value != null && this.isWildcard[i])
+ {
+ if (i == this.TotalComponentsCount - 1)
+ {
+ // Wildcard at end of path definition consumes all the rest
+ var sb = new StringBuilder();
+ sb.Append(value);
+ for (var j = pathIx + 1; j < requestComponents.Length; j++)
+ {
+ sb.Append(PathSeperatorChar + requestComponents[j]);
+ }
+ value = sb.ToString();
+ }
+ else
+ {
+ // Wildcard in middle of path definition consumes up until it
+ // hits a match for the next element in the definition (which must be a literal)
+ // It may consume 0 or more path parts
+ var stopLiteral = i == this.TotalComponentsCount - 1 ? null : this.literalsToMatch[i + 1];
+ if (!String.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
+ {
+ var sb = new StringBuilder();
+ sb.Append(value);
+ pathIx++;
+ while (!String.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
+ {
+ sb.Append(PathSeperatorChar + requestComponents[pathIx++]);
+ }
+ value = sb.ToString();
+ }
+ else
+ {
+ value = null;
+ }
+ }
+ }
+ else
+ {
+ // Variable consumes single path item
+ pathIx++;
+ }
+
+ requestKeyValuesMap[propertyNameOnRequest] = value;
+ }
+
+ if (queryStringAndFormData != null)
+ {
+ //Query String and form data can override variable path matches
+ //path variables < query string < form data
+ foreach (var name in queryStringAndFormData)
+ {
+ requestKeyValuesMap[name.Key] = name.Value;
+ }
+ }
+
+ return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap);
+ }
+
+ public override int GetHashCode()
+ {
+ return UniqueMatchHashKey.GetHashCode();
+ }
+ }
+} \ No newline at end of file
diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
new file mode 100644
index 000000000..fc1cf4ed9
--- /dev/null
+++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Emby.Server.Implementations.Services
+{
+ /// <summary>
+ /// Serializer cache of delegates required to create a type from a string map (e.g. for REST urls)
+ /// </summary>
+ public class StringMapTypeDeserializer
+ {
+ internal class PropertySerializerEntry
+ {
+ public PropertySerializerEntry(Action<object,object> propertySetFn, Func<string, object> propertyParseStringFn)
+ {
+ PropertySetFn = propertySetFn;
+ PropertyParseStringFn = propertyParseStringFn;
+ }
+
+ public Action<object, object> PropertySetFn;
+ public Func<string,object> PropertyParseStringFn;
+ public Type PropertyType;
+ }
+
+ private readonly Type type;
+ private readonly Dictionary<string, PropertySerializerEntry> propertySetterMap
+ = new Dictionary<string, PropertySerializerEntry>(StringComparer.OrdinalIgnoreCase);
+
+ public Func<string, object> GetParseFn(Type propertyType)
+ {
+ if (propertyType == typeof(string))
+ return s => s;
+
+ return _GetParseFn(propertyType);
+ }
+
+ private readonly Func<Type, object> _CreateInstanceFn;
+ private readonly Func<Type, Func<string, object>> _GetParseFn;
+
+ public StringMapTypeDeserializer(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type type)
+ {
+ _CreateInstanceFn = createInstanceFn;
+ _GetParseFn = getParseFn;
+ this.type = type;
+
+ foreach (var propertyInfo in RestPath.GetSerializableProperties(type))
+ {
+ var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo);
+ var propertyType = propertyInfo.PropertyType;
+ var propertyParseStringFn = GetParseFn(propertyType);
+ var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType };
+
+ propertySetterMap[propertyInfo.Name] = propertySerializer;
+ }
+ }
+
+ public object PopulateFromMap(object instance, IDictionary<string, string> keyValuePairs)
+ {
+ string propertyName = null;
+ string propertyTextValue = null;
+ PropertySerializerEntry propertySerializerEntry = null;
+
+ if (instance == null)
+ instance = _CreateInstanceFn(type);
+
+ foreach (var pair in keyValuePairs.Where(x => !string.IsNullOrEmpty(x.Value)))
+ {
+ propertyName = pair.Key;
+ propertyTextValue = pair.Value;
+
+ if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry))
+ {
+ if (propertyName == "v")
+ {
+ continue;
+ }
+
+ continue;
+ }
+
+ if (propertySerializerEntry.PropertySetFn == null)
+ {
+ continue;
+ }
+
+ if (propertySerializerEntry.PropertyType == typeof(bool))
+ {
+ //InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value
+ propertyTextValue = LeftPart(propertyTextValue, ',');
+ }
+
+ var value = propertySerializerEntry.PropertyParseStringFn(propertyTextValue);
+ if (value == null)
+ {
+ continue;
+ }
+ propertySerializerEntry.PropertySetFn(instance, value);
+ }
+
+ return instance;
+ }
+
+ public static string LeftPart(string strVal, char needle)
+ {
+ if (strVal == null) return null;
+ var pos = strVal.IndexOf(needle);
+ return pos == -1
+ ? strVal
+ : strVal.Substring(0, pos);
+ }
+ }
+
+ internal class TypeAccessor
+ {
+ public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo)
+ {
+ if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Any()) return null;
+
+ var setMethodInfo = propertyInfo.SetMethod;
+ return (instance, value) => setMethodInfo.Invoke(instance, new[] { value });
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs
new file mode 100644
index 000000000..c7346789a
--- /dev/null
+++ b/Emby.Server.Implementations/Services/UrlExtensions.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace Emby.Server.Implementations.Services
+{
+ /// <summary>
+ /// Donated by Ivan Korneliuk from his post:
+ /// http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html
+ ///
+ /// Modified to only allow using routes matching the supplied HTTP Verb
+ /// </summary>
+ public static class UrlExtensions
+ {
+ public static string GetMethodName(this Type type)
+ {
+ var typeName = type.FullName != null //can be null, e.g. generic types
+ ? LeftPart(type.FullName, "[[") //Generic Fullname
+ .Replace(type.Namespace + ".", "") //Trim Namespaces
+ .Replace("+", ".") //Convert nested into normal type
+ : type.Name;
+
+ return type.IsGenericParameter ? "'" + typeName : typeName;
+ }
+
+ public static string LeftPart(string strVal, string needle)
+ {
+ if (strVal == null) return null;
+ var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase);
+ return pos == -1
+ ? strVal
+ : strVal.Substring(0, pos);
+ }
+ }
+} \ No newline at end of file