简体   繁体   English

提取文件头签名,因为它直接流式传输到 ASP.NET Core 中的磁盘

[英]Extract the file header signature as it is being streamed directly to disk in ASP.NET Core

I have an API method that streams uploaded files directly to disk to be scanned with a virus checker.我有一个 API 方法,可以将上传的文件直接流式传输到磁盘,以便使用病毒检查程序进行扫描。 Some of these files can be quite large, so IFormFile is a no go:其中一些文件可能非常大,因此 IFormFile 是不可行的:

Any single buffered file exceeding 64 KB is moved from memory to a temp file on disk.任何超过 64 KB 的单个缓冲文件都会从内存移动到磁盘上的临时文件。 Source: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1来源: https : //docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1

I have a working example that uses multipart/form-data and a really nice NuGet package that takes out the headache when working with multipart/form-data , and it works well, however I want to add a file header signature check , to make sure that the file type defined by the client is actually what they say it is.我有一个使用 multipart/form-data 的工作示例和一个非常好的 NuGet 包,它消除了使用 multipart/form-data 时的头痛,它运行良好,但是我想添加一个文件签名检查,以使确保客户端定义的文件类型实际上是他们所说的。 I can't rely on the file extension to do this securely, but I can use the file header signature to make it at least a bit more secure.我不能依靠文件扩展名来安全地执行此操作,但我可以使用文件头签名使其至少更安全一些。 Since I'm am streaming directly to disk, how can I extract the first bytes as it's going through the file stream?由于我直接流式传输到磁盘,如何在它通过文件流时提取第一个字节?

[DisableFormValueModelBinding] // required for form binding
[ValidateMimeMultipartContent] // simple check to make sure this is a multipart form
[FileUploadOperation(typeof(SwaggerFileItem))] // used to define the Swagger schema
[RequestSizeLimit(31457280)] // 30MB
[RequestFormLimits(MultipartBodyLengthLimit = 31457280)]
public async Task<IActionResult> PostAsync([FromRoute] int customerId)
{
    // place holders
    var uploadLocation = string.Empty;
    var trustedFileNameForDisplay = string.Empty;

    // this is using a nuget package that does the hard work on reading the multipart form-data.... using UploadStream;
    var model = await this.StreamFiles<FileItem>(async x =>
    {
        // never trust the client
        trustedFileNameForDisplay = WebUtility.HtmlEncode(Path.GetFileName(x.FileName));

        // determien the quarantine location
        uploadLocation = GetUploadLocation(trustedFileNameForDisplay);

        // stream the input stream to the file stream
        // importantly this should never load the file into memory
        // it should be a straight pass through to disk
        await using var fs = System.IO.File.Create(uploadLocation, BufSize);
        
        // --> How do I extract the file signature? I.e. a copy of the header bytes as it is being streamed??? <--
        await x.OpenReadStream().CopyToAsync(fs);
    });

    // The model state can now be checked
    if (!ModelState.IsValid)
    {
        // delete the file
        DeleteFileIfExists(uploadLocation);

        // return a bad request
        ThrowProblemDetails(ModelState, StatusCodes.Status400BadRequest);
    }

    // map as much as we can
    var request = _mapper.Map<CreateAttachmentRequest>(model);

    // map the remaining properties
    request.CustomerId = customerId;
    request.UploadServer = Environment.MachineName;
    request.uploadLocation = uploadLocation;
    request.FileName = trustedFileNameForDisplay;

    // call mediator with this request to send it over WCF to Pulse Core.
    var result = await _mediator.Send(request);

    // build response
    var response = new FileResponse { Id = result.FileId, CustomerId = customerId, ExternalId = request.ExternalId };

    // return the 201 with the appropriate response
    return CreatedAtAction(nameof(GetFile), new { fileId = response.Id, customerId = response.customerId }, response);
}

The section I'm stuck on is around the line await x.OpenReadStream().CopyToAsync(fs);我被困在这条线附近await x.OpenReadStream().CopyToAsync(fs); . . I would like to pull out the file header here as the stream is being copied to the FileStream .当流被复制到FileStream我想在这里拉出文件头。 Is there a way to add some kind of inspector?有没有办法添加某种检查员? I don't want to read the entire stream again , just the header.我不想再次阅读整个流,只是标题。

Update更新

Based on the answer given by @Ackdari I have successfully switched the code to extract the header from the uploaded file stream.根据@Ackdari 给出的答案,我已成功切换代码以从上传的文件流中提取标头。 I don't know if this could be made any more efficient, but it does work:我不知道这是否可以提高效率,但它确实有效:

//...... removed for clarity
var model = await this.StreamFiles<FileItem>(async x =>
{
    trustedFileNameForDisplay = WebUtility.HtmlEncode(Path.GetFileName(x.FileName));
    quarantineLocation = QuarantineLocation(trustedFileNameForDisplay);

    await using (var fs = System.IO.File.Create(quarantineLocation, BufSize))
    {
        await x.OpenReadStream().CopyToAsync(fs);

        fileFormat = await FileHelpers.GetFileFormatFromFileHeader(fs);
    }
});
//...... removed for clarity

and

// using https://github.com/AJMitev/FileTypeChecker
public static async Task<IFileType> GetFileFormatFromFileHeader(FileStream fs)
{
    IFileType fileFormat = null;
    fs.Position = 0;
    var headerData = new byte[40];
    var bytesRead = await fs.ReadAsync(headerData, 0, 40);
    if (bytesRead > 0)
    {
        await using (var ms = new MemoryStream(headerData))
        {
            if (!FileTypeValidator.IsTypeRecognizable(ms))
            {
                return null;
            }

            fileFormat = FileTypeValidator.GetFileType(ms);
        }
    }

    return fileFormat;
}

You may want to consider reading the header yourself dependent on which file type is expected您可能需要考虑根据预期的文件类型自行阅读标题

int n = 4; // length of header

var headerData = new byte[n];
var bytesRead = 0;
while (bytesRead < n)
    bytesRead += await x.ReadAsync(headerData.AsMemory(bytesRead));

CheckHeader(headerData);

await fs.WriteAsync(headerData.AsMemory());

await x.CopyToAsync(fs);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM