1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-26 22:56:23 -04:00

New: Use ASP.NET Core instead of Nancy

Co-Authored-By: ta264 <ta264@users.noreply.github.com>
This commit is contained in:
Qstick
2021-11-06 15:44:26 -05:00
committed by Mark McDowall
parent b83bb2cade
commit 1169741c54
246 changed files with 4232 additions and 9158 deletions
+139 -83
View File
@@ -1,135 +1,185 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Nancy;
using Microsoft.AspNetCore.Http;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Exceptions;
namespace Sonarr.Http.Extensions
{
public static class RequestExtensions
{
public static bool IsApiRequest(this Request request)
// See src/Readarr.Api.V1/Queue/QueueModule.cs
private static readonly HashSet<string> VALID_SORT_KEYS = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
return request.Path.CleanRequestPath().StartsWith("/api/", StringComparison.InvariantCultureIgnoreCase);
"series.sortname", //Workaround authors table properties not being added on isValidSortKey call
"timeleft",
"estimatedCompletionTime",
"protocol",
"episode",
"indexer",
"downloadClient",
"quality",
"status",
"title",
"progress"
};
private static readonly HashSet<string> EXCLUDED_KEYS = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
{
"page",
"pageSize",
"sortKey",
"sortDirection",
"filterKey",
"filterValue",
};
public static bool IsApiRequest(this HttpRequest request)
{
return request.Path.StartsWithSegments("/api", StringComparison.InvariantCultureIgnoreCase);
}
public static bool IsFeedRequest(this Request request)
public static bool IsFavIconRequest(this HttpRequest request)
{
return request.Path.CleanRequestPath().StartsWith("/feed/", StringComparison.InvariantCultureIgnoreCase);
return request.Path.Equals("/favicon.ico", StringComparison.InvariantCultureIgnoreCase);
}
public static bool IsPingRequest(this Request request)
{
return request.Path.CleanRequestPath().StartsWith("/ping", StringComparison.InvariantCultureIgnoreCase);
}
public static bool IsSignalRRequest(this Request request)
{
return request.Path.CleanRequestPath().StartsWith("/signalr/", StringComparison.InvariantCultureIgnoreCase);
}
public static bool IsLocalRequest(this Request request)
{
return request.UserHostAddress.Equals("localhost") ||
request.UserHostAddress.Equals("127.0.0.1") ||
request.UserHostAddress.Equals("::1");
}
public static bool IsLoginRequest(this Request request)
{
return request.Path.CleanRequestPath().Equals("/login", StringComparison.InvariantCultureIgnoreCase);
}
public static bool IsContentRequest(this Request request)
{
return request.Path.CleanRequestPath().StartsWith("/Content/", StringComparison.InvariantCultureIgnoreCase);
}
public static bool IsBundledJsRequest(this Request request)
{
return !request.Path.CleanRequestPath().EqualsIgnoreCase("/initialize.js") && request.Path.EndsWith(".js", StringComparison.InvariantCultureIgnoreCase);
}
public static bool IsFavIconRequest(this Request request)
{
return request.Path.CleanRequestPath().EqualsIgnoreCase("/favicon.ico");
}
public static bool IsSharedContentRequest(this Request request)
{
return request.Path.CleanRequestPath().StartsWith("/MediaCover/", StringComparison.InvariantCultureIgnoreCase) ||
request.Path.CleanRequestPath().StartsWith("/Content/Images/", StringComparison.InvariantCultureIgnoreCase);
}
public static bool GetBooleanQueryParameter(this Request request, string parameter, bool defaultValue = false)
public static bool GetBooleanQueryParameter(this HttpRequest request, string parameter, bool defaultValue = false)
{
var parameterValue = request.Query[parameter];
if (parameterValue.HasValue)
if (parameterValue.Any())
{
return bool.Parse(parameterValue.Value);
return bool.Parse(parameterValue.ToString());
}
return defaultValue;
}
public static int GetIntegerQueryParameter(this Request request, string parameter, int defaultValue = 0)
public static PagingResource<TResource> ReadPagingResourceFromRequest<TResource>(this HttpRequest request)
{
var parameterValue = request.Query[parameter];
if (parameterValue.HasValue)
if (!int.TryParse(request.Query["PageSize"].ToString(), out var pageSize))
{
return int.Parse(parameterValue.Value);
pageSize = 10;
}
return defaultValue;
}
public static int? GetNullableIntegerQueryParameter(this Request request, string parameter, int? defaultValue = null)
{
var parameterValue = request.Query[parameter];
if (parameterValue.HasValue)
if (!int.TryParse(request.Query["Page"].ToString(), out var page))
{
return int.Parse(parameterValue.Value);
page = 1;
}
return defaultValue;
var pagingResource = new PagingResource<TResource>
{
PageSize = pageSize,
Page = page,
Filters = new List<PagingResourceFilter>()
};
if (request.Query["SortKey"].Any())
{
var sortKey = request.Query["SortKey"].ToString();
if (!VALID_SORT_KEYS.Contains(sortKey) &&
!TableMapping.Mapper.IsValidSortKey(sortKey))
{
throw new BadRequestException($"Invalid sort key {sortKey}");
}
pagingResource.SortKey = sortKey;
if (request.Query["SortDirection"].Any())
{
pagingResource.SortDirection = request.Query["SortDirection"].ToString()
.Equals("ascending", StringComparison.InvariantCultureIgnoreCase)
? SortDirection.Ascending
: SortDirection.Descending;
}
}
// For backwards compatibility with v2
if (request.Query["FilterKey"].Any())
{
var filter = new PagingResourceFilter
{
Key = request.Query["FilterKey"].ToString()
};
if (request.Query["FilterValue"].Any())
{
filter.Value = request.Query["FilterValue"].ToString();
}
pagingResource.Filters.Add(filter);
}
// v3 uses filters in key=value format
foreach (var pair in request.Query)
{
if (EXCLUDED_KEYS.Contains(pair.Key))
{
continue;
}
pagingResource.Filters.Add(new PagingResourceFilter
{
Key = pair.Key,
Value = pair.Value.ToString()
});
}
return pagingResource;
}
public static string GetRemoteIP(this NancyContext context)
public static PagingResource<TResource> ApplyToPage<TResource, TModel>(this PagingSpec<TModel> pagingSpec, Func<PagingSpec<TModel>, PagingSpec<TModel>> function, Converter<TModel, TResource> mapper)
{
if (context == null || context.Request == null)
pagingSpec = function(pagingSpec);
return new PagingResource<TResource>
{
Page = pagingSpec.Page,
PageSize = pagingSpec.PageSize,
SortDirection = pagingSpec.SortDirection,
SortKey = pagingSpec.SortKey,
TotalRecords = pagingSpec.TotalRecords,
Records = pagingSpec.Records.ConvertAll(mapper)
};
}
public static string GetRemoteIP(this HttpContext context)
{
return context?.Request?.GetRemoteIP() ?? "Unknown";
}
public static string GetRemoteIP(this HttpRequest request)
{
if (request == null)
{
return "Unknown";
}
var remoteAddress = context.Request.UserHostAddress;
IPAddress.TryParse(remoteAddress, out IPAddress remoteIP);
if (remoteIP == null)
{
return remoteAddress;
}
var remoteIP = request.HttpContext.Connection.RemoteIpAddress;
if (remoteIP.IsIPv4MappedToIPv6)
{
remoteIP = remoteIP.MapToIPv4();
}
remoteAddress = remoteIP.ToString();
var remoteAddress = remoteIP.ToString();
// Only check if forwarded by a local network reverse proxy
if (remoteIP.IsLocalAddress())
{
var realIPHeader = context.Request.Headers["X-Real-IP"];
var realIPHeader = request.Headers["X-Real-IP"];
if (realIPHeader.Any())
{
return realIPHeader.First().ToString();
}
var forwardedForHeader = context.Request.Headers["X-Forwarded-For"];
var forwardedForHeader = request.Headers["X-Forwarded-For"];
if (forwardedForHeader.Any())
{
// Get the first address that was forwarded by a local IP to prevent remote clients faking another proxy
@@ -153,12 +203,18 @@ namespace Sonarr.Http.Extensions
return remoteAddress;
}
private static string CleanRequestPath(this string path)
public static void DisableCache(this IHeaderDictionary headers)
{
// When running under mono the path is not stripped of extraneous leading slashes which can break our IXRequest
// path detection, this will remove all leading slashes and replace them with a single slash.
headers.Remove("Last-Modified");
headers["Cache-Control"] = "no-cache, no-store";
headers["Expires"] = "-1";
headers["Pragma"] = "no-cache";
}
return $"/{path.TrimStart('/')}";
public static void EnableCache(this IHeaderDictionary headers)
{
headers["Cache-Control"] = "max-age=31536000, public";
headers["Last-Modified"] = BuildInfo.BuildDateTime.ToString("r");
}
}
}