using System; using System.Collections.Generic; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Services { public class ServiceHandler { protected static Task CreateContentTypeRequest(HttpListenerHost host, IRequest httpReq, Type requestType, string contentType) { if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0) { var deserializer = RequestHelper.GetRequestReader(host, contentType); if (deserializer != null) { return deserializer(requestType, httpReq.InputStream); } } return Task.FromResult(host.CreateInstance(requestType)); } public static RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType) { pathInfo = GetSanitizedPathInfo(pathInfo, out contentType); return ServiceController.Instance.GetRestPathForRequest(httpMethod, pathInfo); } public static string GetSanitizedPathInfo(string pathInfo, out string contentType) { contentType = null; var pos = pathInfo.LastIndexOf('.'); if (pos >= 0) { var format = pathInfo.Substring(pos + 1); contentType = GetFormatContentType(format); if (contentType != null) { pathInfo = pathInfo.Substring(0, pos); } } return pathInfo; } private static string GetFormatContentType(string format) { //built-in formats if (format == "json") return "application/json"; if (format == "xml") return "application/xml"; return null; } public RestPath GetRestPath(string httpMethod, string pathInfo) { if (this.RestPath == null) { this.RestPath = FindMatchingRestPath(httpMethod, pathInfo, out string contentType); if (contentType != null) ResponseContentType = contentType; } return this.RestPath; } public RestPath RestPath { get; set; } // Set from SSHHF.GetHandlerForPathInfo() public string ResponseContentType { get; set; } public async Task ProcessRequestAsync(HttpListenerHost appHost, IRequest httpReq, IResponse httpRes, ILogger logger, string operationName, CancellationToken cancellationToken) { var restPath = GetRestPath(httpReq.Verb, httpReq.PathInfo); if (restPath == null) { throw new NotSupportedException("No RestPath found for: " + httpReq.Verb + " " + httpReq.PathInfo); } SetRoute(httpReq, restPath); if (ResponseContentType != null) httpReq.ResponseContentType = ResponseContentType; var request = httpReq.Dto = await CreateRequest(appHost, httpReq, restPath, logger).ConfigureAwait(false); appHost.ApplyRequestFilters(httpReq, httpRes, request); var response = await appHost.ServiceController.Execute(appHost, request, httpReq).ConfigureAwait(false); // Apply response filters foreach (var responseFilter in appHost.ResponseFilters) { responseFilter(httpReq, httpRes, response); } await ResponseHelper.WriteToResponse(httpRes, httpReq, response, cancellationToken).ConfigureAwait(false); } public static async Task CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger) { var requestType = restPath.RequestType; if (RequireqRequestStream(requestType)) { // Used by IRequiresRequestStream var requestParams = await GetRequestParams(httpReq).ConfigureAwait(false); var request = ServiceHandler.CreateRequest(httpReq, restPath, requestParams, host.CreateInstance(requestType)); var rawReq = (IRequiresRequestStream)request; rawReq.RequestStream = httpReq.InputStream; return rawReq; } else { var requestParams = await GetFlattenedRequestParams(httpReq).ConfigureAwait(false); var requestDto = await CreateContentTypeRequest(host, httpReq, restPath.RequestType, httpReq.ContentType).ConfigureAwait(false); return CreateRequest(httpReq, restPath, requestParams, requestDto); } } public static bool RequireqRequestStream(Type requestType) { var requiresRequestStreamTypeInfo = typeof(IRequiresRequestStream).GetTypeInfo(); return requiresRequestStreamTypeInfo.IsAssignableFrom(requestType.GetTypeInfo()); } public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary requestParams, object requestDto) { var pathInfo = !restPath.IsWildCardPath ? GetSanitizedPathInfo(httpReq.PathInfo, out string contentType) : httpReq.PathInfo; return restPath.CreateRequest(pathInfo, requestParams, requestDto); } /// /// Duplicate Params are given a unique key by appending a #1 suffix /// private static async Task> GetRequestParams(IRequest request) { var map = new Dictionary(); foreach (var name in request.QueryString.Keys) { if (name == null) continue; //thank you ASP.NET var values = request.QueryString[name]; if (values.Count == 1) { map[name] = values[0]; } else { for (var i = 0; i < values.Count; i++) { map[name + (i == 0 ? "" : "#" + i)] = values[i]; } } } if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) { var formData = await request.GetFormData().ConfigureAwait(false); if (formData != null) { foreach (var name in formData.Keys) { if (name == null) continue; //thank you ASP.NET var values = formData.GetValues(name); if (values.Count == 1) { map[name] = values[0]; } else { for (var i = 0; i < values.Count; i++) { map[name + (i == 0 ? "" : "#" + i)] = values[i]; } } } } } return map; } private static bool IsMethod(string method, string expected) { return string.Equals(method, expected, StringComparison.OrdinalIgnoreCase); } /// /// Duplicate params have their values joined together in a comma-delimited string /// private static async Task> GetFlattenedRequestParams(IRequest request) { var map = new Dictionary(); foreach (var name in request.QueryString.Keys) { if (name == null) continue; //thank you ASP.NET map[name] = request.QueryString[name]; } if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) { var formData = await request.GetFormData().ConfigureAwait(false); if (formData != null) { foreach (var name in formData.Keys) { if (name == null) continue; //thank you ASP.NET map[name] = formData[name]; } } } return map; } private static void SetRoute(IRequest req, RestPath route) { req.Items["__route"] = route; } private static RestPath GetRoute(IRequest req) { req.Items.TryGetValue("__route", out var route); return route as RestPath; } } }