Wie überprüfe ich den MIME-Typ der Datei vor dem Hochladen mit Javascript?

176

Ich habe diese und diese Fragen , die , dass die Datei MIME - Typ vorzuschlagen scheint Javascript überprüft werden könnte unter Verwendung auf Client - Seite. Jetzt verstehe ich, dass die eigentliche Validierung noch auf der Serverseite durchgeführt werden muss. Ich möchte eine clientseitige Überprüfung durchführen, um unnötige Verschwendung von Serverressourcen zu vermeiden.

Um zu testen, ob dies auf der Clientseite möglich ist, habe ich die Erweiterung einer JPEGTestdatei in geändert .pngund die Datei zum Hochladen ausgewählt. Vor dem Senden der Datei frage ich das Dateiobjekt mit einer Javascript-Konsole ab:

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

Folgendes bekomme ich auf Chrome 28.0:

Datei {webkitRelativePath: "", lastModifiedDate: Di 16.10.2012 10:00:00 GMT + 0000 (UTC), Name: "test.png", Typ: "image / png", Größe: 500055…}

Es wird der Typ image/pngangezeigt, der darauf hinweist, dass die Überprüfung basierend auf der Dateierweiterung anstelle des MIME-Typs erfolgt. Ich habe Firefox 22.0 ausprobiert und es gibt mir das gleiche Ergebnis. Gemäß der W3C-Spezifikation sollte MIME Sniffing implementiert werden.

Kann ich zu Recht sagen, dass es derzeit keine Möglichkeit gibt, den MIME-Typ mit Javascript zu überprüfen? Oder fehlt mir etwas?

Fragenüberlauf
quelle
5
I want to perform a client side checking to avoid unnecessary wastage of server resource.Ich verstehe nicht, warum Sie sagen, dass die Validierung auf der Serverseite durchgeführt werden muss, aber dann sagen Sie, dass Sie die Serverressourcen reduzieren möchten. Goldene Regel: Vertraue niemals Benutzereingaben . Was bringt es, den MIME-Typ auf der Clientseite zu überprüfen, wenn Sie dies dann nur auf der Serverseite tun? Sicherlich ist das eine "unnötige Verschwendung von Kundenressourcen "?
Ian Clark
7
Es ist eine gute Idee, den Benutzern auf Client-Seite eine bessere Überprüfung / Rückmeldung des Dateityps zu bieten. Wie Sie bereits festgestellt haben, verlassen sich Browser bei der Ermittlung des Werts der typeEigenschaft für FileObjekte einfach auf die Dateierweiterungen . Der Webkit-Quellcode zum Beispiel enthüllt diese Wahrheit. Es ist möglich, Dateien clientseitig genau zu identifizieren, indem unter anderem in den Dateien nach "magischen Bytes" gesucht wird. Ich arbeite derzeit an einer MIT-Bibliothek (in der wenigen Freizeit, die ich habe), die genau das tun wird. Wenn Sie an meinen Fortschritten interessiert sind, besuchen Sie github.com/rnicholus/determinater .
Ray Nicholus
32
@IanClark, der Punkt ist, dass wenn die Datei von einem ungültigen Typ ist, ich sie auf der Clientseite ablehnen kann, anstatt die Upload-Bandbreite nur zu verschwenden, um sie auf der Serverseite abzulehnen.
Frage Überlauf
@ RayNicholus, cooler Typ! Werde es durchsehen, wenn ich Zeit habe. Danke :)
Frage Überlauf
Sind Sie sicher, dass Ihre Testdatei noch den Mimetyp hat image/jpeg, und Sie haben dies nicht durch Ändern der Erweiterung geändert?
Bergi

Antworten:

341

Sie können den MIME-Dateityp einfach mit JavaScript ermitteln, FileReaderbevor Sie ihn auf einen Server hochladen. Ich bin damit einverstanden, dass wir die serverseitige Überprüfung der clientseitigen Überprüfung vorziehen sollten, aber die clientseitige Überprüfung ist weiterhin möglich. Ich werde Ihnen zeigen, wie und unten eine funktionierende Demo bereitstellen.


Überprüfen Sie, ob Ihr Browser sowohl Fileals auch unterstützt Blob. Alle wichtigen sollten.

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

Schritt 1:

Sie können die FileInformationen von einem <input>Element wie diesem abrufen ( 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>

Hier ist eine Drag-and-Drop-Version des oben genannten ( 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>

Schritt 2:

Wir können jetzt die Dateien überprüfen und Header und MIME-Typen herausziehen.

✘ Schnelle Methode

Sie können Blob naiv nach dem MIME-Typ der Datei fragen, die er darstellt, indem Sie dieses Muster verwenden:

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

Bei Bildern werden MIME-Typen wie folgt zurückgegeben:

image / jpeg
image / png
...

Vorsichtsmaßnahme: Der MIME-Typ wird anhand der Dateierweiterung erkannt und kann getäuscht oder gefälscht werden. Man kann a .jpgin a umbenennen .pngund der MIME-Typ wird als gemeldet image/png.


✓ Richtige Methode zur Überprüfung des Headers

Um den bonafide MIME-Typ einer clientseitigen Datei zu erhalten, können wir einen Schritt weiter gehen und die ersten Bytes der angegebenen Datei untersuchen, um sie mit sogenannten magischen Zahlen zu vergleichen . Seien Sie gewarnt, dass es nicht ganz einfach ist, zum Beispiel JPEG einige "magische Zahlen" enthält. Dies liegt daran, dass sich das Format seit 1991 weiterentwickelt hat. Möglicherweise müssen Sie nur die ersten beiden Bytes überprüfen, aber ich bevorzuge es, mindestens 4 Bytes zu überprüfen, um Fehlalarme zu reduzieren.

Beispiel für Dateisignaturen von JPEG (erste 4 Bytes):

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

Hier ist der wesentliche Code zum Abrufen des Dateikopfs:

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

Sie können dann den tatsächlichen MIME-Typ wie folgt bestimmen (weitere Dateisignaturen hier und hier ):

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

Akzeptieren oder lehnen Sie das Hochladen von Dateien nach Ihren Wünschen ab, basierend auf den erwarteten MIME-Typen.


Demo

Hier ist eine funktionierende Demo für lokale Dateien und Remote-Dateien (ich musste CORS nur für diese Demo umgehen). Öffnen Sie das Snippet, führen Sie es aus, und es sollten drei Remote-Images verschiedener Typen angezeigt werden. Oben können Sie ein lokales Bild oder eine lokale Datendatei auswählen. Die Dateisignatur und / oder der MIME-Typ werden angezeigt.

Beachten Sie, dass selbst wenn ein Bild umbenannt wird, sein wahrer MIME-Typ bestimmt werden kann. Siehe unten.

Bildschirmfoto

Erwartete Ausgabe der Demo


Drakes
quelle
8
2 kleinere Kommentare. (1) Wäre es nicht besser, die Datei vor dem Lesen auf die ersten 4 Bytes aufzuteilen? fileReader.readAsArrayBuffer(blob.slice(0,4))? (2) Sollte der Header zum Kopieren / Einfügen von Dateisignaturen nicht mit führenden Nullen erstellt werden for(var i = 0; i < bytes.length; i++) { var byte = bytes[i]; fileSignature += (byte < 10 ? "0" : "") + byte.toString(16); }?
Matthew Madson
1
@Deadpool Siehe hier . Es gibt mehr, weniger gebräuchliche JPEG-Formate von verschiedenen Herstellern. Beispiel: FF D8 FF E2= CANNON EOS JPEG-DATEI, FF D8 FF E3= SAMSUNG D500 JPEG-DATEI. Der Hauptteil der JPEG-Signatur besteht nur aus 2 Bytes. Um jedoch Fehlalarme zu reduzieren, habe ich die häufigsten 4-Byte-Signaturen hinzugefügt. Ich hoffe das hilft.
Drakes
22
Die Qualität dieser Antwort ist einfach unglaublich.
Luca
2
Sie müssen keinen vollständigen Blob als ArrayBuffer laden, um den mimeType zu bestimmen. Sie können einfach die ersten 4 Bytes des Blobs wie fileReader.readAsArrayBuffer(blob.slice(0, 4))
folgt in
2
Was sollte die Prüfung sein, um nur Klartext zuzulassen? Die ersten 4 Bytes für Textdateien scheinen die ersten 4 Zeichen in der Textdatei zu sein.
MP Droid
19

Wie in anderen Antworten angegeben, können Sie den MIME-Typ überprüfen, indem Sie die Signatur der Datei in den ersten Bytes der Datei überprüfen .

Andere Antworten laden jedoch die gesamte Datei in den Speicher , um die Signatur zu überprüfen. Dies ist sehr verschwenderisch und kann Ihren Browser leicht einfrieren, wenn Sie versehentlich eine große Datei auswählen oder nicht.

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

Vitim.us
quelle
Ich denke, es readyStatewird immer FileReader.DONEim Event-Handler ( W3C-Spezifikation ) sein, auch wenn es einen Fehler gab - sollte die Prüfung nicht (!e.target.error)stattdessen erfolgen?
Boycy
5

Für alle, die dies nicht selbst implementieren möchten, hat Sindresorhus ein Dienstprogramm erstellt, das im Browser funktioniert und die Header-to-Mime-Zuordnungen für die meisten gewünschten Dokumente enthält.

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

Sie können den Vorschlag von Vitim.us kombinieren, nur die ersten X Bytes zu lesen, um zu vermeiden, dass mit diesem Dienstprogramm alles in den Speicher geladen wird (Beispiel in 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.fromBuffer(bytes);

  // ext is the desired extension and mime is the mimetype
};
reader.readAsArrayBuffer(blob);
Vinay
quelle
Für mich funktionierte die neueste Version der Bibliothek nicht, aber die "file-type": "12.4.0"funktionierte und ich mussteimport * as fileType from "file-type";
ssz
4

Wenn Sie nur überprüfen möchten, ob es sich bei der hochgeladenen Datei um ein Bild handelt, können Sie einfach versuchen, es in das <img>Tag zu laden und nach Fehlern zu suchen.

Beispiel:

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;
}
Roberto14
quelle
1
Funktioniert super, ich habe einen GIF-Datei-Uploader-Hack ausprobiert und es wurde ein Fehler ausgegeben :)
Pathfinder
4

Das müssen Sie tun

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

Wenn Sie nach Bilddateitypen suchen möchten, dann

if(fileVariable.type.match('image.*'))
{
 alert('its an image');
}
Kailas
quelle
Derzeit funktioniert nicht für: Firefox für Android, Opera für Android und Safari für iOS. developer.mozilla.org/en-US/docs/Web/API/File/type
Reid
3

Hier ist eine Typescript-Implementierung, die webp unterstützt. Dies basiert auf der JavaScript-Antwort von Vitim.us.

interface Mime {
  mime: string;
  pattern: (number | undefined)[];
}

// tslint:disable number-literal-format
// tslint:disable no-magic-numbers
const imageMimes: Mime[] = [
  {
    mime: 'image/png',
    pattern: [0x89, 0x50, 0x4e, 0x47]
  },
  {
    mime: 'image/jpeg',
    pattern: [0xff, 0xd8, 0xff]
  },
  {
    mime: 'image/gif',
    pattern: [0x47, 0x49, 0x46, 0x38]
  },
  {
    mime: 'image/webp',
    pattern: [0x52, 0x49, 0x46, 0x46, undefined, undefined, undefined, undefined, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50],
  }
  // You can expand this list @see https://mimesniff.spec.whatwg.org/#matching-an-image-type-pattern
];
// tslint:enable no-magic-numbers
// tslint:enable number-literal-format

function isMime(bytes: Uint8Array, mime: Mime): boolean {
  return mime.pattern.every((p, i) => !p || bytes[i] === p);
}

function validateImageMimeType(file: File, callback: (b: boolean) => void) {
  const numBytesNeeded = Math.max(...imageMimes.map(m => m.pattern.length));
  const blob = file.slice(0, numBytesNeeded); // Read the needed bytes of the file

  const fileReader = new FileReader();

  fileReader.onloadend = e => {
    if (!e || !fileReader.result) return;

    const bytes = new Uint8Array(fileReader.result as ArrayBuffer);

    const valid = imageMimes.some(mime => isMime(bytes, mime));

    callback(valid);
  };

  fileReader.readAsArrayBuffer(blob);
}

// When selecting a file on the input
fileInput.onchange = () => {
  const file = fileInput.files && fileInput.files[0];
  if (!file) return;

  validateImageMimeType(file, valid => {
    if (!valid) {
      alert('Not a valid image file.');
    }
  });
};

<input type="file" id="fileInput">

Eric Coulthard
quelle
1

Wie Drake feststellt, könnte dies mit FileReader geschehen. Was ich hier jedoch präsentiere, ist eine funktionale Version. Beachten Sie, dass das große Problem bei der Verwendung von JavaScript darin besteht, die Eingabedatei zurückzusetzen. Nun, dies beschränkt sich nur auf JPG (für andere Formate müssen Sie den MIME-Typ und die magische Zahl ändern ):

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

Beachten Sie, dass dies auf den neuesten Versionen von Firefox und Chrome sowie auf IExplore 10 getestet wurde.

Eine vollständige Liste der MIME-Typen finden Sie in Wikipedia .

Eine vollständige Liste der magischen Zahlen finden Sie in Wikipedia .

lmiguelmh
quelle
Die obigen Wikipedia-Links sind nicht mehr gültig.
Bob Quinn
@ BobQuinn behoben, thansk
lmiguelmh
0

Hier ist eine Erweiterung der Antwort von Roberto14, die Folgendes bewirkt:

Dies erlaubt nur Bilder

Überprüft, ob FileReader verfügbar ist, und greift auf die Erweiterungsprüfung zurück, wenn diese nicht verfügbar ist.

Gibt eine Fehlermeldung aus, wenn kein Bild vorhanden ist

Wenn es sich um ein Bild handelt, wird eine Vorschau geladen

** Sie sollten weiterhin eine serverseitige Validierung durchführen. Dies ist für den Endbenutzer vor allem eine Annehmlichkeit. Aber es ist praktisch!

<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>
Pfadfinder
quelle
-1

Kurze Antwort ist nein.

Wie Sie bemerken, leiten sich die Browser typevon der Dateierweiterung ab. Die Mac-Vorschau scheint auch über die Erweiterung zu laufen. Ich gehe davon aus, dass es schneller ist, den im Zeiger enthaltenen Dateinamen zu lesen, als die Datei auf der Festplatte nachzuschlagen und zu lesen.

Ich habe eine Kopie eines mit png umbenannten JPG erstellt.

Ich konnte aus beiden Bildern in Chrom konsistent Folgendes erhalten (sollte in modernen Browsern funktionieren).

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

Womit Sie eine String.indexOf ('jpeg') - Prüfung auf Bildtyp durchführen könnten.

Hier ist eine Geige zum Erkunden von http://jsfiddle.net/bamboo/jkZ2v/1/

Die mehrdeutige Zeile habe ich im Beispiel vergessen zu kommentieren

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

  • Teilt die base64-codierten IMG-Daten und belässt das Bild
  • Base64 dekodiert das Bild
  • Stimmt nur mit der ersten Zeile der Bilddaten überein

Der Geigencode verwendet die Base64-Dekodierung, die in IE9 nicht funktioniert. Ich habe ein schönes Beispiel mit einem VB-Skript gefunden, das in IE funktioniert Http://blog.nihilogic.dk/2008/08/imageinfo-reading-image-metadata-with.html

Der Code zum Laden des Bildes stammt von Joel Vardy, der vor dem Hochladen eine coole Seite für die Größenänderung von Bildern erstellt, die von Interesse sein kann. Https://joelvardy.com/writing/javascript-image-upload

Lex
quelle
1
Bitte durchsuchen Sie JPEGs nicht nach dem Teilstring "jpeg", das ist nur ein Zufall, dass Sie ihn in einem Kommentar gefunden haben. JPEG-Dateien müssen es nicht enthalten (und wenn Sie JFIFstattdessen nach einer Suche suchen , müssen Sie auch APP0kein JFIF in EXIF-JPEGs enthalten , damit es auch nicht funktioniert).
Kornel
Siehe oben "Kurze Antwort ist nein".
Lex