简体   繁体   中英

How to validate uploaded file in ASP.Net Core

I'm using ASP.NET Core 2.2 and I'm using model binding for uploading file.

This is my UserViewModel

public class UserViewModel
{
    [Required(ErrorMessage = "Please select a file.")]
    [DataType(DataType.Upload)]
    public IFormFile Photo { get; set; }
}

This is MyView

@model UserViewModel

<form method="post"
      asp-action="UploadPhoto"
      asp-controller="TestFileUpload"
      enctype="multipart/form-data">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>

    <input asp-for="Photo" />
    <span asp-validation-for="Photo" class="text-danger"></span>
    <input type="submit" value="Upload"/>
</form>

And finally this is MyController

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhoto(UserViewModel userViewModel)
{
    if (ModelState.IsValid)
    {
        var formFile = userViewModel.Photo;
        if (formFile == null || formFile.Length == 0)
        {
            ModelState.AddModelError("", "Uploaded file is empty or null.");
            return View(viewName: "Index");
        }

        var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads");
        if (!Directory.Exists(uploadsRootFolder))
        {
            Directory.CreateDirectory(uploadsRootFolder);
        }

        var filePath = Path.Combine(uploadsRootFolder, formFile.FileName);
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            await formFile.CopyToAsync(fileStream).ConfigureAwait(false);
        }

        RedirectToAction("Index");
    }
    return View(viewName: "Index");
}

How can I limit uploaded files to lower than 5MB with specific extensions like.jpeg and.png? I think both of these validations are done in the ViewModel. But I don't know how to do that.

You could custom validation attribute MaxFileSizeAttribute like below

MaxFileSizeAttribute

public class MaxFileSizeAttribute : ValidationAttribute
{
    private readonly int _maxFileSize;
    public MaxFileSizeAttribute(int maxFileSize)
    {
        _maxFileSize = maxFileSize;
    }

    protected override ValidationResult IsValid(
    object value, ValidationContext validationContext)
    {
        var file = value as IFormFile;
        if (file != null)
        {
           if (file.Length > _maxFileSize)
            {
                return new ValidationResult(GetErrorMessage());
            }
        }

        return ValidationResult.Success;
    }

    public string GetErrorMessage()
    {
        return $"Maximum allowed file size is { _maxFileSize} bytes.";
    }
}

AllowedExtensionsAttribute

public class AllowedExtensionsAttribute : ValidationAttribute
{
    private readonly string[] _extensions;
    public AllowedExtensionsAttribute(string[] extensions)
    {
        _extensions = extensions;
    }
    
    protected override ValidationResult IsValid(
    object value, ValidationContext validationContext)
    {
        var file = value as IFormFile;
        if (file != null)
        {
            var extension = Path.GetExtension(file.FileName);
            if (!_extensions.Contains(extension.ToLower()))
            {
                return new ValidationResult(GetErrorMessage());
            }
        }
        
        return ValidationResult.Success;
    }

    public string GetErrorMessage()
    {
        return $"This photo extension is not allowed!";
    }
}

Add MaxFileSize attribute and AllowedExtensions attribute to Photo property

public class UserViewModel
{
        [Required(ErrorMessage = "Please select a file.")]
        [DataType(DataType.Upload)]
        [MaxFileSize(5* 1024 * 1024)]
        [AllowedExtensions(new string[] { ".jpg", ".png" })]
        public IFormFile Photo { get; set; }
 }

This is how I use the custom validation attributes, almost the same as @xueli-chen 's answer but production-ready.

FileExtensionsAttribute

using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Linq;

using Microsoft.AspNetCore.Http;

using NewsPassWebApi.Properties;

namespace NewsPassWebApi.Models.DataAnnotaions
{
    /// <summary>
    /// Validation attribute to assert an <see cref="IFormFile">IFormFile</see> property, field or parameter has a specific extention.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public sealed class FileExtensionsAttribute : ValidationAttribute
    {
        private string _extensions;

        /// <summary>
        /// Gets or sets the acceptable extensions of the file.
        /// </summary>
        public string Extensions
        {
            get
            {
                // Default file extensions match those from jquery validate.
                return string.IsNullOrEmpty(_extensions) ? "png,jpg,jpeg,gif" : _extensions;
            }
            set
            {
                _extensions = value;
            }
        }

        private string ExtensionsNormalized
        {
            get
            {
                return Extensions.Replace(" ", "", StringComparison.Ordinal).ToUpperInvariant();
            }
        }

        /// <summary>
        /// Parameterless constructor.
        /// </summary>
        public FileExtensionsAttribute() : base(() => Resources.FileExtensionsAttribute_ValidationError)
        { }

        /// <summary>
        /// Override of <see cref="ValidationAttribute.IsValid(object)"/>
        /// </summary>
        /// <remarks>
        /// This method returns <c>true</c> if the <paramref name="value"/> is null.  
        /// It is assumed the <see cref="RequiredAttribute"/> is used if the value may not be null.
        /// </remarks>
        /// <param name="value">The value to test.</param>
        /// <returns><c>true</c> if the value is null or it's extension is not invluded in the set extensionss</returns>
        public override bool IsValid(object value)
        {
            // Automatically pass if value is null. RequiredAttribute should be used to assert a value is not null.
            if (value == null)
            {
                return true;
            }

            // We expect a cast exception if the passed value was not an IFormFile.
            return ExtensionsNormalized.Split(",").Contains(Path.GetExtension(((IFormFile)value).FileName).ToUpperInvariant());
        }

        /// <summary>
        /// Override of <see cref="ValidationAttribute.FormatErrorMessage"/>
        /// </summary>
        /// <param name="name">The name to include in the formatted string</param>
        /// <returns>A localized string to describe the acceptable extensions</returns>
        public override string FormatErrorMessage(string name)
        {
            return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, Extensions);
        }
    }
}

FileSizeAttribute

using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;

using Microsoft.AspNetCore.Http;

using NewsPassWebApi.Properties;

namespace NewsPassWebApi.Models.DataAnnotaions
{
    /// <summary>
    /// Validation attribute to assert an <see cref="IFormFile">IFormFile</see> property, field or parameter does not exceed a maximum size.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public sealed class FileSizeAttribute : ValidationAttribute
    {
        /// <summary>
        /// Gets the maximum acceptable size of the file.
        /// </summary>
        public long MaximumSize { get; private set; }

        /// <summary>
        /// Gets or sets the minimum acceptable size of the file.
        /// </summary>
        public int MinimumSize { get; set; }

        /// <summary>
        /// Constructor that accepts the maximum size of the file.
        /// </summary>
        /// <param name="maximumSize">The maximum size, inclusive.  It may not be negative.</param>
        public FileSizeAttribute(int maximumSize) : base(() => Resources.FileSizeAttribute_ValidationError)
        {
            MaximumSize = maximumSize;
        }

        /// <summary>
        /// Override of <see cref="ValidationAttribute.IsValid(object)"/>
        /// </summary>
        /// <remarks>
        /// This method returns <c>true</c> if the <paramref name="value"/> is null.  
        /// It is assumed the <see cref="RequiredAttribute"/> is used if the value may not be null.
        /// </remarks>
        /// <param name="value">The value to test.</param>
        /// <returns><c>true</c> if the value is null or it's size is less than or equal to the set maximum size</returns>
        /// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
        public override bool IsValid(object value)
        {
            // Check the lengths for legality
            EnsureLegalSizes();

            // Automatically pass if value is null. RequiredAttribute should be used to assert a value is not null.
            // We expect a cast exception if the passed value was not an IFormFile.
            var length = value == null ? 0 : ((IFormFile)value).Length;

            return value == null || (length >= MinimumSize && length <= MaximumSize);
        }

        /// <summary>
        /// Override of <see cref="ValidationAttribute.FormatErrorMessage"/>
        /// </summary>
        /// <param name="name">The name to include in the formatted string</param>
        /// <returns>A localized string to describe the maximum acceptable size</returns>
        /// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
        public override string FormatErrorMessage(string name)
        {
            EnsureLegalSizes();

            string errorMessage = MinimumSize != 0 ? Resources.FileSizeAttribute_ValidationErrorIncludingMinimum : ErrorMessageString;

            // it's ok to pass in the minLength even for the error message without a {2} param since String.Format will just ignore extra arguments
            return string.Format(CultureInfo.CurrentCulture, errorMessage, name, MaximumSize, MinimumSize);
        }

        /// <summary>
        /// Checks that MinimumSize and MaximumSize have legal values.  Throws InvalidOperationException if not.
        /// </summary>
        private void EnsureLegalSizes()
        {
            if (MaximumSize < 0)
            {
                throw new InvalidOperationException(Resources.FileSizeAttribute_InvalidMaxSize);
            }

            if (MaximumSize < MinimumSize)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.RangeAttribute_MinGreaterThanMax, MaximumSize, MinimumSize));
            }
        }
    }
}

now you can use those like any built-in validation attribute including custom/localized error messages, max-min file sizes, and file extensions.

You can implement IValidatableObject to validate your model.

public class UserViewModel : IValidatableObject
    {
        [Required(ErrorMessage = "Please select a file.")]
        [DataType(DataType.Upload)]
        public IFormFile Photo { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var photo = ((UserViewModel)validationContext.ObjectInstance).Photo;
            var extension = Path.GetExtension(photo.FileName);
            var size = photo.Length;

            if (!extension.ToLower().Equals(".jpg"))
                yield return new ValidationResult("File extension is not valid.");

           if(size > (5 * 1024 * 1024))
                yield return new ValidationResult("File size is bigger than 5MB.");
        }
    }

Following previous comment , you can add this class:

public class ValidateModelStateFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (context.ModelState.IsValid)
        {
            return;
        }

        var validationErrors = context.ModelState
            .Keys
            .SelectMany(k => context.ModelState[k].Errors)
            .Select(e => e.ErrorMessage)
            .ToArray();

        var json = new JsonErrorResponse
        {
            Messages = validationErrors
        };

        context.Result = new BadRequestObjectResult(json);
    }
}

Finally, add the filter to the controllers in Startup class:

 services.AddControllers(options =>
            {
                options.Filters.Add(typeof(ValidateModelStateFilter));
            })

Tested on .Net 5. How about more elastic attribute with custom object (you can use own ActionResult class)

[AttributeUsage(AttributeTargets.Method)]
public class MaxFileSizeAttribute : Attribute, IAsyncActionFilter
{
    public MaxFileSizeAttribute(int sizeInByte) { this._maxFileSize = sizeInByte; }
    private int _maxFileSize;
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (
            context
            .HttpContext
            .Request
            .Form != null
            && context.HttpContext.Request.Form.Files.Count > 0
            )
        {

            if (
                context
                .HttpContext
                .Request
                .Form
                .Files
                .Any(x => x.Length >= _maxFileSize)
                )
            {
                context
                    .Result = new ObjectResult($"Max file size is {_maxFileSize} bytes"
                    /*put here whatever object you want*/)
                    {
                        StatusCode = 413 //Payload Too Large
                    };
            } else { await next(); }
        } else { await next(); }

    }
}

And use it in controller:

[MaxFileSize(1024)]
public async Task<ActionResult> SendImages([FromForm] SendAttachment request)
{
 return Ok();
}

You can do like below code

    var formFile = userViewModel.Photo;
    var extension = Path.GetExtension(formFile.FileName);
    var allowedExtensions = new[] { ".jpeg", ".png" };

    if (formFile == null || formFile.Length == 0)
    {
        ModelState.AddModelError("", "Uploaded file is empty or null.");
        return View(viewName: "Index");
    }


    if (formFile != null && formFile.ContentLength > 0 && 
     allowedExtensions.Contains(extension.ToLower()) 
     && formFile.ContentLength <= (5 * 1024 * 1024))
    {
        //do some logic here because the data is smaller than 5mb   

    }
    else 
    { 
        // error
        return View("Error");
    }

Above solutions are validating file by its extension (which is just the filename really). That's not secure, as it allows loading executables (pretending to be image or so).

You should be comparing signature of the file with a pre-defined list of allowed signatures. See the File signature validation section in MS docs.

I then call the IsFileValid() method from controllers.


public static bool IsFileValid(IFormFile file)
{
    using (var reader = new BinaryReader(file.OpenReadStream()))
    {
        var signatures = _fileSignatures.Values.SelectMany(x => x).ToList();  // flatten all signatures to single list
        var headerBytes = reader.ReadBytes(_fileSignatures.Max(m => m.Value.Max(n => n.Length)));
        bool result = signatures.Any(signature => headerBytes.Take(signature.Length).SequenceEqual(signature));
        return result;
    }
}

private static readonly Dictionary<string, List<byte[]>> _fileSignatures = new()
{
    { ".gif", new List<byte[]> { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
    { ".png", new List<byte[]> { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } },
    { ".jpeg", new List<byte[]>
        {
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xEE },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
        }
    },
    { ".jpeg2000", new List<byte[]> { new byte[] { 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A } } },
    
    { ".jpg", new List<byte[]>
        {
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xEE },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
        }
    },
    { ".zip", new List<byte[]> //also docx, xlsx, pptx, ...
        {
            new byte[] { 0x50, 0x4B, 0x03, 0x04 },
            new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
            new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
            new byte[] { 0x50, 0x4B, 0x05, 0x06 },
            new byte[] { 0x50, 0x4B, 0x07, 0x08 },
            new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
        }
    },

    { ".pdf", new List<byte[]> { new byte[] { 0x25, 0x50, 0x44, 0x46 } } },
    { ".z", new List<byte[]>
        {
            new byte[] { 0x1F, 0x9D },
            new byte[] { 0x1F, 0xA0 }
        }
    },
    { ".tar", new List<byte[]>
        {
            new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30 , 0x30 },
            new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20 , 0x00 },
        }
    },
    { ".tar.z", new List<byte[]>
        {
            new byte[] { 0x1F, 0x9D },
            new byte[] { 0x1F, 0xA0 }
        }
    },
    { ".tif", new List<byte[]>
        {
            new byte[] { 0x49, 0x49, 0x2A, 0x00 },
            new byte[] { 0x4D, 0x4D, 0x00, 0x2A }
        }
    },
    { ".tiff", new List<byte[]>
        {
            new byte[] { 0x49, 0x49, 0x2A, 0x00 },
            new byte[] { 0x4D, 0x4D, 0x00, 0x2A }
        }
    },
    { ".rar", new List<byte[]>
        {
            new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07 , 0x00 },
            new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07 , 0x01, 0x00 },
        }
    },
    { ".7z", new List<byte[]>
        {
            new byte[] { 0x37, 0x7A, 0xBC, 0xAF, 0x27 , 0x1C },
        }
    },
    { ".txt", new List<byte[]>
        {
            new byte[] { 0xEF, 0xBB , 0xBF },
            new byte[] { 0xFF, 0xFE},
            new byte[] { 0xFE, 0xFF },
            new byte[] { 0x00, 0x00, 0xFE, 0xFF },
        }
    },
    { ".mp3", new List<byte[]>
        {
            new byte[] { 0xFF, 0xFB },
            new byte[] { 0xFF, 0xF3},
            new byte[] { 0xFF, 0xF2},
            new byte[] { 0x49, 0x44, 0x43},
        }
    },
};

More signatures can be found on Wikipedia .

Secure Extension Validation

With the help of this solution and this solution I could come up with a combined solution which works as a DataAnnotation also prevents files like exe from being injected maliciously. As explained well by FFFffff , checking extensions strings only

allows loading executables (pretending to be image or so)

So Here it is:

public class AllowedFileExtensions : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        var file = value as IFormFile;
        if (file != null)
        {
            if (!IsFileValid(file))
            {
                // get the allowed extensions to show in ValidationResult
                string? keys = null;
                foreach (var key in _fileSignatures.Keys)
                {
                    keys += key + ", ";
                }

                return new ValidationResult($"File type is not allowed. These extensions are allowed only: {keys}");
            }
        }

        return ValidationResult.Success;
    }


    public static bool IsFileValid(IFormFile file)
    {
        using (var reader = new BinaryReader(file.OpenReadStream()))
        {
            var signatures = _fileSignatures.Values.SelectMany(x => x).ToList();  // flatten all signatures to single list
            var headerBytes = reader.ReadBytes(_fileSignatures.Max(m => m.Value.Max(n => n.Length)));
            bool result = signatures.Any(signature => headerBytes.Take(signature.Length).SequenceEqual(signature));
            return result;
        }
    }

    public static Dictionary<string, List<byte[]>> _fileSignatures = new()
    {
        { ".jpeg", new List<byte[]>
            {
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xEE },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
            }
        },
        { ".jpg", new List<byte[]>
            {
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xEE },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
            }
        },
        { ".jpeg2000", new List<byte[]> { new byte[] { 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A } } },
        { ".png", new List<byte[]> { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } },
        // { ".gif", new List<byte[]> { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
        // { ".zip", new List<byte[]> //also docx, xlsx, pptx, ...
        //     {
        //         new byte[] { 0x50, 0x4B, 0x03, 0x04 },
        //         new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
        //         new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
        //         new byte[] { 0x50, 0x4B, 0x05, 0x06 },
        //         new byte[] { 0x50, 0x4B, 0x07, 0x08 },
        //         new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
        //     }
        // },

        // { ".pdf", new List<byte[]> { new byte[] { 0x25, 0x50, 0x44, 0x46 } } },
        // { ".z", new List<byte[]>
        //     {
        //         new byte[] { 0x1F, 0x9D },
        //         new byte[] { 0x1F, 0xA0 }
        //     }
        // },
        // { ".tar", new List<byte[]>
        //     {
        //         new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30 , 0x30 },
        //         new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20 , 0x00 },
        //     }
        // },
        // { ".tar.z", new List<byte[]>
        //     {
        //         new byte[] { 0x1F, 0x9D },
        //         new byte[] { 0x1F, 0xA0 }
        //     }
        // },
        // { ".tif", new List<byte[]>
        //     {
        //         new byte[] { 0x49, 0x49, 0x2A, 0x00 },
        //         new byte[] { 0x4D, 0x4D, 0x00, 0x2A }
        //     }
        // },
        // { ".tiff", new List<byte[]>
        //     {
        //         new byte[] { 0x49, 0x49, 0x2A, 0x00 },
        //         new byte[] { 0x4D, 0x4D, 0x00, 0x2A }
        //     }
        // },
        // { ".rar", new List<byte[]>
        //     {
        //         new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07 , 0x00 },
        //         new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07 , 0x01, 0x00 },
        //     }
        // },
        // { ".7z", new List<byte[]>
        //     {
        //         new byte[] { 0x37, 0x7A, 0xBC, 0xAF, 0x27 , 0x1C },
        //     }
        // },
        // { ".txt", new List<byte[]>
        //     {
        //         new byte[] { 0xEF, 0xBB , 0xBF },
        //         new byte[] { 0xFF, 0xFE},
        //         new byte[] { 0xFE, 0xFF },
        //         new byte[] { 0x00, 0x00, 0xFE, 0xFF },
        //     }
        // },
        // { ".mp3", new List<byte[]>
        //     {
        //         new byte[] { 0xFF, 0xFB },
        //         new byte[] { 0xFF, 0xF3},
        //         new byte[] { 0xFF, 0xF2},
        //         new byte[] { 0x49, 0x44, 0x43},
        //     }
        // },
    };
}

I purposely commented some extensions from the _fileSignatures Dictionary to block them.

Now just add [AllowedFileExtensions] to your Controller's method signature directly or Model:

public async Task<IActionResult> AddPhoto([AllowedFileExtensions] IFormFile file) { }

Here is the output if a non-allowed file is selected:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "00-94aa03b188cdf7c402ff22d809ad1bd4-4c1a49080c27213d-00",
    "errors": {
        "files": [
            "File type is not allowed. These extensions are allowed only: .jpeg, .jpg, .jpeg2000, .png, "
        ]
    }
}

Just a quick one if anyone is looking for a PDF file validator:

 { ".pdf", new List<byte[]> { new byte[] { 0x25, 0x50, 0x44, 0x46 } } },

See the brilliant site:

https://www.garykessler.net/library/file_sigs.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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