You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core(C#)中如何验证文件内容以防范误导性扩展名?

ASP.NET Core 基于文件内容验证文本文件的实现

针对你需要验证.abc文件实际为文本内容、防止可执行文件重命名上传的需求,可以通过以下几步实现可靠的内容验证:

核心思路

  1. 排除可执行文件特征:可执行文件(如EXE、DLL、ELF等)开头有固定的字节签名,先检查文件前几个字节直接排除这类文件。
  2. 验证文本有效性:通过检查内容中可打印字符的比例,或尝试用文本编码解码,判断文件是否为有效文本(可执行文件包含大量非打印/不可解码的字节)。

实现代码

1. 内容验证工具类

创建一个静态工具类封装验证逻辑,避免重复代码:

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

public static class FileContentValidator
{
    // 常见可执行/非文本文件的开头字节签名
    private static readonly HashSet<byte[]> NonTextSignatures = new HashSet<byte[]>(new ByteArrayComparer())
    {
        new byte[] { 0x4D, 0x5A },      // EXE/DLL的MZ头
        new byte[] { 0x7F, 0x45, 0x4C, 0x46 }, // ELF格式(Linux可执行文件)
        new byte[] { 0xCA, 0xFE, 0xBA, 0xBE }, // Java Class文件
        new byte[] { 0xFF, 0xD8, 0xFF }, // JPG图片
        new byte[] { 0x89, 0x50, 0x4E, 0x47 }  // PNG图片
    };

    // 判断文件开头是否匹配非文本签名
    private static bool HasNonTextHeader(byte[] headerBytes)
    {
        foreach (var signature in NonTextSignatures)
        {
            if (headerBytes.Length >= signature.Length 
                && headerBytes.Take(signature.Length).SequenceEqual(signature))
            {
                return true;
            }
        }
        return false;
    }

    // 检查内容中可打印字符比例,判断是否为文本
    private static bool IsValidTextContent(byte[] contentBytes)
    {
        int printableChars = 0;
        int totalBytes = contentBytes.Length;

        foreach (byte b in contentBytes)
        {
            // 包含可打印ASCII字符(32-126)+ 常见控制字符(换行、回车、制表符)
            if ((b >= 32 && b <= 126) || b == 10 || b == 13 || b == 9)
            {
                printableChars++;
            }
        }

        // 可打印字符占比超过90%则认定为文本(可根据需求调整阈值)
        return (printableChars / (double)totalBytes) > 0.9;
    }

    // 针对.abc文件的专属验证方法
    public static async Task<bool> IsValidAbcTextFile(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return false;

        using (var stream = file.OpenReadStream())
        {
            // 读取前16字节检查非文本签名
            var headerBuffer = new byte[16];
            await stream.ReadAsync(headerBuffer, 0, headerBuffer.Length);
            if (HasNonTextHeader(headerBuffer))
                return false;

            // 读取前4KB内容验证文本有效性(避免加载大文件占用内存)
            var contentBuffer = new byte[4096];
            int bytesRead = await stream.ReadAsync(contentBuffer, 0, contentBuffer.Length);
            
            // 空文件直接判定无效
            if (bytesRead == 0)
                return false;

            return IsValidTextContent(contentBuffer.Take(bytesRead).ToArray());
        }
    }
}

// 用于HashSet比较字节数组的自定义比较器
public class ByteArrayComparer : IEqualityComparer<byte[]>
{
    public bool Equals(byte[] x, byte[] y)
    {
        if (x == null || y == null)
            return x == y;
        return x.SequenceEqual(y);
    }

    public int GetHashCode(byte[] obj)
    {
        if (obj == null)
            return 0;
        int hash = 17;
        foreach (byte b in obj)
        {
            hash = hash * 31 + b.GetHashCode();
        }
        return hash;
    }
}

2. 在控制器中使用验证

在文件上传接口中,先验证扩展名(快速过滤),再执行内容验证:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Threading.Tasks;

[ApiController]
[Route("api/files")]
public class FileUploadController : ControllerBase
{
    [HttpPost("upload")]
    public async Task<IActionResult> UploadAbcFile(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return BadRequest("请选择要上传的.abc文件");

        // 先检查扩展名(快速过滤非.abc文件)
        var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();
        if (fileExtension != ".abc")
            return BadRequest("仅支持.abc格式的文本文件");

        // 执行内容验证
        bool isContentValid = await FileContentValidator.IsValidAbcTextFile(file);
        if (!isContentValid)
            return BadRequest("上传的文件不是有效的文本文件,禁止上传");

        // 后续保存文件逻辑(示例)
        var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "abc-uploads");
        Directory.CreateDirectory(uploadDir);
        var savePath = Path.Combine(uploadDir, Path.GetFileName(file.FileName));

        using (var stream = new FileStream(savePath, FileMode.Create))
        {
            await file.CopyToAsync(stream);
        }

        return Ok(new { Message = "文件上传成功", FilePath = savePath });
    }
}

补充优化建议

  • 编码兼容:如果需要支持GB2312等非UTF-8编码的文本,可以在IsValidTextContent中添加多编码尝试解码的逻辑,避免误判合法文本。
  • 阈值调整:可打印字符的比例阈值可根据业务需求调整,如果允许包含较多特殊字符,可适当降低阈值(如80%)。
  • 大文件处理:如果需要支持超大文件,可分段读取内容验证,避免一次性加载过多数据到内存。

内容的提问来源于stack exchange,提问作者Md. Mustafizur Rahman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 03:11:01