1
This commit is contained in:
@@ -16,10 +16,12 @@ global using FluentMigrator;
|
||||
global using FluentMigrator.Runner;
|
||||
global using Fluid;
|
||||
global using Fluid.ViewEngine;
|
||||
global using ICSharpCode.SharpZipLib.Zip;
|
||||
global using Microsoft.AspNetCore.Authentication;
|
||||
global using Microsoft.AspNetCore.Authorization;
|
||||
global using Microsoft.AspNetCore.Hosting;
|
||||
global using Microsoft.AspNetCore.Http;
|
||||
global using Microsoft.AspNetCore.Http.Extensions;
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
global using Microsoft.Data.SqlClient;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
@@ -28,6 +30,8 @@ global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using Microsoft.Extensions.Options;
|
||||
global using Microsoft.IdentityModel.Tokens;
|
||||
global using MySql.Data.MySqlClient;
|
||||
global using Npgsql;
|
||||
global using Yes.Domain.Articles;
|
||||
global using Yes.Domain.Categories;
|
||||
global using Yes.Domain.Comments;
|
||||
@@ -44,6 +48,4 @@ global using Yes.Infrastructure.Authorizations.Identity.Authroizations;
|
||||
global using Yes.Infrastructure.Authorizations.Identity.Context;
|
||||
global using Yes.Infrastructure.Data.DbContexts;
|
||||
global using Yes.Infrastructure.Migrator.Migrations;
|
||||
global using MySql.Data.MySqlClient;
|
||||
global using Npgsql;
|
||||
global using Microsoft.AspNetCore.Http.Extensions;
|
||||
global using System.Security;
|
||||
123
Yes.Infrastructure/Helpers/ZipHelper.cs
Normal file
123
Yes.Infrastructure/Helpers/ZipHelper.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
namespace Yes.Infrastructure.Helpers
|
||||
{
|
||||
public class ZipHelper
|
||||
{
|
||||
public static void ExtractToDirectory(string zipPath, string targetDirectory, string themeName)
|
||||
{
|
||||
if (!Directory.Exists(targetDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
}
|
||||
|
||||
var requiredFileName = "config.json";
|
||||
|
||||
using (var zipFile = new ZipFile(zipPath))
|
||||
{
|
||||
bool fileFound = false;
|
||||
|
||||
foreach (ZipEntry entry in zipFile)
|
||||
{
|
||||
if (entry.IsDirectory)
|
||||
continue;
|
||||
|
||||
string entryName = entry.Name.Trim('/');
|
||||
if (string.IsNullOrEmpty(entryName))
|
||||
continue;
|
||||
|
||||
string[] parts = entryName.Split('/');
|
||||
|
||||
// 文件名必须匹配
|
||||
if (!parts[^1].Equals(requiredFileName, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
// 所有父级目录必须为 themeName
|
||||
bool allParentsAreTopDir = parts.Take(parts.Length - 1)
|
||||
.All(p => p.Equals(themeName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (allParentsAreTopDir)
|
||||
{
|
||||
fileFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileFound)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
$"ZIP 文件中未找到配置文件: {requiredFileName}");
|
||||
}
|
||||
|
||||
|
||||
foreach (ZipEntry entry in zipFile)
|
||||
{
|
||||
string originalPath = entry.Name.Trim('/');
|
||||
if (string.IsNullOrEmpty(originalPath)) continue;
|
||||
|
||||
string[] parts = originalPath.Split('/');
|
||||
int repeatCount = 0;
|
||||
|
||||
for (int i = 0; i < parts.Length; i++)
|
||||
{
|
||||
if (parts[i] == themeName)
|
||||
{
|
||||
repeatCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var newPathParts = new List<string>();
|
||||
newPathParts.Add(themeName);
|
||||
newPathParts.AddRange(parts.Skip(repeatCount));
|
||||
|
||||
string newRelativePath = Path.Combine(newPathParts.ToArray());
|
||||
string targetPath = Path.Combine(targetDirectory, newRelativePath);
|
||||
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
Directory.CreateDirectory(targetPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsSafeFile(entry.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
using (var stream = zipFile.GetInputStream(entry))
|
||||
{
|
||||
using (var fileStream = File.Create(targetPath))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static string SanitizeEntryPath(string entryPath, string extractRoot)
|
||||
{
|
||||
// 路径安全处理
|
||||
string fullPath = Path.GetFullPath(Path.Combine(extractRoot, entryPath));
|
||||
|
||||
// 验证是否在解压根目录内(防止路径遍历)
|
||||
if (!fullPath.StartsWith(Path.GetFullPath(extractRoot)))
|
||||
{
|
||||
throw new SecurityException("非法路径访问!");
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
private static bool IsSafeFile(string fileName)
|
||||
{
|
||||
var ext = Path.GetExtension(fileName).ToLower();
|
||||
return new[] { ".liquid", ".js", ".css", ".html", ".md", ".ttf", ".svg", ".eot", ".woff", ".woff2", ".toml", ".xml" }.Contains(ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@
|
||||
else
|
||||
{
|
||||
title = "服务器内部错误";
|
||||
message = "发生了一个未经处理的异常。";
|
||||
message = ex.Message;
|
||||
_logger.LogError(ex, "Exception");
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.2.efcore.9.0.0" />
|
||||
<PackageReference Include="MySql.Data" Version="9.3.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user