修复部分GitHub url不携带协议头的时候报错的问题
This commit is contained in:
parent
26d01669fd
commit
00db7a863b
6 changed files with 96 additions and 82 deletions
|
@ -2,9 +2,9 @@ using iFileProxy.Config;
|
|||
using iFileProxy.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Serilog;
|
||||
using iFileProxy.Helpers;
|
||||
using System.Web;
|
||||
using System.Net; // 用于 URL 解码
|
||||
using System.Net;
|
||||
using iFileProxy.Helpers; // 用于 URL 解码
|
||||
|
||||
namespace iFileProxy.Controllers
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ namespace iFileProxy.Controllers
|
|||
public class StreamProxyController(IHttpClientFactory httpClientFactory, AppConfig appConfig) : ControllerBase
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
|
||||
private long SizeLimit = appConfig.StreamProxyOptions.SizeLimit;
|
||||
private long SizeLimit = appConfig.StreamProxyOptions.SizeLimit;
|
||||
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<StreamProxyController>();
|
||||
|
||||
// 匹配文件代理请求
|
||||
|
@ -23,14 +23,19 @@ namespace iFileProxy.Controllers
|
|||
[HttpPatch("{*proxyUrl}")]
|
||||
public async Task<IActionResult> ProxyGitRequest(string proxyUrl)
|
||||
{
|
||||
DownloadFileInfo? downloadFileInfo;
|
||||
try
|
||||
{
|
||||
if (proxyUrl.StartsWith("github.com"))
|
||||
proxyUrl = "https://" + proxyUrl;
|
||||
if (MasterHelper.IsGithubUrl(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)
|
||||
{
|
||||
if (proxyUrl.IndexOf(keywords) != -1)
|
||||
if (proxyUrl.Contains(keywords, StringComparison.CurrentCulture))
|
||||
return StatusCode((int)HttpStatusCode.Forbidden, "Keyword::Forbidden");
|
||||
}
|
||||
var t = new Uri(proxyUrl);
|
||||
|
@ -38,7 +43,7 @@ namespace iFileProxy.Controllers
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("[Stream] 解析下载文件时出现问题: {ex}", ex);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError,"服务故障 请联系开发者反馈");
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError, "服务故障 请联系开发者反馈");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(proxyUrl))
|
||||
|
@ -77,7 +82,7 @@ namespace iFileProxy.Controllers
|
|||
.Where(h => h.Key != "Host") // 排除 'Host' 头部
|
||||
.ToDictionary(h => h.Key, h => h.Value.ToString());
|
||||
|
||||
List<KeyValuePair<string, string>> failedHeaders = new();
|
||||
List<KeyValuePair<string, string>> failedHeaders = [];
|
||||
|
||||
client.DefaultRequestHeaders.Clear();
|
||||
|
||||
|
@ -109,12 +114,12 @@ namespace iFileProxy.Controllers
|
|||
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)
|
||||
{
|
||||
var content = new StreamContent(Request.Body); // 仅对于 POST/PUT/PATCH 请求,转发请求体
|
||||
// 处理之前添加失败的请求头
|
||||
// 处理之前添加失败的请求头
|
||||
foreach (var header in failedHeaders)
|
||||
{
|
||||
content.Headers.Add(header.Key, header.Value);
|
||||
|
@ -123,44 +128,42 @@ 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)}");
|
||||
}
|
||||
|
||||
// 确保响应成功
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return StatusCode((int)response.StatusCode, await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
// 获取服务器响应的文件大小
|
||||
var contentLength = response.Content.Headers.ContentLength;
|
||||
|
||||
// 如果文件超过大小限制,返回错误
|
||||
if (contentLength.HasValue && contentLength.Value > SizeLimit)
|
||||
{
|
||||
return StatusCode(413, "File is too large to download.");
|
||||
}
|
||||
|
||||
// 设置响应头:告诉浏览器这是一个文件下载
|
||||
var fileName = Path.GetFileName(new Uri(targetUrl).LocalPath); // 从 URL 获取文件名
|
||||
Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}");
|
||||
Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream";
|
||||
|
||||
if (contentLength > 0)
|
||||
Response.ContentLength = contentLength;
|
||||
|
||||
// 流式转发文件
|
||||
using (var stream = await response.Content.ReadAsStreamAsync())
|
||||
{
|
||||
await stream.CopyToAsync(Response.Body);
|
||||
}
|
||||
|
||||
return new EmptyResult(); // 控制器返回空结果,响应已经在流中完成
|
||||
_logger.Debug($"[Response Header] Key: {header.Key} Value: {string.Join(",", header.Value)}");
|
||||
}
|
||||
|
||||
// 确保响应成功
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return StatusCode((int)response.StatusCode, await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
// 获取服务器响应的文件大小
|
||||
var contentLength = response.Content.Headers.ContentLength;
|
||||
|
||||
// 如果文件超过大小限制,返回错误
|
||||
if (contentLength.HasValue && contentLength.Value > SizeLimit)
|
||||
{
|
||||
return StatusCode(413, "File is too large to download.");
|
||||
}
|
||||
|
||||
// 设置响应头:告诉浏览器这是一个文件下载
|
||||
var fileName = HttpUtility.UrlEncode(Path.GetFileName(new Uri(targetUrl).LocalPath)); // 对文件名进行编码 防止报错
|
||||
Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}");
|
||||
Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream";
|
||||
|
||||
if (contentLength > 0)
|
||||
Response.ContentLength = contentLength;
|
||||
|
||||
// 流式转发文件
|
||||
using (var stream = await response.Content.ReadAsStreamAsync())
|
||||
{
|
||||
await stream.CopyToAsync(Response.Body);
|
||||
}
|
||||
|
||||
return new EmptyResult(); // 控制器返回空结果,响应已经在流中完成
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -213,5 +213,19 @@ namespace iFileProxy.Helpers
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace iFileProxy
|
||||
namespace iFileProxy.Middleware
|
||||
{
|
||||
using iFileProxy.Helpers;
|
||||
using iFileProxy.Models;
|
||||
|
@ -11,28 +11,30 @@ namespace iFileProxy
|
|||
using System.Text.Json;
|
||||
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>();
|
||||
|
||||
|
||||
public ErrorHandlerMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
private readonly static ILogger _logger = Log.Logger.ForContext<ErrorHandlerMiddleware>();
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
if (context.Response.HasStarted)
|
||||
{
|
||||
_logger.Debug($"响应已经开始 错误处理中间件无法再修改其响应内容");
|
||||
return;
|
||||
}
|
||||
if (context.Response.StatusCode == 404)
|
||||
{
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync( JsonSerializer.Serialize(new CommonRsp { Retcode = 404, Message = "this route not exists." }));
|
||||
|
||||
{
|
||||
context.Response.OnStarting(async () =>
|
||||
{
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 404, Message = "你正在请求的资源不存在!" }));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -44,10 +46,10 @@ namespace iFileProxy
|
|||
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
|
||||
case NotImplementedException:
|
||||
code = HttpStatusCode.NotImplemented; // 501
|
||||
break;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using iFileProxy.Config;
|
||||
using iFileProxy.Helpers;
|
||||
using iFileProxy.Middleware;
|
||||
using iFileProxy.Helpers;
|
||||
using iFileProxy.Services;
|
||||
using Serilog;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Serilog;
|
||||
using System.Text;
|
||||
|
||||
namespace iFileProxy
|
||||
|
@ -19,6 +19,7 @@ namespace iFileProxy
|
|||
Console.Write(" "); // 补全日志第一行开头的空白
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// CORS配置
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
|
@ -26,18 +27,15 @@ namespace iFileProxy
|
|||
builder =>
|
||||
{
|
||||
builder
|
||||
.WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn", "https://github.linxi.info", "http://github.linxi.info/", "http://localhost:4173" )
|
||||
.WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn", "https://github.linxi.info", "http://github.linxi.info/", "http://localhost:4173")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
// Add services to the container
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
|
@ -45,11 +43,8 @@ namespace iFileProxy
|
|||
|
||||
// 注入依赖
|
||||
builder.Services.AddSingleton(AppConfig.GetCurrConfig());
|
||||
|
||||
builder.Services.AddSingleton<DatabaseGateService>();
|
||||
|
||||
builder.Services.AddSingleton<TaskManager>();
|
||||
|
||||
builder.Services.AddSingleton<Dictionary<string, Dictionary<string, uint>>>();
|
||||
|
||||
// 添加验证服务
|
||||
|
@ -75,24 +70,24 @@ namespace iFileProxy
|
|||
|
||||
var app = builder.Build();
|
||||
// 初始化缓存管理服务
|
||||
LocalCacheManager localCacheManager = new (app.Services);
|
||||
LocalCacheManager localCacheManager = new(app.Services);
|
||||
|
||||
// 1. 配置CORS(要在Routing之前)
|
||||
// 1. 错误处理(放在请求管道的最前面)
|
||||
app.UseMiddleware<ErrorHandlerMiddleware>();
|
||||
|
||||
// 2. 配置CORS(要在Routing之前)
|
||||
app.UseCors("AllowFrontend");
|
||||
|
||||
// 2. 配置静态文件(要在Routing之前)
|
||||
// 3. 配置静态文件(要在Routing之前)
|
||||
var defaultFilesOptions = new DefaultFilesOptions();
|
||||
defaultFilesOptions.DefaultFileNames.Clear();
|
||||
defaultFilesOptions.DefaultFileNames.Add("index.html");
|
||||
app.UseDefaultFiles(defaultFilesOptions);
|
||||
app.UseStaticFiles();
|
||||
|
||||
// 3. 强制使用HTTPS(要在Routing之前)
|
||||
// 4. 强制使用HTTPS(要在Routing之前)
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
// 4. 错误处理(在RequestLogging之前)
|
||||
app.UseMiddleware<ErrorHandlerMiddleware>();
|
||||
|
||||
// 5. 请求日志记录(在Routing之前)
|
||||
app.UseSerilogRequestLogging(options =>
|
||||
{
|
||||
|
|
|
@ -154,7 +154,7 @@ namespace iFileProxy.Services
|
|||
|
||||
try
|
||||
{
|
||||
var conn = new MySqlConnection(builder.ConnectionString);
|
||||
var conn = new MySqlConnection(builder.ConnectionString);
|
||||
conn.Open();
|
||||
return conn;
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ namespace iFileProxy.Services
|
|||
string? t_url = c.Request.Query["url"].FirstOrDefault() ?? c.Request.Form["url"].FirstOrDefault();
|
||||
|
||||
foreach (var keywords in _appConfig.SecurityOptions.BlockedKeyword) {
|
||||
if (t_url.IndexOf(keywords) != -1)
|
||||
if (t_url.Contains(keywords, StringComparison.CurrentCulture))
|
||||
return TaskAddState.ErrKeywordForbidden;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue