diff --git a/README.md b/README.md index 22a9dc6..5837e5f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,66 @@ -### 文件代理下载工具 +# 项目概述 +iFileProxy 是一个功能丰富的代理下载系统,支持多种下载方式和用户管理功能。主要用于解决网络访问受限环境下的文件下载需求。 -#### 功能列表 -- [x] 代理下载文件 -- [x] 浏览器下载文件 -- [x] 文件名黑名单 -- [x] 目标Host黑名单 -- [x] 文件大小限制 -- [x] 基于IP查询提交的任务状态 -- [x] 基于IP和路由的请求次数限制 -- [x] 任务队列 -- [x] 已经存在对应文件时候直接跳过代理下载 直接下载已经缓存的内容 -- [ ] 捐赠 \ No newline at end of file +## 核心功能 +- 文件下载系统 +2. + - 离线下载 + - 多线程下载支持 + - 任务队列管理 + - 下载进度跟踪 + - 文件缓存管理 + - 文件处理 + - 流式传输 + - 断点续传 + - 文件哈希校验 + - 自动清理过期缓存 +1. 代理功能 + - GitHub 代理 +jsDelivr CDN 加速支持 + - 仓库文件直接下载 + - Git 克隆支持 + - 通用流代理 + - HTTP/HTTPS 资源代理 + - 请求头透传 + - 流量控制 +1.- 支持 +1. 用户系统 + - 账户管理 + - 用户注册/登录 + - JWT 身份验证 + - 设备指纹验证 + - 权限控制 + - 多级权限(用户/管理员/超级管理员) + - 基于角色的访问控制 + - 用户操作日志 +1. 安全特性 + - 访问控制 + - IP 访问限制 + - 请求频率限制 + - 黑名单系统 + - 文件大小限制 +安全防护 +HTTPS 支持 + - CORS 配置 + - 请求验证 +1. 管理功能 + - 任务管理 + - 任务创建/删除 + - 任务状态监控 + - 任务优先级调整 + - 任务队列管理 + - 系统管理 + - 用户管理 +系统配置 + - 访问统计 + - 系统日志 + +1. 技术特性 + - ASP.NET Core 8.0 + - MySQL 数据库 + - JWT 认证 + - 依赖注入 + - 中间件管道 + - 日志系统 + - 连接池 +静态文件服务 \ No newline at end of file diff --git a/src/Config/AppConfig.cs b/src/Config/AppConfig.cs index 282c6a0..06ae7d0 100644 --- a/src/Config/AppConfig.cs +++ b/src/Config/AppConfig.cs @@ -14,6 +14,12 @@ namespace iFileProxy.Config AllowTrailingCommas = true // 允许尾随逗号 }; + public void Save(string configPath = "iFileProxy.json") + { + var config = JsonSerializer.Serialize(this); + File.WriteAllText(configPath, config); + } + [JsonPropertyName("Download")] public DownloadOptions DownloadOptions { get; set; } = new(); @@ -23,8 +29,8 @@ namespace iFileProxy.Config [JsonPropertyName("Database")] public Database Database { get; set; } - [JsonPropertyName("GithubProxy")] - public GithubProxyOptions GithubProxyOptions { get; set; } = new(); + [JsonPropertyName("StreamProxy")] + public StreamProxyOptions StreamProxyOptions { get; set; } = new(); public static AppConfig GetCurrConfig(string configPath = "iFileProxy.json") { @@ -88,6 +94,7 @@ namespace iFileProxy.Config public List BlockedHost { get; set; } = []; public List BlockedFileName { get; set; } = []; public List BlockedClientIP { get; set; } = []; + public List BlockedKeyword { get; set; } = []; public List RoutesToTrack { get; set; } = []; public int DailyRequestLimitPerIP { get; set; } = -1; public bool AllowDifferentIPsForDownload { get; set; } = true; @@ -146,21 +153,11 @@ namespace iFileProxy.Config public string Description { get; set; } } - public class GithubProxyOptions + public class StreamProxyOptions { /// /// 文件大小限制(字节) /// - public long SizeLimit { get; set; } = 1024L * 1024L * 1024L; // 默认1GB - - /// - /// 黑名单列表 - /// - public List Blacklist { get; set; } = []; - - /// - /// 是否启用 jsDelivr 加速 - /// - public bool EnableJsDelivr { get; set; } = false; + public long SizeLimit { get; set; } = 1024L * 1024L * 1024L * 10; // 默认10GB } } diff --git a/src/Controllers/GithubProxyController.cs b/src/Controllers/GithubProxyController.cs deleted file mode 100644 index f0562a8..0000000 --- a/src/Controllers/GithubProxyController.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using iFileProxy.Services; -using iFileProxy.Models; - -namespace iFileProxy.Controllers -{ - [Route("[controller]")] - [ApiController] - public class GithubProxyController : ControllerBase - { - private readonly ILogger _logger; - private readonly GithubProxyService _proxyService; - - public GithubProxyController( - ILogger logger, - GithubProxyService proxyService) - { - _logger = logger; - _proxyService = proxyService; - } - - [HttpGet("{**url}")] - public async Task ProxyDownload(string url) - { - try - { - // 1. URL 格式化 - url = url.StartsWith("http") ? url : $"https://{url}"; - - // 2. 验证 URL - var (isValid, author, repo) = _proxyService.ValidateUrl(url); - if (!isValid || author == null || repo == null) - { - return BadRequest(new CommonRsp - { - Retcode = 1, - Message = "Invalid GitHub URL" - }); - } - - // 3. 检查黑名单 - if (_proxyService.IsBlocked(author, repo)) - { - return Forbid(); - } - - // 4. 处理URL(CDN或Raw) - url = _proxyService.ProcessUrl(url); - - // 5. 代理请求 - var response = await _proxyService.ProxyRequestAsync(url, Request.Headers); - if (!response.Success) - { - return StatusCode(response.StatusCode, new CommonRsp - { - Retcode = 1, - Message = response.Message - }); - } - - // 6. 设置响应头 - if (response.Headers != null) - { - foreach (var header in response.Headers) - { - Response.Headers[header.Key] = header.Value; - } - } - - // 7. 返回流式响应 - return new FileStreamResult( - response.Stream!, - response.ContentType ?? "application/octet-stream" - ); - } - catch (Exception ex) - { - _logger.LogError(ex, "Proxy download failed"); - return StatusCode(500, new CommonRsp - { - Retcode = 1, - Message = "Internal server error" - }); - } - } - } -} \ No newline at end of file diff --git a/src/Controllers/ManagementController.cs b/src/Controllers/ManagementController.cs index 4a3ba2f..74348f8 100644 --- a/src/Controllers/ManagementController.cs +++ b/src/Controllers/ManagementController.cs @@ -12,7 +12,7 @@ namespace iFileProxy.Controllers [Authorize(UserMask.Admin,UserMask.SuperAdmin)] [Route("[controller]")] [ApiController] - public class ManagementController(TaskManager taskManager, DatabaseGateService dbGateService) : ControllerBase + public class ManagementController(TaskManager taskManager, DatabaseGateService dbGateService, Dictionary> ipAccessLimitData) : ControllerBase { public TaskManager _taskManager = taskManager; public DatabaseGateService _dbGateService = dbGateService; @@ -593,6 +593,12 @@ namespace iFileProxy.Controllers } } + [HttpGet("GetIPAccessLimitData")] + public ActionResult GetIPAccessLimitData() + { + return Ok(new CommonRsp { Retcode = 0, Data = ipAccessLimitData, Message = "succ" }); + } + public class AddUserRequest { public string Username { get; set; } = string.Empty; diff --git a/src/Controllers/StreamProxyController.cs b/src/Controllers/StreamProxyController.cs new file mode 100644 index 0000000..37ce313 --- /dev/null +++ b/src/Controllers/StreamProxyController.cs @@ -0,0 +1,184 @@ +using iFileProxy.Config; +using iFileProxy.Models; +using Microsoft.AspNetCore.Mvc; +using Serilog; +using iFileProxy.Helpers; +using System.Web; +using System.Net; // 用于 URL 解码 + +namespace iFileProxy.Controllers +{ + [Route("/")] + public class StreamProxyController(IHttpClientFactory httpClientFactory, AppConfig appConfig) : Controller + { + private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; + private long SizeLimit = appConfig.StreamProxyOptions.SizeLimit; + private readonly static Serilog.ILogger _logger = Log.Logger.ForContext(); + + // 匹配文件代理请求 + [HttpGet("{*proxyUrl}")] + [HttpPost("{*proxyUrl}")] + [HttpPut("{*proxyUrl}")] + [HttpDelete("{*proxyUrl}")] + [HttpPatch("{*proxyUrl}")] + public async Task ProxyGitRequest(string proxyUrl) + { + DownloadFileInfo downloadFileInfo; + try + { + foreach (var keywords in appConfig.SecurityOptions.BlockedKeyword) + { + if (proxyUrl.IndexOf(keywords) != -1) + return StatusCode((int)HttpStatusCode.Forbidden, "Keyword::Forbidden"); + + } + var t = new Uri(proxyUrl); + downloadFileInfo = FileDownloadHelper.GetDownloadFileInfo(proxyUrl); + if (downloadFileInfo != null) + { + if (appConfig.SecurityOptions.BlockedFileName.IndexOf(downloadFileInfo.FileName) != -1) + { + return StatusCode((int)HttpStatusCode.Forbidden, "This Filename is Blocked"); + } + if (appConfig.SecurityOptions.BlockedHost.IndexOf(t.Host) != -1) + { + return StatusCode((int)HttpStatusCode.Forbidden, "Target Host is Blocked"); + } + } + else + return NotFound(); + } + catch (Exception) + { + return NotFound(); + } + + if (string.IsNullOrWhiteSpace(proxyUrl)) + { + return BadRequest("URL cannot be empty."); + } + + // URL 解码,确保完整的 URL 被正确处理 + proxyUrl = HttpUtility.UrlDecode(proxyUrl); + + // 如果 URL 没有带协议(http:// 或 https://),默认加上 https:// + if (!proxyUrl.StartsWith("http://") && !proxyUrl.StartsWith("https://")) + { + proxyUrl = "https://" + proxyUrl; // 默认为 https + } + + // 如果目标 host 是 github.com,强制使用 https 协议 + var targetUri = new Uri(proxyUrl); + if (targetUri.Host == "github.com" && !proxyUrl.StartsWith("https://")) + { + proxyUrl = "https://" + targetUri.Host + targetUri.PathAndQuery; + } + + // 获取原始请求的查询字符串并添加到目标 URL 中 + string queryString = Request.QueryString.HasValue ? Request.QueryString.Value : string.Empty; + + // 拼接完整的 URL,包括查询字符串 + string targetUrl = proxyUrl + queryString; + + try + { + var client = _httpClientFactory.CreateClient(); + + // 创建请求头(转发客户端的请求头,除了 'Host') + var requestHeaders = Request.Headers + .Where(h => h.Key != "Host") // 排除 'Host' 头部 + .ToDictionary(h => h.Key, h => h.Value.ToString()); + + List> failedHeaders = new(); + + client.DefaultRequestHeaders.Clear(); + + // 设置目标主机名 + client.DefaultRequestHeaders.Host = targetUri.Host; + foreach (var header in requestHeaders) + { + // 排除反代理的转发头 + if (header.Key.StartsWith("X-Forwarded") || header.Key.StartsWith("X-Real")) + continue; + try + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + catch (Exception ex) + { + // 添加失败的头放到后面处理 + failedHeaders.Add(new(header.Key, header.Value)); + _logger.Debug($"Http头添加失败: Key: {header.Key} Value: {header.Value} {ex.Message}"); + } + } + + foreach (var header in client.DefaultRequestHeaders) + { + _logger.Debug($"[Request Header] Key: {header.Key} Value: {string.Join(",", header.Value)}"); + } + + // 动态获取客户端的 HTTP 请求方法 + var method = Request.Method; + + // 根据客户端请求方法动态代理请求 + HttpRequestMessage requestMessage = new HttpRequestMessage(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); + } + requestMessage.Content = content; + } + + // 发送请求并获取响应 + using (var response = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead)) + { + 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(); // 控制器返回空结果,响应已经在流中完成 + } + } + catch (Exception ex) + { + // 捕获异常并返回错误 + return StatusCode(500, $"An error occurred while proxying the request: {ex.Message}"); + } + } + } +} diff --git a/src/Controllers/iProxyController.cs b/src/Controllers/iProxyController.cs index 1576e76..1d8b102 100644 --- a/src/Controllers/iProxyController.cs +++ b/src/Controllers/iProxyController.cs @@ -45,6 +45,7 @@ namespace iFileProxy.Controllers TaskAddState.ErrQueueLengthLimit => (ActionResult)Ok(new CommonRsp() { Retcode = (int)TaskAddState.ErrQueueLengthLimit, Message = "服务器任务队列已满 请稍候重试!" }), TaskAddState.Pending => (ActionResult)Ok(new CommonRsp() { Retcode = (int)TaskAddState.Pending, Message = "已经添加到任务队列!" }), TaskAddState.ErrDisabledStreamTransferOrZeroSize => (ActionResult)Ok(new CommonRsp() { Retcode = (int)TaskAddState.ErrDisabledStreamTransferOrZeroSize, Message = "禁止0大小文件或者流式传输!" }), + TaskAddState.ErrKeywordForbidden => (ActionResult)Ok(new CommonRsp() { Retcode = (int)TaskAddState.ErrKeywordForbidden, Message = "禁止代理此url!" }), _ => (ActionResult)Ok(new CommonRsp() { Retcode = (int)TaskAddState.Success, Message = "succ default" }), }; } @@ -83,10 +84,14 @@ namespace iFileProxy.Controllers var d = _taskManager.GetTaskListByIpAddr(HttpContext); var taskInfo = _taskManager.GetTaskInfo(taskID); - if ((!_appConfig.SecurityOptions.AllowDifferentIPsForDownload && d.Where(x => x.TaskId == taskID).Any()) || taskInfo != null) + if ((!_appConfig.SecurityOptions.AllowDifferentIPsForDownload && d.Where(x => x.TaskId == taskID).Any()) || taskInfo?.Count > 0) { if (_appConfig.SecurityOptions.AllowDifferentIPsForDownload) { + if (taskInfo == null || taskInfo.Count == 0) + { + return Ok(new CommonRsp() { Message = "task not exists", Retcode = -1 }); + } fileName = taskInfo[0].FileName; } else diff --git a/src/Helpers/MasterHelper.cs b/src/Helpers/MasterHelper.cs index 3e65a44..3412caf 100644 --- a/src/Helpers/MasterHelper.cs +++ b/src/Helpers/MasterHelper.cs @@ -53,7 +53,7 @@ namespace iFileProxy.Helpers /// /// HttpContext /// - public static string? GetClientIPAddr(HttpContext c) + public static string GetClientIPAddr(HttpContext c) { // 尝试从 X-Forwarded-For 请求头获取客户端IP地址 string? clientIp = c.Request.Headers["X-Forwarded-For"].FirstOrDefault(); @@ -65,7 +65,7 @@ namespace iFileProxy.Helpers IPAddress? ipAddress = null; if (IPAddress.TryParse(clientIp, out ipAddress)) return ipAddress.ToString(); - return null; + return "127.0.0.1"; } /// diff --git a/src/Middleware/IPAccessLimitMiddleware.cs b/src/Middleware/IPAccessLimitMiddleware.cs index 5c161e5..739b6cc 100644 --- a/src/Middleware/IPAccessLimitMiddleware.cs +++ b/src/Middleware/IPAccessLimitMiddleware.cs @@ -9,24 +9,22 @@ using System.Text.Json; using System.Threading.Tasks; - public class IPAccessLimitMiddleware + public class IPAccessLimitMiddleware(RequestDelegate next, Dictionary> IPAccessCountDict, AppConfig appConfig) { - private readonly RequestDelegate _next; - private readonly Dictionary> _IPAccessCountDict; - private readonly int _dailyRequestLimitPerIP; - private readonly AppConfig _appConfig = AppConfig.GetCurrConfig(); - - public IPAccessLimitMiddleware(RequestDelegate next, Dictionary> IPAccessCountDict, int dailyRequestLimitPerIP) - { - _next = next; - _IPAccessCountDict = IPAccessCountDict; - _dailyRequestLimitPerIP = dailyRequestLimitPerIP; - } + private readonly RequestDelegate _next = next; + private readonly Dictionary> _IPAccessCountDict = IPAccessCountDict; public async Task InvokeAsync(HttpContext context) { + if (appConfig.SecurityOptions.BlockedClientIP.IndexOf(MasterHelper.GetClientIPAddr(context)) != -1) + { + context.Response.StatusCode = 403; + await context.Response.WriteAsJsonAsync(new CommonRsp { Retcode = 403, Message = "你的IP地址已经被管理员拉入黑名单!"}); + return; + } + // 获取需要跟踪的路由列表 - var routesToTrack = _appConfig.SecurityOptions.RoutesToTrack; + var routesToTrack = appConfig.SecurityOptions.RoutesToTrack; // 检查当前请求的路径是否在需要跟踪的路由列表中 foreach (var p in routesToTrack) @@ -52,11 +50,11 @@ { if (_IPAccessCountDict[dateStr].ContainsKey(ipStr)) { - if (_IPAccessCountDict[dateStr][ipStr] >= _dailyRequestLimitPerIP) + if (_IPAccessCountDict[dateStr][ipStr] >= appConfig.SecurityOptions.DailyRequestLimitPerIP) { context.Response.StatusCode = 200; context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 403, Message = "请求次数超过限制!" })); + await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 403, Message = "日请求次数超过限制! (GMT+8 时间00:00重置)" })); return; } _IPAccessCountDict[dateStr][ipStr]++; diff --git a/src/Models/CommonRsp.cs b/src/Models/CommonRsp.cs index d38463b..5e74b48 100644 --- a/src/Models/CommonRsp.cs +++ b/src/Models/CommonRsp.cs @@ -1,12 +1,21 @@ -namespace iFileProxy.Models +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace iFileProxy.Models { /// /// 通用Http Response /// public class CommonRsp { + [JsonPropertyName("message")] + [JsonProperty("message")] public string Message { get; set; } = "def msg"; + [JsonPropertyName("data")] + [JsonProperty("data")] public object Data { get; set; } = null; + [JsonPropertyName("retcode")] + [JsonProperty("retcode")] public int Retcode { get; set; } } } diff --git a/src/Models/Task.cs b/src/Models/Task.cs index d09a2ff..f507cc7 100644 --- a/src/Models/Task.cs +++ b/src/Models/Task.cs @@ -105,6 +105,10 @@ /// 文件不允许0大小或者流式(动态大小)传输 /// ErrDisabledStreamTransferOrZeroSize = 14, + /// + /// 触发关键词 + /// + ErrKeywordForbidden = 15, } public class DownloadFileInfo { /// diff --git a/src/Program.cs b/src/Program.cs index d75abf7..f1f5959 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -43,17 +43,15 @@ namespace iFileProxy builder.Host.UseSerilog(logger: Log.Logger); - builder.Services.AddSingleton>>(serviceProvider => - { - return new Dictionary>(); - }); - + // 注入依赖 builder.Services.AddSingleton(AppConfig.GetCurrConfig()); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton>>(); + // 添加验证服务 builder.Services.AddScoped(); @@ -73,11 +71,29 @@ namespace iFileProxy }; }); - var app = builder.Build(); + builder.Services.AddHttpClient(); - // 全局错误处理中间件 + var app = builder.Build(); + // 初始化缓存管理服务 + LocalCacheManager localCacheManager = new (app.Services); + + // 1. 配置CORS(要在Routing之前) + app.UseCors("AllowFrontend"); + + // 2. 配置静态文件(要在Routing之前) + var defaultFilesOptions = new DefaultFilesOptions(); + defaultFilesOptions.DefaultFileNames.Clear(); + defaultFilesOptions.DefaultFileNames.Add("index.html"); + app.UseDefaultFiles(defaultFilesOptions); + app.UseStaticFiles(); + + // 3. 强制使用HTTPS(要在Routing之前) + app.UseHttpsRedirection(); + + // 4. 错误处理(在RequestLogging之前) app.UseMiddleware(); + // 5. 请求日志记录(在Routing之前) app.UseSerilogRequestLogging(options => { options.EnrichDiagnosticContext = (diagCtx, httpCtx) => @@ -88,44 +104,23 @@ namespace iFileProxy }; }); - // Configure the HTTP request pipeline. - if (app.Environment.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(); - } + // 6. 路由配置 + app.UseRouting(); - // 检查自定义配置 - AppConfig.CheckAppConfig(app.Services); - - // 初始化缓存管理器 - LocalCacheManager localCacheManager = new(app.Services); - - - app.UseCors("AllowFrontend"); - - app.UseHttpsRedirection(); - - - var defaultFilesOptions = new DefaultFilesOptions(); - defaultFilesOptions.DefaultFileNames.Clear(); - defaultFilesOptions.DefaultFileNames.Add("index.html"); - - app.UseDefaultFiles(defaultFilesOptions); - - app.UseStaticFiles(); + // 7. 限制访问(IP访问限制,通常在认证之前) + app.UseMiddleware(); + // 8. 身份验证与授权 + app.UseAuthentication(); app.UseAuthorization(); - app.UseMiddleware( - app.Services.GetRequiredService>>(), - AppConfig.GetCurrConfig().SecurityOptions.DailyRequestLimitPerIP); - - // JWT中间件 + // 9. 配置JWT验证 app.UseMiddleware(); + // 10. 映射控制器 app.MapControllers(); + // 11. 启动应用 var dbGateService = app.Services.GetRequiredService(); SerilogConfig.CreateLogger(dbGateService); diff --git a/src/Services/GithubProxyService.cs b/src/Services/GithubProxyService.cs deleted file mode 100644 index 7b1aa7f..0000000 --- a/src/Services/GithubProxyService.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System.Text.RegularExpressions; -using iFileProxy.Config; -using iFileProxy.Models; - -namespace iFileProxy.Services -{ - public class GithubProxyService - { - private readonly ILogger _logger; - private readonly AppConfig _appConfig; - private readonly HttpClient _httpClient; - - private static readonly Regex[] URL_PATTERNS = - { - new(@"^(?:https?://)?github\.com/(?.+?)/(?.+?)/(?:releases|archive)/.*$"), - new(@"^(?:https?://)?github\.com/(?.+?)/(?.+?)/(?:blob|raw)/.*$"), - new(@"^(?:https?://)?raw\.(?:githubusercontent|github)\.com/(?.+?)/(?.+?)/.+?/.+$"), - new(@"^(?:https?://)?gist\.(?:githubusercontent|github)\.com/(?.+?)/.+?/.+$") - }; - - public GithubProxyService( - ILogger logger, - AppConfig appConfig, - HttpClient httpClient) - { - _logger = logger; - _appConfig = appConfig; - _httpClient = httpClient; - } - - public (bool isValid, string? author, string? repo) ValidateUrl(string url) - { - foreach (var pattern in URL_PATTERNS) - { - var match = pattern.Match(url); - if (match.Success) - { - return (true, match.Groups["author"].Value, match.Groups["repo"].Value); - } - } - return (false, null, null); - } - - public bool IsBlocked(string author, string repo) - { - return _appConfig.GithubProxyOptions.Blacklist.Contains($"{author}/{repo}") || - _appConfig.GithubProxyOptions.Blacklist.Contains(author); - } - - public string ProcessUrl(string url) - { - if (_appConfig.GithubProxyOptions.EnableJsDelivr && url.Contains("/blob/")) - { - return url.Replace("/blob/", "@") - .Replace("github.com", "cdn.jsdelivr.net/gh"); - } - else if (url.Contains("/blob/")) - { - return url.Replace("/blob/", "/raw/"); - } - return url; - } - - public async Task ProxyRequestAsync( - string url, - IHeaderDictionary headers, - CancellationToken cancellationToken = default) - { - try - { - var request = new HttpRequestMessage(HttpMethod.Get, url); - - // 复制请求头 - foreach (var header in headers) - { - if (!header.Key.Equals("Host", StringComparison.OrdinalIgnoreCase)) - { - request.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); - } - } - - var response = await _httpClient.SendAsync( - request, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken); - - // 检查文件大小 - var contentLength = response.Content.Headers.ContentLength; - if (contentLength.HasValue && contentLength.Value > _appConfig.GithubProxyOptions.SizeLimit) - { - return new ProxyResponse - { - Success = false, - StatusCode = StatusCodes.Status413PayloadTooLarge, - Message = "File too large" - }; - } - - return new ProxyResponse - { - Success = true, - StatusCode = (int)response.StatusCode, - Headers = response.Headers.ToDictionary(h => h.Key, h => h.Value.ToArray()), - Stream = await response.Content.ReadAsStreamAsync(cancellationToken), - ContentType = response.Content.Headers.ContentType?.ToString() - }; - } - catch (Exception ex) - { - _logger.LogError(ex, "Proxy request failed"); - return new ProxyResponse - { - Success = false, - StatusCode = StatusCodes.Status500InternalServerError, - Message = "Internal server error" - }; - } - } - } - - public class ProxyResponse - { - public bool Success { get; set; } - public int StatusCode { get; set; } - public string? Message { get; set; } - public Dictionary? Headers { get; set; } - public Stream? Stream { get; set; } - public string? ContentType { get; set; } - } -} \ No newline at end of file diff --git a/src/Services/TaskManager.cs b/src/Services/TaskManager.cs index bc26ffa..1ff04ae 100644 --- a/src/Services/TaskManager.cs +++ b/src/Services/TaskManager.cs @@ -94,6 +94,11 @@ namespace iFileProxy.Services string? clientIp = MasterHelper.GetClientIPAddr(c); 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) + return TaskAddState.ErrKeywordForbidden; + } + bool queue_task = false; // 如果当前并行任务量已经达到设定并行任务和列队上限 diff --git a/src/appsettings.json b/src/appsettings.json index 90a065a..42f9c78 100644 --- a/src/appsettings.json +++ b/src/appsettings.json @@ -10,13 +10,5 @@ "Key": "iFileProxy-JWT-Secret-Key-2024-Very-Long-Secret-Key-For-Security", "Issuer": "iFileProxy", "Audience": "iFileProxy.Client" - }, - "GithubProxy": { - "SizeLimit": 1073741824, // 1GB in bytes - "Blacklist": [ - "blockedUser1", - "blockedUser2/blockedRepo", - "blockedOrg/*" - ] } } diff --git a/src/iFileProxy.json b/src/iFileProxy.json index 809678d..601947a 100644 --- a/src/iFileProxy.json +++ b/src/iFileProxy.json @@ -17,7 +17,7 @@ "Download": { "SavePath": "./download/", // 临时文件保存位置 "ThreadNum": 4, // 下载线程数 - "MaxAllowedFileSize": 1000000000, // 允许代理的最大文件尺寸 + "MaxAllowedFileSize": 1000000000, // 允许代理的最大文件尺寸 单位: byte "MaxParallelTasks": 4, // 同一时间最大并行任务数 "MaxQueueLength": 60, // 最大等待队列长度 "Aria2cPath": "./lib/aria2c", @@ -32,6 +32,8 @@ "BlockedClientIP": [ // 禁止使用服务的客户端IP "127.0.0.1" ], + "BlockedKeyword": [ // 关键字黑名单 检测url是否包含 + ], "DailyRequestLimitPerIP": 200, // 单个IP每日最大请求次数限制 "RoutesToTrack": [ // 参加IP请求数统计的路径名单 "/Download", @@ -41,13 +43,7 @@ "AllowStreamTransferOrZeroSize": true, // 是否允许0大小文件或者动态大小文件下载 "EnableUserRegistration": true // 是否开放用户注册 }, - "GithubProxy": { - "SizeLimit": 10000000000, - "Blacklist": [ - "blockedUser1", - "blockedUser2/blockedRepo", - "blockedOrg/*" - ], - "EnableJsDelivr": false + "StreamProxy": { // 流代理设置 支持仓库clone + "SizeLimit": 10000000000 } } \ No newline at end of file diff --git a/src/wwwroot/index.html b/src/wwwroot/index.html index ef2993e..1653751 100644 --- a/src/wwwroot/index.html +++ b/src/wwwroot/index.html @@ -4,95 +4,287 @@ - Github文件下载加速 - - - + iFileProxy - 文件下载加速服务 + + +
+
提示
+

发现新版本界面,是否跳转?

+ +
-
-
-