Jak sprawdzić typ MIME pliku za pomocą javascript przed przesłaniem?

Przeczytałem to i to pytania, które wydają się sugerować, że typ MIME pliku może być sprawdzany przy użyciu javascript po stronie klienta. Rozumiem, że prawdziwa Walidacja nadal musi być wykonana po stronie serwera. Chcę przeprowadzić kontrolę po stronie klienta, aby uniknąć niepotrzebnego marnotrawstwa zasobów serwera.

Aby sprawdzić, czy można to zrobić po stronie klienta, zmieniłem rozszerzenie pliku testowego {[1] } na .png i wybrałem plik do przesłania. Przed wysłaniem plik, odpytywam obiekt file za pomocą konsoli javascript:

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

Oto co dostaję na Chrome 28.0:

File {webkitRelativePath:"", lastModifiedDate: WTO Paź 16 2012 10: 00: 00 GMT + 0000 (UTC), nazwa: "test.png", type: "image / png", size: 500055…}

Pokazuje typ image/png, co zdaje się wskazywać, że sprawdzanie odbywa się na podstawie rozszerzenia pliku zamiast typu MIME. Próbowałem Firefox 22.0 i daje mi ten sam wynik. Ale według W3C spec, należy zaimplementować Sniffing MIME.

Czy mam rację mówiąc, że w tej chwili nie ma możliwości sprawdzenia typu MIME za pomocą javascript? Czy coś przeoczyłem?

Author: Community, 2013-08-18

7 answers

Możesz łatwo określić typ MIME pliku za pomocą JavaScript FileReader przed przesłaniem go na serwer. Zgadzam się, że powinniśmy preferować sprawdzanie po stronie serwera niż po stronie klienta, ale sprawdzanie po stronie klienta jest nadal możliwe. Pokażę Ci, jak i przedstawię demo na dole.


Sprawdź, czy twoja przeglądarka obsługuje zarówno File, jak i Blob. Wszystkie główne powinny.

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

Krok 1:

Możesz pobrać File informacje z <input> elementu takiego jak ten (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>

Oto wersja przeciągnij i upuść powyższej (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>

Krok 2:

Możemy teraz sprawdzić pliki i dokuczać nagłówkom i typom MIME.

✘ szybka metoda

Możesz naiwnie zapytaćBlob o typ MIME dowolnego pliku, który reprezentuje, używając tego wzorca:

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

Dla obrazów, typy MIME powracają następująco:

Image / jpeg
image / png
...

Zastrzeżenie: typ MIME jest wykrywany z rozszerzenia pliku i można go oszukać lub sfałszować. Można zmienić nazwę .jpg na .png, A Typ MIME będzie zgłaszany jako image/png.


✓ właściwa metoda kontroli nagłówka

Aby uzyskać Typ bonafide MIME pliku po stronie klienta, możemy pójść o krok dalej i sprawdzić kilka pierwszych bajtów danego pliku w celu porównania z tak zwanymi liczbami magicznymi. Ostrzegam to nie jest do końca proste, ponieważ na przykład JPEG ma kilka "magicznych liczb". To dlatego, że format ewoluował od 1991 roku. Możesz ujść na sucho sprawdzając tylko dwa pierwsze bajty, ale wolę sprawdzać co najmniej 4 bajty, aby zmniejszyć fałszywe alarmy.

Przykładowe sygnatury pliku JPEG (pierwsze 4 bajty):

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

Oto najważniejsze kod do pobrania nagłówka pliku:

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

Możesz następnie określić prawdziwy typ MIME w ten sposób (więcej podpisów plików tutaj I tutaj):

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

Akceptuj lub odrzucaj przesyłane pliki zgodnie z oczekiwanymi typami MIME.


Demo

Oto działające demo dla plików lokalnych i plików zdalnych (musiałem ominąć CORS tylko dla tego demo). Otwórz fragment, uruchom go, a zobaczysz trzy zdalne obrazy różnych typów wyświetlane. Na górze możesz wybrać plik danych lokalnego obrazu lub , a wyświetlona zostanie sygnatura pliku i / lub typ MIME.

Zauważ, że nawet jeśli obraz zostanie przemianowany, można określić jego prawdziwy typ MIME. Patrz poniżej.

Zrzut ekranu

Oczekiwany wynik 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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-30 23:11:51

Jak podano w innych odpowiedziach, możesz sprawdzić typ mime, sprawdzając sygnaturę pliku w pierwszych bajtach pliku.

Ale inne odpowiedzi toŁadowanie całego pliku do pamięci w celu sprawdzenia podpisu, co jest bardzo marnotrawne i może łatwo zamrozić przeglądarkę, jeśli przypadkowo wybierzesz duży plik.

/**
 * 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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-06 16:34:17

Jeśli chcesz tylko sprawdzić, czy przesłany plik jest obrazem, możesz po prostu spróbować załadować go do <img> Oznacz sprawdzenie, czy nie występują błędy wywołania zwrotnego.

Przykład:

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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-20 08:40:20

Jak twierdzi Drake, można to zrobić za pomocą FileReader. To, co tutaj prezentuję, jest jednak wersją funkcjonalną. Weź pod uwagę, że dużym problemem z robieniem tego z JavaScript jest zresetowanie pliku wejściowego. Cóż, to ogranicza się tylko do JPG (dla innych formatów będziesz musiał zmienić typ MIME i magiczną liczbę ):

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

Weź pod uwagę, że zostało to przetestowane na najnowszych wersjach Firefoksa i Chrome oraz na IExplore 10.

Na pełna lista typów mime znajduje się w Wikipedii .

Pełna lista liczb magicznych znajduje się w Wikipedii .

 1
Author: lmiguelmh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-13 22:56:47

Krótka odpowiedź brzmi nie.

Jak zauważysz przeglądarki czerpią type z rozszerzenia pliku. Mac preview wydaje się również uruchamiać rozszerzenie. Zakładam, że jego szybsze odczytywanie nazwy pliku zawartej w wskaźniku, niż szukanie i odczytywanie pliku na dysku.

Zrobiłem kopię jpg zmienioną na png.

Udało mi się konsekwentnie pobrać następujące obrazy z obu obrazów w chrome (powinno działać w nowoczesnych przeglądarkach).

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

Które ty może wyrwać strunę.indexOf ('jpeg') sprawdza typ obrazu.

Oto skrzypce do zbadania http://jsfiddle.net/bamboo/jkZ2v/1/

Dwuznaczny wiersz zapomniałem skomentować w przykładzie

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

  • dzieli zakodowane dane img base64, pozostawiając na obrazku
  • Base64 dekoduje obraz
  • dopasowuje tylko pierwszą linię danych obrazu

Kod fiddle używa dekodera base64, który nie działa w IE9, znalazłem ładny przykład użycia skryptu VB działającego w IE http://blog.nihilogic.dk/2008/08/imageinfo-reading-image-metadata-with.html

Kod do załadowania obrazu został zaczerpnięty od Joela Vardy ' ego, który robi fajne płótno, zmieniając rozmiar po stronie klienta przed przesłaniem, co może być interesujące https://joelvardy.com/writing/javascript-image-upload

 0
Author: Lex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-26 02:35:35

Oto rozszerzenie odpowiedzi Roberto14, które wykonuje następujące czynności:

TO POZWOLI TYLKO NA OBRAZY

Sprawdza, czy FileReader jest dostępny i wraca do sprawdzania rozszerzenia, czy nie jest dostępny.

Wyświetla komunikat o błędzie, jeśli nie obraz

Jeśli jest to obrazek ładuje podgląd

* * nadal powinieneś wykonywać walidację po stronie serwera, jest to wygoda dla użytkownika końcowego niż cokolwiek innego. Ale to jest przydatne!

<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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-18 16:48:42

This is what you have to do

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

Jeśli chcesz sprawdzić typy plików obrazów to

if(fileVariable.type.match('image.*'))
{
 alert('its an image');
}
 0
Author: Kailas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-14 07:15:43