¿Cómo verificar el tipo MIME del archivo con JavaScript antes de cargarlo?

Resuelto Question Overflow asked hace 11 años • 12 respuestas

He leído esto y esto preguntas que parecen sugerir que el tipo de archivo MIME podría verificarse usando JavaScript en el lado del cliente. Ahora entiendo que la validación real aún debe realizarse en el lado del servidor. Quiero realizar una verificación del lado del cliente para evitar el desperdicio innecesario de recursos del servidor.

Para probar si esto se puede hacer en el lado del cliente, cambié la extensión de un JPEGarchivo de prueba .pngy elegí el archivo para cargar. Antes de enviar el archivo, consulto el objeto del archivo usando una consola JavaScript:

document.getElementsByTagName('input')[0].files[0];

Esto es lo que obtengo en Chrome 28.0:

Archivo {webkitRelativePath: "", lastModifiedDate: martes 16 de octubre de 2012 10:00:00 GMT+0000 (UTC), nombre: "test.png", tipo: "image/png", tamaño: 500055…}

Muestra el tipo, image/pnglo que parece indicar que la verificación se realiza según la extensión del archivo en lugar del tipo MIME. Probé Firefox 22.0 y me da el mismo resultado. Pero según las especificaciones del W3C , MIME Sniffing se debe implementar

¿Tengo razón al decir que no hay forma de comprobar el tipo MIME con JavaScript en este momento? ¿O me estoy perdiendo algo?

Question Overflow avatar Aug 18 '13 20:08 Question Overflow
Aceptado

Puede determinar fácilmente el tipo MIME del archivo con JavaScript FileReaderantes de cargarlo en un servidor. Estoy de acuerdo en que deberíamos preferir la verificación del lado del servidor a la del lado del cliente, pero la verificación del lado del cliente aún es posible. Le mostraré cómo y le proporcionaré una demostración funcional en la parte inferior.


Compruebe que su navegador admita tanto Filey Blob. Todos los principales deberían hacerlo.

if (window.FileReader && window.Blob) {
    // All the File APIs are supported.
} else {
    // File and Blob are not supported
}

Paso 1:

Puedes recuperar la Fileinformación de un <input>elemento como este ( ref ):

<input type="file" id="your-files" multiple>
<script>
var control = document.getElementById("your-files");
control.addEventListener("change", function(event) {
    // When the control has changed, there are new files
    var files = control.files,
    for (var i = 0; i < files.length; i++) {
        console.log("Filename: " + files[i].name);
        console.log("Type: " + files[i].type);
        console.log("Size: " + files[i].size + " bytes");
    }
}, false);
</script>

Aquí hay una versión de arrastrar y soltar de lo anterior ( ref ):

<div id="your-files"></div>
<script>
var target = document.getElementById("your-files");
target.addEventListener("dragover", function(event) {
    event.preventDefault();
}, false);

target.addEventListener("drop", function(event) {
    // Cancel default actions
    event.preventDefault();
    var files = event.dataTransfer.files,
    for (var i = 0; i < files.length; i++) {
        console.log("Filename: " + files[i].name);
        console.log("Type: " + files[i].type);
        console.log("Size: " + files[i].size + " bytes");
    }
}, false);
</script>

Paso 2:

Ahora podemos inspeccionar los archivos y extraer encabezados y tipos MIME.

✘ Método rápido

Puedes preguntarle ingenuamente a Blob el tipo MIME de cualquier archivo que represente usando este patrón:

var blob = files[i]; // See step 1 above
console.log(blob.type);

Para imágenes, los tipos MIME son como los siguientes:

imagen/imagen jpeg
/png
...

Advertencia: el tipo MIME se detecta a partir de la extensión del archivo y puede engañarse o falsificarse. Se puede cambiar el nombre .jpgde a .pngy el tipo MIME se informará como image/png.


✓ Método adecuado de inspección del encabezado

Para obtener el tipo MIME auténtico de un archivo del lado del cliente, podemos ir un paso más allá e inspeccionar los primeros bytes del archivo dado para compararlos con los llamados números mágicos . Tenga en cuenta que no es del todo sencillo porque, por ejemplo, JPEG tiene algunos "números mágicos". Esto se debe a que el formato ha evolucionado desde 1991. Es posible que pueda verificar solo los dos primeros bytes, pero prefiero verificar al menos 4 bytes para reducir los falsos positivos.

Firmas de archivos de ejemplo de JPEG (primeros 4 bytes):

FF D8 FF E0 (SOI + ADD0)
FF D8 FF E1 (SOI + ADD1)
FF D8 FF E2 (SOI + ADD2)

Aquí está el código esencial para recuperar el encabezado del archivo:

var blob = files[i]; // See step 1 above
var fileReader = new FileReader();
fileReader.onloadend = function(e) {
  var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
  var header = "";
  for(var i = 0; i < arr.length; i++) {
     header += arr[i].toString(16);
  }
  console.log(header);

  // Check the file signature against known types

};
fileReader.readAsArrayBuffer(blob);

Luego puede determinar el tipo MIME real de esta manera (más firmas de archivos aquí y aquí ):

switch (header) {
    case "89504e47":
        type = "image/png";
        break;
    case "47494638":
        type = "image/gif";
        break;
    case "ffd8ffe0":
    case "ffd8ffe1":
    case "ffd8ffe2":
    case "ffd8ffe3":
    case "ffd8ffe8":
        type = "image/jpeg";
        break;
    default:
        type = "unknown"; // Or you can use the blob.type as fallback
        break;
}

Acepte o rechace la carga de archivos como desee según los tipos MIME esperados.


Manifestación

Aquí hay una demostración funcional para archivos locales y archivos remotos (tuve que omitir CORS solo para esta demostración). Abra el fragmento, ejecútelo y debería ver tres imágenes remotas de diferentes tipos. En la parte superior puede seleccionar una imagen local o un archivo de datos y se mostrará la firma del archivo y/o el tipo MIME.

Tenga en cuenta que incluso si se cambia el nombre de una imagen, se puede determinar su verdadero tipo MIME. Vea abajo.

Captura de pantalla

Resultado esperado de la demostración


Mostrar fragmento de código

Drakes avatar Apr 16 '2015 11:04 Drakes

Como se indicó en otras respuestas, puede verificar el tipo mime verificando la firma del archivo en los primeros bytes del archivo.

Pero lo que hacen otras respuestas es cargar el archivo completo en la memoria para verificar la firma, lo cual es un gran desperdicio y podría congelar fácilmente su navegador si selecciona un archivo grande por accidente o no.

/**
 * Load the mime type based on the signature of the first bytes of the file
 * @param  {File}   file        A instance of File
 * @param  {Function} callback  Callback with the result
 * @author Victor www.vitim.us
 * @date   2017-03-23
 */
function loadMime(file, callback) {
    
    //List of known mimes
    var mimes = [
        {
            mime: 'image/jpeg',
            pattern: [0xFF, 0xD8, 0xFF],
            mask: [0xFF, 0xFF, 0xFF],
        },
        {
            mime: 'image/png',
            pattern: [0x89, 0x50, 0x4E, 0x47],
            mask: [0xFF, 0xFF, 0xFF, 0xFF],
        }
        // you can expand this list @see https://mimesniff.spec.whatwg.org/#matching-an-image-type-pattern
    ];

    function check(bytes, mime) {
        for (var i = 0, l = mime.mask.length; i < l; ++i) {
            if ((bytes[i] & mime.mask[i]) - mime.pattern[i] !== 0) {
                return false;
            }
        }
        return true;
    }

    var blob = file.slice(0, 4); //read the first 4 bytes of the file

    var reader = new FileReader();
    reader.onloadend = function(e) {
        if (e.target.readyState === FileReader.DONE) {
            var bytes = new Uint8Array(e.target.result);

            for (var i=0, l = mimes.length; i<l; ++i) {
                if (check(bytes, mimes[i])) return callback("Mime: " + mimes[i].mime + " <br> Browser:" + file.type);
            }

            return callback("Mime: unknown <br> Browser:" + file.type);
        }
    };
    reader.readAsArrayBuffer(blob);
}


//when selecting a file on the input
fileInput.onchange = function() {
    loadMime(fileInput.files[0], function(mime) {

        //print the output to the screen
        output.innerHTML = mime;
    });
};
<input type="file" id="fileInput">
<div id="output"></div>
Expandir fragmento

Vitim.us avatar Mar 23 '2017 17:03 Vitim.us