Como verificar o tipo MIME de ficheiros com o javascript antes de enviar?

eu li isto e isto perguntas que parecem sugerir que o tipo MIME do ficheiro poderia ser verificado usando o javascript do lado do cliente. Agora, eu entendo que a validação real ainda tem que ser feita no lado do servidor. Eu quero realizar uma verificação do lado do cliente para evitar desperdício desnecessário de recursos do servidor.

para testar se isto pode ser feito do lado do cliente, alterei a extensão de um ficheiro de teste JPEG para .png e escolhi o ficheiro para envio. Antes de enviar o ficheiro, eu questiono o objecto do ficheiro usando uma consola de javascript:

document.getElementsByTagName('input')[0].files[0];
Isto é o que ganho com o cromado 28.0:

File {webkitRelativePath:"", lastModifiedDate: Tue Oct 16 2012 10:00: 00 GMT+00: 00 (UTC), nome: "test.png", tipo: "Imagem/png", tamanho: 500055…}

mostra o tipo a ser image/png o que parece indicar que a verificação é feita com base na extensão do ficheiro em vez do tipo MIME. Tentei o Firefox 22.0 e dá-me o mesmo resultado. Mas de acordo com the W3C spec, o Sniffing MIME deve ser implementado.

Tenho razão em dizer que não há forma de verificar o tipo MIME com javascript neste momento? Ou está a escapar-me alguma coisa?

Author: Community, 2013-08-18

7 answers

Pode determinar facilmente o tipo MIME de ficheiros com o Javascript's FileReader antes de O enviar para um servidor. Concordo que devemos preferir o lado do servidor a verificar o lado do cliente, mas a verificação do lado do cliente ainda é possível. Vou mostrar-te como e fornecer uma demo no fundo.


Verifique se o seu navegador suporta tanto File como Blob. Todos os grandes deviam.

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

Passo 1:

Você pode recuperar a informação File de um elemento como <input> 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>

Aqui está uma versão drag-and-drop do acima (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>

Passo 2:

Agora podemos inspeccionar os ficheiros e provocar cabeçalhos e tipos mímicos.

✘ método Rápido

Você pode ingenuamente perguntar Blob para o tipo MIME de qualquer arquivo que ele representa usando este padrão:

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

Para as imagens, os tipos mímicos voltam do seguinte modo:

Imagem / jpeg
image / png
...

Caveat: o tipo MIME é detectado a partir da extensão do ficheiro e pode ser enganado ou falsificado. Pode-se mudar o nome de a .jpg para a .png e o tipo MIME será reportado como image/png.


✓ método adequado de inspecção do cabeçalho

Para obter o tipo MIME bonafide de um ficheiro do lado do cliente, podemos ir um passo mais longe e inspeccionar os primeiros bytes do ficheiro indicado para comparar com os chamados números mágicos. Aviso que não é inteiramente simples porque, por exemplo, JPEG tem alguns "números mágicos". Isto porque o formato evoluiu desde 1991. Você pode sair impune verificando apenas os dois primeiros bytes, mas eu prefiro verificar pelo menos 4 bytes para reduzir falsos positivos.

Exemplo de assinaturas de ficheiros JPEG (primeiros 4 bytes):

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

Aqui está o essencial. código para obter o cabeçalho do ficheiro:
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);

Você pode então determinar o tipo MIME real como so (mais assinaturas de arquivos aqui e aqui):

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;
}

Aceitar ou rejeitar os uploads de ficheiros como desejar, com base nos tipos MIME esperados.


Demo

Aqui está uma demonstração de trabalho para arquivos locais e arquivos remotos (eu tive que contornar o CORS só para esta demonstração). Abra o excerto, execute-o e deverá ver três imagens remotas de diferentes tipos figurar. No topo, poderá seleccionar um ficheiro de dados local ou , para que a assinatura do ficheiro e/ou o tipo MIME sejam apresentados.

Repare que mesmo que uma imagem seja renomeada, o seu verdadeiro tipo MIME pode ser determinado. Abaixar.

Imagem

Expected output of demo


// Return the first few bytes of the file as a hex string
function getBLOBFileHeader(url, blob, callback) {
  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);
    }
    callback(url, header);
  };
  fileReader.readAsArrayBuffer(blob);
}

function getRemoteFileHeader(url, callback) {
  var xhr = new XMLHttpRequest();
  // Bypass CORS for this demo - naughty, Drakes
  xhr.open('GET', '//cors-anywhere.herokuapp.com/' + url);
  xhr.responseType = "blob";
  xhr.onload = function() {
    callback(url, xhr.response);
  };
  xhr.onerror = function() {
    alert('A network error occurred!');
  };
  xhr.send();
}

function headerCallback(url, headerString) {
  printHeaderInfo(url, headerString);
}

function remoteCallback(url, blob) {
  printImage(blob);
  getBLOBFileHeader(url, blob, headerCallback);
}

function printImage(blob) {
  // Add this image to the document body for proof of GET success
  var fr = new FileReader();
  fr.onloadend = function() {
    $("hr").after($("<img>").attr("src", fr.result))
      .after($("<div>").text("Blob MIME type: " + blob.type));
  };
  fr.readAsDataURL(blob);
}

// Add more from http://en.wikipedia.org/wiki/List_of_file_signatures
function mimeType(headerString) {
  switch (headerString) {
    case "89504e47":
      type = "image/png";
      break;
    case "47494638":
      type = "image/gif";
      break;
    case "ffd8ffe0":
    case "ffd8ffe1":
    case "ffd8ffe2":
      type = "image/jpeg";
      break;
    default:
      type = "unknown";
      break;
  }
  return type;
}

function printHeaderInfo(url, headerString) {
  $("hr").after($("<div>").text("Real MIME type: " + mimeType(headerString)))
    .after($("<div>").text("File header: 0x" + headerString))
    .after($("<div>").text(url));
}

/* Demo driver code */

var imageURLsArray = ["http://media2.giphy.com/media/8KrhxtEsrdhD2/giphy.gif", "http://upload.wikimedia.org/wikipedia/commons/e/e9/Felis_silvestris_silvestris_small_gradual_decrease_of_quality.png", "http://static.giantbomb.com/uploads/scale_small/0/316/520157-apple_logo_dec07.jpg"];

// Check for FileReader support
if (window.FileReader && window.Blob) {
  // Load all the remote images from the urls array
  for (var i = 0; i < imageURLsArray.length; i++) {
    getRemoteFileHeader(imageURLsArray[i], remoteCallback);
  }

  /* Handle local files */
  $("input").on('change', function(event) {
    var file = event.target.files[0];
    if (file.size >= 2 * 1024 * 1024) {
      alert("File size must be at most 2MB");
      return;
    }
    remoteCallback(escape(file.name), file);
  });

} else {
  // File and Blob are not supported
  $("hr").after( $("<div>").text("It seems your browser doesn't support FileReader") );
} /* Drakes, 2015 */
img {
  max-height: 200px
}
div {
  height: 26px;
  font: Arial;
  font-size: 12pt
}
form {
  height: 40px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<form>
  <input type="file" />
  <div>Choose an image to see its file signature.</div>
</form>
<hr/>
 226
Author: Drakes, 2018-01-30 23:11:51

Tal como indicado nas outras respostas, poderá verificar o tipo mime, verificando a assinatura do ficheiro nos primeiros bytes do ficheiro.

Mas o que outras respostas estão a fazer é carregar o ficheiro inteiro na memória {[[9]} de modo a verificar a Assinatura, o que é muito inútil e poderia facilmente congelar o seu navegador se seleccionar um ficheiro grande por acidente ou não.

/**
 * 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>
 4
Author: Vitim.us, 2018-07-06 16:34:17

Se só quiser verificar se o ficheiro carregado é uma imagem, pode tentar carregá-la para <img> marcar uma verificação para qualquer chamada de erro.

Exemplo:

var input = document.getElementsByTagName('input')[0];
var reader = new FileReader();

reader.onload = function (e) {
    imageExists(e.target.result, function(exists){
        if (exists) {

            // Do something with the image file.. 

        } else {

            // different file format

        }
    });
};

reader.readAsDataURL(input.files[0]);


function imageExists(url, callback) {
    var img = new Image();
    img.onload = function() { callback(true); };
    img.onerror = function() { callback(false); };
    img.src = url;
}
 3
Author: Roberto14, 2016-05-20 08:40:20
Como Drake afirma, isto pode ser feito com o FileReader. No entanto, o que eu apresento aqui é uma versão funcional. Tenha em consideração que o grande problema em fazer isso com JavaScript é reiniciar o arquivo de entrada. Bem, isto restringe-se apenas ao JPG (para outros formatos, terá de alterar o tipo mime e o número mágico ):
<form id="form-id">
  <input type="file" id="input-id" accept="image/jpeg"/>
</form>

<script type="text/javascript">
    $(function(){
        $("#input-id").on('change', function(event) {
            var file = event.target.files[0];
            if(file.size>=2*1024*1024) {
                alert("JPG images of maximum 2MB");
                $("#form-id").get(0).reset(); //the tricky part is to "empty" the input file here I reset the form.
                return;
            }

            if(!file.type.match('image/jp.*')) {
                alert("only JPG images");
                $("#form-id").get(0).reset(); //the tricky part is to "empty" the input file here I reset the form.
                return;
            }

            var fileReader = new FileReader();
            fileReader.onload = function(e) {
                var int32View = new Uint8Array(e.target.result);
                //verify the magic number
                // for JPG is 0xFF 0xD8 0xFF 0xE0 (see https://en.wikipedia.org/wiki/List_of_file_signatures)
                if(int32View.length>4 && int32View[0]==0xFF && int32View[1]==0xD8 && int32View[2]==0xFF && int32View[3]==0xE0) {
                    alert("ok!");
                } else {
                    alert("only valid JPG images");
                    $("#form-id").get(0).reset(); //the tricky part is to "empty" the input file here I reset the form.
                    return;
                }
            };
            fileReader.readAsArrayBuffer(file);
        });
    });
</script>

Tome em consideração que isto foi testado em versões mais recentes do Firefox e do Chrome, e em IExplore 10.

Por lista completa dos tipos mime ver Wikipedia .

Para uma lista completa de números mágicos veja Wikipedia .

 1
Author: lmiguelmh, 2017-06-13 22:56:47
A resposta curta é não.

À medida que se nota que os navegadores derivam type da extensão do ficheiro. Mac preview também parece correr fora da extensão. Estou assumindo que é porque é mais rápido ler o nome do arquivo contido no ponteiro, em vez de olhar para cima e ler o arquivo no disco.

Fiz uma cópia de um jpg renomeado com png.

Fui capaz de obter consistentemente o seguinte de ambas as imagens em chrome (deve funcionar em navegadores modernos).

ÿØÿàJFIFÿþ;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90

Que você podia cortar uma corda.indexOf ('jpeg') verifique o tipo de imagem.

Aqui está um violino para explorar http://jsfiddle.net/bamboo/jkZ2v/1/

A linha ambígua que me esqueci de comentar no exemplo

console.log( /^(.*)$/m.exec(window.atob( image.src.split(',')[1] )) );

  • divide os dados img codificados de base64, deixando na imagem
  • Base64 decodifica a imagem
  • corresponde apenas à primeira linha dos dados da imagem
O código de fiddle usa descodificação base64 que não funciona na IE9, encontrei um bom exemplo com o programa VB que funciona no IE http://blog.nihilogic.dk/2008/08/imageinfo-reading-image-metadata-with.html

O código para carregar a imagem foi tirado de Joel Vardy, que está a fazer alguma tela de imagem fixe a redimensionar o lado do cliente antes de carregar o que pode ser interessante https://joelvardy.com/writing/javascript-image-upload

 0
Author: Lex, 2014-06-26 02:35:35
Aqui está uma extensão da resposta do Roberto14 que faz o seguinte:

ISTO SÓ PERMITIRÁ IMAGENS

Verifica se o FileReader está disponível e volta para a verificação de extensão se não estiver disponível.

Dá um alerta de erro se não for uma imagem

Se é uma imagem carrega uma antevisão

* * você ainda deve fazer a validação do lado do servidor, isto é mais uma conveniência para o usuário final do que qualquer outra coisa. Mas dá jeito!

<form id="myform">
    <input type="file" id="myimage" onchange="readURL(this)" />
    <img id="preview" src="#" alt="Image Preview" />
</form>

<script>
function readURL(input) {
    if (window.FileReader && window.Blob) {
        if (input.files && input.files[0]) {
            var reader = new FileReader();
            reader.onload = function (e) {
                var img = new Image();
                img.onload = function() {
                    var preview = document.getElementById('preview');
                    preview.src = e.target.result;
                    };
                img.onerror = function() { 
                    alert('error');
                    input.value = '';
                    };
                img.src = e.target.result;
                }
            reader.readAsDataURL(input.files[0]);
            }
        }
    else {
        var ext = input.value.split('.');
        ext = ext[ext.length-1].toLowerCase();      
        var arrayExtensions = ['jpg' , 'jpeg', 'png', 'bmp', 'gif'];
        if (arrayExtensions.lastIndexOf(ext) == -1) {
            alert('error');
            input.value = '';
            }
        else {
            var preview = document.getElementById('preview');
            preview.setAttribute('alt', 'Browser does not support preview.');
            }
        }
    }
</script>
 0
Author: pathfinder, 2016-09-18 16:48:42

Isto é o que tens de fazer

var fileVariable =document.getElementsById('fileId').files[0];

Se quiser verificar os tipos de ficheiros de imagens, então

if(fileVariable.type.match('image.*'))
{
 alert('its an image');
}
 0
Author: Kailas, 2017-03-14 07:15:43