web-dev-qa-db-fra.com

Comment vérifier le type de fichier MIME avec javascript avant le téléchargement?

J'ai lu this et this des questions qui semblent suggérer que le type de fichier MIME pourrait être vérifié à l'aide de javascript du côté client. Maintenant, je comprends que la vraie validation doit encore être faite côté serveur. Je souhaite effectuer une vérification côté client pour éviter un gaspillage inutile des ressources du serveur.

Pour vérifier si cela peut être fait côté client, j'ai changé l'extension d'un fichier de test JPEG en .png et j'ai choisi le fichier à télécharger. Avant d'envoyer le fichier, j'interroge l'objet fichier à l'aide d'une console javascript:

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

Voici ce que je lis sur Chrome 28.0:

Fichier {webkitRelativePath: "", lastModifiedDate: mardi 16 oct. 2012 10:00:00 GMT + 0000 (UTC), nom: "test.png", type: "image/png", taille: 500055…}

Il indique que le type est image/png, ce qui semble indiquer que la vérification est effectuée en fonction de l'extension de fichier au lieu du type MIME. J'ai essayé Firefox 22.0 et cela me donne le même résultat. Mais selon les spécifications du W3C , MIME Sniffing devrait être implémenté.

Ai-je raison de dire qu'il n'y a aucun moyen de vérifier le type MIME avec javascript pour le moment? Ou est-ce que je manque quelque chose?

141
Question Overflow

Vous pouvez facilement déterminer le type de fichier MIME avec le code FileReader de JavaScript avant de le télécharger sur un serveur. Je conviens que nous devrions préférer la vérification côté serveur plutôt que côté client, mais la vérification côté client est toujours possible. Je vais vous montrer comment et fournir une démo de travail au bas.


Vérifiez que votre navigateur prend en charge File et Blob. Tous les principaux devraient.

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

Étape 1:

Vous pouvez récupérer les informations File d'un élément <input> comme ceci ( 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>

Voici une version glisser-déposer de ce qui précède ( 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>

Étape 2:

Nous pouvons maintenant inspecter les fichiers et extraire les en-têtes et les types MIME.

method Méthode rapide

Vous pouvez naïvement demander Blob pour le type MIME du fichier qu’il représente en utilisant ce modèle:

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

Pour les images, les types MIME reviennent comme suit:

image/jpeg
image/png
...

Avertissement: Le type MIME est détecté à partir de l'extension de fichier et peut être dupé ou falsifié. On peut renommer un .jpg en un .png et le type MIME sera signalé par image/png.


✓ Méthode d’inspection d’en-tête appropriée

Pour obtenir le type MIME de bonne foi d'un fichier côté client, nous pouvons aller plus loin et inspecter les premiers octets du fichier donné pour les comparer à ce qu'on appelle nombres magiques . Soyez averti que ce n'est pas tout à fait simple car, par exemple, JPEG a quelques "nombres magiques". En effet, le format a évolué depuis 1991. Vous pouvez vous contenter de vérifier uniquement les deux premiers octets, mais je préfère vérifier au moins 4 octets pour réduire les faux positifs.

Exemple de signatures de fichiers JPEG (4 premiers octets):

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

Voici le code essentiel pour récupérer l'en-tête du fichier:

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

Vous pouvez alors déterminer le type réel de MIME comme suit (plus de signatures de fichiers ici et ici ):

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

Acceptez ou refusez les téléchargements de fichiers à votre guise en fonction des types MIME attendus.


Démo

Voici une démo de travail pour les fichiers locaux et les fichiers distants (j'ai dû contourner CORS uniquement pour cette démo). Ouvrez l'extrait de code, exécutez-le et vous devriez voir trois images distantes de types différents affichées. En haut, vous pouvez sélectionner une image locale ou fichier de données. La signature du fichier et/ou le type MIME seront affichés.

Notez que même si une image est renommée, son vrai type MIME peut être déterminé. Voir ci-dessous.

Capture d'écran

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/>
270
Drakes

Comme indiqué dans d'autres réponses, vous pouvez vérifier le type MIME en vérifiant le signature du fichier dans les premiers octets du fichier.

Mais ce que les autres réponses sont en train de faire, c’est de charger le fichier entier en mémoire afin de vérifier la signature, ce qui est très inutile et pourrait facilement geler votre navigateur. sélectionnez un gros fichier par accident ou non.

/**
 * 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>
8
Vitim.us

Si vous voulez juste vérifier si le fichier téléchargé est une image, vous pouvez simplement essayer de le charger dans la balise <img> et vérifier si un rappel d'erreur se produit.

Exemple:

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
Roberto14

Sindresorhus a créé un utilitaire qui fonctionne dans le navigateur et propose des mappages d’en-tête à mime pour la plupart des documents souhaités.

https://github.com/sindresorhus/file-type

Vous pouvez combiner la suggestion de Vitim.us de ne lire que les X premiers octets pour éviter de tout charger en mémoire avec l'utilisation de cet utilitaire (exemple dans es6):

import fileType from 'file-type'; // or wherever you load the dependency

const blob = file.slice(0, fileType.minimumBytes);

const reader = new FileReader();
reader.onloadend = function(e) {
  if (e.target.readyState !== FileReader.DONE) {
    return;
  }

  const bytes = new Uint8Array(e.target.result);
  const { ext, mime } = fileType(bytes);

  // ext is the desired extension and mime is the mimetype
};
reader.readAsArrayBuffer(blob);
3
Vinay

C'est ce que tu dois faire

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

Si vous voulez vérifier les types de fichiers image, puis

if(fileVariable.type.match('image.*'))
{
 alert('its an image');
}
3
Kailas

Comme Drake le dit, cela pourrait être fait avec FileReader. Cependant, ce que je présente ici est une version fonctionnelle. Notez que le gros problème avec JavaScript est de réinitialiser le fichier d’entrée. Eh bien, cela se limite à JPG (pour les autres formats, vous devrez changer le type mime et le nombre magique ):

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

Notez que cela a été testé sur les dernières versions de Firefox et Chrome, et sur IExplore 10.

Pour une liste complète des types de mime, voir Wikipedia .

Pour une liste complète des nombres magiques, voir Wikipedia .

1
lmiguelmh

La réponse courte est non.

Comme vous le constatez, les navigateurs dérivent type de l’extension de fichier. L'aperçu Mac semble également fonctionner à partir de l'extension. Je suppose que c'est parce que c'est plus rapide de lire le nom de fichier contenu dans le pointeur, plutôt que de chercher et de lire le fichier sur le disque.

J'ai fait une copie d'un jpg renommé avec png.

J'ai pu obtenir de manière constante les éléments suivants des deux images dans chrome (devrait fonctionner dans les navigateurs modernes).

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

Ce que vous pourriez pirater une vérification String.indexOf ('jpeg') pour le type d'image.

Voici un violon à explorer http://jsfiddle.net/bamboo/jkZ2v/1/

La ligne ambiguë que j'ai oublié de commenter dans l'exemple

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

  • Divise les données img encodées en base64, laissant sur l'image
  • Base64 décode l'image
  • Correspond seulement à la première ligne des données d'image

Le code de violon utilise le décodage en base64, ce qui ne fonctionnera pas dans IE9. J'ai trouvé un exemple intéressant utilisant un script VB fonctionnant dans IE http://blog.nihilogic.dk /2008/08/imageinfo-reading-image-metadata-with.html

Le code pour charger l’image a été emprunté à Joel Vardy, qui est en train de redimensionner le canevas de l’image avant le téléchargement, ce qui peut présenter un intérêt https://joelvardy.com/writing/javascript-image-upload =

0
Lex

Voici une extension de la réponse de Roberto14 qui fait ce qui suit:

CECI NE PERMET QUE DES IMAGES

Vérifie si FileReader est disponible et revient à la vérification d'extension si elle n'est pas disponible.

Donne une alerte d'erreur s'il ne s'agit pas d'une image

Si c'est une image, il charge un aperçu

** Vous devez toujours effectuer la validation côté serveur, ceci est plus pratique pour l'utilisateur final qu'autre chose. Mais c'est pratique!

<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
pathfinder