修复部分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 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);
|
||||||
|
@ -38,7 +43,7 @@ namespace iFileProxy.Controllers
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error("[Stream] 解析下载文件时出现问题: {ex}", ex);
|
_logger.Error("[Stream] 解析下载文件时出现问题: {ex}", ex);
|
||||||
return StatusCode((int)HttpStatusCode.InternalServerError,"服务故障 请联系开发者反馈");
|
return StatusCode((int)HttpStatusCode.InternalServerError, "服务故障 请联系开发者反馈");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(proxyUrl))
|
if (string.IsNullOrWhiteSpace(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,12 +114,12 @@ 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)
|
||||||
{
|
{
|
||||||
var content = new StreamContent(Request.Body); // 仅对于 POST/PUT/PATCH 请求,转发请求体
|
var content = new StreamContent(Request.Body); // 仅对于 POST/PUT/PATCH 请求,转发请求体
|
||||||
// 处理之前添加失败的请求头
|
// 处理之前添加失败的请求头
|
||||||
foreach (var header in failedHeaders)
|
foreach (var header in failedHeaders)
|
||||||
{
|
{
|
||||||
content.Headers.Add(header.Key, header.Value);
|
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)}");
|
||||||
{
|
|
||||||
_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(); // 控制器返回空结果,响应已经在流中完成
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保响应成功
|
||||||
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.ContentType = "application/json";
|
context.Response.OnStarting(async () =>
|
||||||
await context.Response.WriteAsync( JsonSerializer.Serialize(new CommonRsp { Retcode = 404, Message = "this route not exists." }));
|
{
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -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 =>
|
||||||
{
|
{
|
||||||
|
@ -26,18 +27,15 @@ namespace iFileProxy
|
||||||
builder =>
|
builder =>
|
||||||
{
|
{
|
||||||
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()
|
.AllowAnyMethod()
|
||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
.AllowCredentials();
|
.AllowCredentials();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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>>>();
|
||||||
|
|
||||||
// 添加验证服务
|
// 添加验证服务
|
||||||
|
@ -75,24 +70,24 @@ namespace iFileProxy
|
||||||
|
|
||||||
var app = builder.Build();
|
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");
|
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 =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -154,7 +154,7 @@ namespace iFileProxy.Services
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var conn = new MySqlConnection(builder.ConnectionString);
|
var conn = new MySqlConnection(builder.ConnectionString);
|
||||||
conn.Open();
|
conn.Open();
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue