修复部分GitHub url不携带协议头的时候报错的问题

This commit is contained in:
root 2024-12-09 23:26:01 +08:00
parent 26d01669fd
commit 00db7a863b
6 changed files with 96 additions and 82 deletions

View file

@ -2,9 +2,9 @@ using iFileProxy.Config;
using iFileProxy.Models; using iFileProxy.Models;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Serilog; using Serilog;
using iFileProxy.Helpers;
using System.Web; using System.Web;
using System.Net; // 用于 URL 解码 using System.Net;
using iFileProxy.Helpers; // 用于 URL 解码
namespace iFileProxy.Controllers namespace iFileProxy.Controllers
{ {
@ -23,14 +23,19 @@ namespace iFileProxy.Controllers
[HttpPatch("{*proxyUrl}")] [HttpPatch("{*proxyUrl}")]
public async Task<IActionResult> ProxyGitRequest(string proxyUrl) public async Task<IActionResult> ProxyGitRequest(string proxyUrl)
{ {
DownloadFileInfo? downloadFileInfo;
try try
{ {
if (proxyUrl.StartsWith("github.com")) if (MasterHelper.IsGithubUrl(proxyUrl))
proxyUrl = "https://" + proxyUrl; {
if (!proxyUrl.StartsWith("http://") && !proxyUrl.StartsWith("https://"))
proxyUrl = "http://" + proxyUrl; // 默认选择Http协议 如果支持HTTPs应该正常应该会被重定向(除非网站管理员没设置)
}
else
if (!proxyUrl.StartsWith("http://") && !proxyUrl.StartsWith("https://")) // 不带协议头 直接扔进垃圾桶
return StatusCode(400, "非法Url! 除GitHubUrl外其他代理请求必须携带协议头!");
foreach (var keywords in appConfig.SecurityOptions.BlockedKeyword) foreach (var keywords in appConfig.SecurityOptions.BlockedKeyword)
{ {
if (proxyUrl.IndexOf(keywords) != -1) if (proxyUrl.Contains(keywords, StringComparison.CurrentCulture))
return StatusCode((int)HttpStatusCode.Forbidden, "Keyword::Forbidden"); return StatusCode((int)HttpStatusCode.Forbidden, "Keyword::Forbidden");
} }
var t = new Uri(proxyUrl); var t = new Uri(proxyUrl);
@ -77,7 +82,7 @@ namespace iFileProxy.Controllers
.Where(h => h.Key != "Host") // 排除 'Host' 头部 .Where(h => h.Key != "Host") // 排除 'Host' 头部
.ToDictionary(h => h.Key, h => h.Value.ToString()); .ToDictionary(h => h.Key, h => h.Value.ToString());
List<KeyValuePair<string, string>> failedHeaders = new(); List<KeyValuePair<string, string>> failedHeaders = [];
client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.Clear();
@ -109,7 +114,7 @@ namespace iFileProxy.Controllers
var method = Request.Method; var method = Request.Method;
// 根据客户端请求方法动态代理请求 // 根据客户端请求方法动态代理请求
HttpRequestMessage requestMessage = new HttpRequestMessage(new HttpMethod(method), targetUrl); HttpRequestMessage requestMessage = new(new HttpMethod(method), targetUrl);
if (method == HttpMethods.Post || method == HttpMethods.Put || method == HttpMethods.Patch) if (method == HttpMethods.Post || method == HttpMethods.Put || method == HttpMethods.Patch)
{ {
@ -123,8 +128,7 @@ namespace iFileProxy.Controllers
} }
// 发送请求并获取响应 // 发送请求并获取响应
using (var response = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead)) using var response = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead);
{
foreach (var header in response.Headers) foreach (var header in response.Headers)
{ {
_logger.Debug($"[Response Header] Key: {header.Key} Value: {string.Join(",", header.Value)}"); _logger.Debug($"[Response Header] Key: {header.Key} Value: {string.Join(",", header.Value)}");
@ -146,7 +150,7 @@ namespace iFileProxy.Controllers
} }
// 设置响应头:告诉浏览器这是一个文件下载 // 设置响应头:告诉浏览器这是一个文件下载
var fileName = Path.GetFileName(new Uri(targetUrl).LocalPath); // 从 URL 获取文件名 var fileName = HttpUtility.UrlEncode(Path.GetFileName(new Uri(targetUrl).LocalPath)); // 对文件名进行编码 防止报错
Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}"); Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}");
Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream"; Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream";
@ -161,7 +165,6 @@ namespace iFileProxy.Controllers
return new EmptyResult(); // 控制器返回空结果,响应已经在流中完成 return new EmptyResult(); // 控制器返回空结果,响应已经在流中完成
} }
}
catch (Exception ex) catch (Exception ex)
{ {
// 捕获异常并返回错误 // 捕获异常并返回错误

View file

@ -213,5 +213,19 @@ namespace iFileProxy.Helpers
return debugInfo; return debugInfo;
} }
// 定义正则表达式
private static readonly Regex exp1 = new Regex(@"^(?:https?://)?github\.com/(?<author>.+?)/(?<repo>.+?)/(?:releases|archive)/.*$", RegexOptions.IgnoreCase);
private static readonly Regex exp2 = new Regex(@"^(?:https?://)?github\.com/(?<author>.+?)/(?<repo>.+?)/(?:blob|raw)/.*$", RegexOptions.IgnoreCase);
private static readonly Regex exp3 = new Regex(@"^(?:https?://)?github\.com/(?<author>.+?)/(?<repo>.+?)/(?:info|git-).*$", RegexOptions.IgnoreCase);
private static readonly Regex exp4 = new Regex(@"^(?:https?://)?raw\.(?:githubusercontent|github)\.com/(?<author>.+?)/(?<repo>.+?)/.+?/.+$", RegexOptions.IgnoreCase);
private static readonly Regex exp5 = new Regex(@"^(?:https?://)?gist\.(?:githubusercontent|github)\.com/(?<author>.+?)/.+?/.+$", RegexOptions.IgnoreCase);
public static bool IsGithubUrl(string url)
{
// 判断url是否匹配任何一个正则表达式
return exp1.IsMatch(url) || exp2.IsMatch(url) || exp3.IsMatch(url) || exp4.IsMatch(url) || exp5.IsMatch(url);
}
} }
} }

View file

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace iFileProxy namespace iFileProxy.Middleware
{ {
using iFileProxy.Helpers; using iFileProxy.Helpers;
using iFileProxy.Models; using iFileProxy.Models;
@ -11,28 +11,30 @@ namespace iFileProxy
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
public class ErrorHandlerMiddleware public class ErrorHandlerMiddleware(RequestDelegate next)
{ {
private readonly RequestDelegate _next; private readonly RequestDelegate _next = next;
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<ErrorHandlerMiddleware>(); private readonly static ILogger _logger = Log.Logger.ForContext<ErrorHandlerMiddleware>();
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context) public async Task InvokeAsync(HttpContext context)
{ {
try try
{ {
await _next(context); await _next(context);
if (context.Response.HasStarted)
{
_logger.Debug($"响应已经开始 错误处理中间件无法再修改其响应内容");
return;
}
if (context.Response.StatusCode == 404) if (context.Response.StatusCode == 404)
{
context.Response.OnStarting(async () =>
{ {
context.Response.ContentType = "application/json"; context.Response.ContentType = "application/json";
await context.Response.WriteAsync( JsonSerializer.Serialize(new CommonRsp { Retcode = 404, Message = "this route not exists." })); await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 404, Message = "你正在请求的资源不存在!" }));
}
);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -44,7 +46,7 @@ namespace iFileProxy
private static Task HandleExceptionAsync(HttpContext context, Exception exception) private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{ {
var code = HttpStatusCode.InternalServerError; // 500 if unexpected var code = HttpStatusCode.InternalServerError; // 500 if unexpected
_logger.Error("Crash Data: {exception}\nContext: {context}", exception, JsonSerializer.Serialize (MasterHelper.ExtractDebugInfo(context))); _logger.Fatal("崩溃数据: {exception}\n上下文信息: {context}", exception, JsonSerializer.Serialize(MasterHelper.ExtractDebugInfo(context)));
switch (exception) switch (exception)
{ {

View file

@ -1,10 +1,10 @@
using iFileProxy.Config; using iFileProxy.Config;
using iFileProxy.Helpers;
using iFileProxy.Middleware; using iFileProxy.Middleware;
using iFileProxy.Helpers;
using iFileProxy.Services; using iFileProxy.Services;
using Serilog;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Serilog;
using System.Text; using System.Text;
namespace iFileProxy namespace iFileProxy
@ -19,6 +19,7 @@ namespace iFileProxy
Console.Write(" "); // 补全日志第一行开头的空白 Console.Write(" "); // 补全日志第一行开头的空白
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// CORS配置 // CORS配置
builder.Services.AddCors(options => builder.Services.AddCors(options =>
{ {
@ -33,11 +34,8 @@ namespace iFileProxy
}); });
}); });
// Add services to the container
// Add services to the container.
builder.Services.AddControllers(); builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
@ -45,11 +43,8 @@ namespace iFileProxy
// 注入依赖 // 注入依赖
builder.Services.AddSingleton(AppConfig.GetCurrConfig()); builder.Services.AddSingleton(AppConfig.GetCurrConfig());
builder.Services.AddSingleton<DatabaseGateService>(); builder.Services.AddSingleton<DatabaseGateService>();
builder.Services.AddSingleton<TaskManager>(); builder.Services.AddSingleton<TaskManager>();
builder.Services.AddSingleton<Dictionary<string, Dictionary<string, uint>>>(); builder.Services.AddSingleton<Dictionary<string, Dictionary<string, uint>>>();
// 添加验证服务 // 添加验证服务
@ -77,22 +72,22 @@ namespace iFileProxy
// 初始化缓存管理服务 // 初始化缓存管理服务
LocalCacheManager localCacheManager = new(app.Services); LocalCacheManager localCacheManager = new(app.Services);
// 1. 配置CORS要在Routing之前 // 1. 错误处理(放在请求管道的最前面)
app.UseMiddleware<ErrorHandlerMiddleware>();
// 2. 配置CORS要在Routing之前
app.UseCors("AllowFrontend"); app.UseCors("AllowFrontend");
// 2. 配置静态文件要在Routing之前 // 3. 配置静态文件要在Routing之前
var defaultFilesOptions = new DefaultFilesOptions(); var defaultFilesOptions = new DefaultFilesOptions();
defaultFilesOptions.DefaultFileNames.Clear(); defaultFilesOptions.DefaultFileNames.Clear();
defaultFilesOptions.DefaultFileNames.Add("index.html"); defaultFilesOptions.DefaultFileNames.Add("index.html");
app.UseDefaultFiles(defaultFilesOptions); app.UseDefaultFiles(defaultFilesOptions);
app.UseStaticFiles(); app.UseStaticFiles();
// 3. 强制使用HTTPS要在Routing之前 // 4. 强制使用HTTPS要在Routing之前
app.UseHttpsRedirection(); app.UseHttpsRedirection();
// 4. 错误处理在RequestLogging之前
app.UseMiddleware<ErrorHandlerMiddleware>();
// 5. 请求日志记录在Routing之前 // 5. 请求日志记录在Routing之前
app.UseSerilogRequestLogging(options => app.UseSerilogRequestLogging(options =>
{ {

View file

@ -95,7 +95,7 @@ namespace iFileProxy.Services
string? t_url = c.Request.Query["url"].FirstOrDefault() ?? c.Request.Form["url"].FirstOrDefault(); string? t_url = c.Request.Query["url"].FirstOrDefault() ?? c.Request.Form["url"].FirstOrDefault();
foreach (var keywords in _appConfig.SecurityOptions.BlockedKeyword) { foreach (var keywords in _appConfig.SecurityOptions.BlockedKeyword) {
if (t_url.IndexOf(keywords) != -1) if (t_url.Contains(keywords, StringComparison.CurrentCulture))
return TaskAddState.ErrKeywordForbidden; return TaskAddState.ErrKeywordForbidden;
} }