using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.Services { public static class ServiceExecExtensions { public static string[] AllVerbs = new[] { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616 "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518 "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT", "MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY", // RFC 3253 "ORDERPATCH", // RFC 3648 "ACL", // RFC 3744 "PATCH", // https://datatracker.ietf.org/doc/draft-dusseault-http-patch/ "SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/ "BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY", "POLL", "SUBSCRIBE", "UNSUBSCRIBE" }; public static List GetActions(this Type serviceType) { var list = new List(); foreach (var mi in serviceType.GetRuntimeMethods()) { if (!mi.IsPublic) { continue; } if (mi.IsStatic) { continue; } if (mi.GetParameters().Length != 1) continue; var actionName = mi.Name; if (!AllVerbs.Contains(actionName, StringComparer.OrdinalIgnoreCase)) continue; list.Add(mi); } return list; } } internal static class ServiceExecGeneral { private static Dictionary execMap = new Dictionary(); public static void CreateServiceRunnersFor(Type requestType, List actions) { foreach (var actionCtx in actions) { if (execMap.ContainsKey(actionCtx.Id)) continue; execMap[actionCtx.Id] = actionCtx; } } public static Task Execute(Type serviceType, IRequest request, object instance, object requestDto, string requestName) { var actionName = request.Verb ?? "POST"; if (execMap.TryGetValue(ServiceMethod.Key(serviceType, actionName, requestName), out ServiceMethod actionContext)) { if (actionContext.RequestFilters != null) { foreach (var requestFilter in actionContext.RequestFilters) { requestFilter.RequestFilter(request, request.Response, requestDto); if (request.Response.HasStarted) { Task.FromResult(null); } } } var response = actionContext.ServiceAction(instance, requestDto); if (response is Task taskResponse) { return GetTaskResult(taskResponse); } return Task.FromResult(response); } var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLowerInvariant(); throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetMethodName(), expectedMethodName, serviceType.GetMethodName())); } private static async Task GetTaskResult(Task task) { try { if (task is Task taskObject) { return await taskObject.ConfigureAwait(false); } await task.ConfigureAwait(false); var type = task.GetType().GetTypeInfo(); if (!type.IsGenericType) { return null; } var resultProperty = type.GetDeclaredProperty("Result"); if (resultProperty == null) { return null; } var result = resultProperty.GetValue(task); // hack alert if (result.GetType().Name.IndexOf("voidtaskresult", StringComparison.OrdinalIgnoreCase) != -1) { return null; } return result; } catch (TypeAccessException) { return null; // return null for void Task's } } public static List Reset(Type serviceType) { var actions = new List(); foreach (var mi in serviceType.GetActions()) { var actionName = mi.Name; var args = mi.GetParameters(); var requestType = args[0].ParameterType; var actionCtx = new ServiceMethod { Id = ServiceMethod.Key(serviceType, actionName, requestType.GetMethodName()) }; actionCtx.ServiceAction = CreateExecFn(serviceType, requestType, mi); var reqFilters = new List(); foreach (var attr in mi.GetCustomAttributes(true)) { if (attr is IHasRequestFilter hasReqFilter) { reqFilters.Add(hasReqFilter); } } if (reqFilters.Count > 0) { actionCtx.RequestFilters = reqFilters.OrderBy(i => i.Priority).ToArray(); } actions.Add(actionCtx); } return actions; } private static ActionInvokerFn CreateExecFn(Type serviceType, Type requestType, MethodInfo mi) { var serviceParam = Expression.Parameter(typeof(object), "serviceObj"); var serviceStrong = Expression.Convert(serviceParam, serviceType); var requestDtoParam = Expression.Parameter(typeof(object), "requestDto"); var requestDtoStrong = Expression.Convert(requestDtoParam, requestType); Expression callExecute = Expression.Call( serviceStrong, mi, requestDtoStrong); if (mi.ReturnType != typeof(void)) { var executeFunc = Expression.Lambda( callExecute, serviceParam, requestDtoParam).Compile(); return executeFunc; } else { var executeFunc = Expression.Lambda( callExecute, serviceParam, requestDtoParam).Compile(); return (service, request) => { executeFunc(service, request); return null; }; } } } }