Yüklemeden önce MIME türünü javascript ile nasıl kontrol edebilirim?


177

Ben bu ve MIME türü dosya istemci tarafında javascript kullanılarak kontrol edilebilir önermek gibi görünüyor bu soruları okudum . Şimdi, gerçek doğrulama işleminin hala sunucu tarafında yapılması gerektiğini anlıyorum. Sunucu kaynağının gereksiz israfını önlemek için istemci tarafı denetimi yapmak istiyorum.

Bunun istemci tarafında yapılıp yapılamayacağını test etmek için, bir JPEGtest dosyasının uzantısını olarak değiştirdim ve .pngyüklenecek dosyayı seçtim . Dosyayı göndermeden önce, bir javascript konsolu kullanarak dosya nesnesini sorgularım:

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

Chrome 28.0'da aldığım şey bu:

{WebkitRelativePath: "" dosyası, lastModifiedDate: Sal 16 Ekim 2012 10:00:00 GMT + 0000 (UTC), adı: "test.png", şunu yazın: "image / png", boyut: 500055…}

Öyle olmak tip gösterir image/pngdenetimi yerine MIME türü dosya uzantısına göre yapılır belirtmek için görünüyor. Firefox 22.0'ı denedim ve bana aynı sonucu veriyor. Ancak W3C spesifikasyonuna göre , MIME Kokusu uygulanmalıdır.

Şu anda javascript ile MIME türünü kontrol etmenin bir yolu olmadığını söyleyebilir miyim? Yoksa bir şey mi kaçırıyorum?


5
I want to perform a client side checking to avoid unnecessary wastage of server resource.Doğrulamanın neden sunucu tarafında yapılması gerektiğini söylediğinizi anlamıyorum, ancak sunucu kaynaklarını azaltmak istediğinizi söylüyorum. Altın kural: Kullanıcı girişine asla güvenme . O zaman sadece sunucu tarafında yapıyorsanız, istemci tarafında MIME türünü kontrol etmenin anlamı nedir. Şüphesiz bu, " istemci kaynağının gereksiz bir israfı " mıydı?
Ian Clark

7
Kullanıcılara daha iyi dosya türü denetimi / geri bildirimi sağlamak iyi bir fikirdir. Ancak, belirttiğiniz gibi, tarayıcılar type, Filenesnenin özelliğinin değerini belirlerken dosya uzantılarına güvenir . Örneğin, webkit kaynak kodu bu gerçeği ortaya koymaktadır. Diğer şeylerin yanı sıra, dosyalarda "sihirli baytlar" arayarak dosyaları istemci tarafında doğru bir şekilde tanımlamak mümkündür. Şu anda sadece bunu yapacak bir MIT kütüphanesi (ne kadar az boş zaman var) üzerinde çalışıyorum. İlerlememle ilgileniyorsanız, github.com/rnicholus/determinater adresine bir göz atın .
Ray Nicholus

32
@IanClark, mesele, dosya geçersiz bir türdeyse, sunucu tarafında reddetmek için yükleme bant genişliğini boşa harcamak yerine istemci tarafında reddedebilirim.
Question Overflow

@ RayNicholus, harika ahbap! Zamanım olduğunda ona bakacağım. Teşekkürler :)
Question Overflow

Test dosyanızın hala mimetype'a sahip olduğundan emin misiniz image/jpegve uzantıyı değiştirerek bunu gerçekten değiştirmediniz mi?
Bergi

Yanıtlar:


343

FileReaderBir sunucuya yüklemeden önce MIME türünü JavaScript'lerle kolayca belirleyebilirsiniz . İstemci tarafı üzerinden sunucu tarafı kontrolünü tercih etmemiz gerektiğini kabul ediyorum, ancak istemci tarafı kontrolü hala mümkün. Size nasıl olduğunu göstereceğim ve en altta çalışan bir demo sunacağım.


Tarayıcınızın hem Fileve hem de desteklediğinden emin olun Blob. Tüm büyük olanlar gerekir.

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

Aşama 1:

FileBilgileri <input>böyle bir elemandan alabilirsiniz ( 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>

Yukarıdakilerin sürükle ve bırak versiyonu ( 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>

Adım 2:

Artık dosyaları inceleyebilir ve üstbilgileri ve MIME türlerini alay edebiliriz.

Method Hızlı yöntem

Bu kalıbı kullanarak Blob'un MIME türünü temsil ettiği herhangi bir dosyayı isteyebilirsiniz.

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

Görüntüler için MIME türleri aşağıdaki gibi geri gelir:

görüntü / jpeg
görüntü / png
...

Uyarı: MIME türü dosya uzantısından algılanır ve aldatılabilir veya aldatılabilir. Birini a .jpgolarak yeniden adlandırabilir .pngve MIME türü olarak bildirilir image/png.


✓ Uygun başlık inceleme yöntemi

İstemci tarafı bir dosyanın bonafide MIME türünü elde etmek için bir adım daha ileri gidebilir ve söz konusu dosyanın ilk birkaç baytını sihirli sayılarla karşılaştırmak için inceleyebiliriz . Bunun tamamen açık olmadığı konusunda uyarıda bulunun, örneğin JPEG'de birkaç "sihirli sayı" vardır. Bunun nedeni, biçimin 1991'den beri gelişmesidir. Yalnızca ilk iki baytı kontrol etmekten kurtulabilirsiniz, ancak yanlış pozitifleri azaltmak için en az 4 bayt kontrol etmeyi tercih ederim.

JPEG örnek dosya imzaları (ilk 4 bayt):

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

İşte dosya üstbilgisini almak için gerekli kod:

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

Daha sonra gerçek MIME türünü bu şekilde belirleyebilirsiniz ( burada ve burada daha fazla dosya imzası ):

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

Beklenen MIME türlerine göre dosya yüklemelerini istediğiniz gibi kabul edin veya reddedin.


gösteri

İşte yerel dosyalar ve uzak dosyalar için çalışan bir demo (Ben sadece bu demo için CORS atlamak zorunda kaldı). Parçacığı açın, çalıştırın ve farklı türde üç uzak resim görüntülendiğini görmelisiniz. Üstte yerel bir görüntü veya veri dosyası seçebilirsiniz ve dosya imzası ve / veya MIME türü görüntülenir.

Bir görüntü yeniden adlandırılsa bile, gerçek MIME türünün belirlenebileceğine dikkat edin. Aşağıya bakınız.

Ekran görüntüsü

Demodan beklenen çıktı



8
2 küçük yorum. (1) Okumadan önce dosyayı ilk 4 bayta dilimlemek daha iyi olmaz mı? fileReader.readAsArrayBuffer(blob.slice(0,4))? (2) Dosya imzalarını kopyalamak / yapıştırmak için, başlık önde gelen 0'larla oluşturulmamalıdır for(var i = 0; i < bytes.length; i++) { var byte = bytes[i]; fileSignature += (byte < 10 ? "0" : "") + byte.toString(16); }?
Matthew Madson

1
Bkz @Deadpool burada . Farklı üreticilerin daha fazla, daha az yaygın olan JPEG formatları vardır. Örneğin, FF D8 FF E2= CANNON EOS JPEG DOSYASI, FF D8 FF E3= SAMSUNG D500 JPEG DOSYASI. JPEG imzasının anahtar kısmı sadece 2 bayttır, ancak yanlış pozitifleri azaltmak için en yaygın 4 baytlık imzaları ekledim. Umarım bu yardımcı olur.
Drakes

23
Bu cevabın kalitesi inanılmaz.
Luca

2
MimeType'ı belirlemek için tam bir bloğu ArrayBuffer olarak yüklemeniz gerekmez. Sadece dilimleyebilir ve fileReader.readAsArrayBuffer(blob.slice(0, 4))
blobun

2
Yalnızca düz metne izin vermek için denetim ne olmalıdır? Metin dosyaları için ilk 4 bayt metin dosyasındaki ilk 4 karaktere görünür.
MP Droid

19

Diğer yanıtlarda belirtildiği gibi , dosyanın ilk baytında dosyanın imzasını kontrol ederek mime türünü kontrol edebilirsiniz .

Ancak diğer yanıtların yaptığı, imzayı kontrol etmek için tüm dosyayı belleğe yüklemektir , bu da çok israftır ve yanlışlıkla büyük bir dosyayı seçerseniz veya seçmezseniz tarayıcınızı kolayca dondurur.

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


Ben bir hata olsa bile readyStateher zaman FileReader.DONEolay işleyicisi ( W3C spec ) olacağını düşünüyorum - (!e.target.error)bunun yerine onay olmamalı ?
boycy

5

Bunu kendileri uygulamak istemeyen herkes için Sindresorhus, tarayıcıda çalışan ve isteyebileceğiniz çoğu belge için başa-mime eşleştirmelerine sahip bir yardımcı program oluşturmuştur.

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

Bu programı kullanarak her şeyi belleğe yüklemekten kaçınmak için Vitim.us'un yalnızca ilk X baytında okuma önerisini birleştirebilirsiniz (es6'da örnek):

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

Benim için kütüphanenin son sürümü işe yaramadı ama "file-type": "12.4.0"çalıştı ve kullanmak zorunda kaldımimport * as fileType from "file-type";
ssz

4

Yüklenen dosyanın bir resim olup olmadığını kontrol etmek istiyorsanız, <img>herhangi bir hata geri araması için bir çek işaretini etikete yüklemeyi deneyebilirsiniz .

Misal:

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

1
Harika çalışıyor, bir .gif dosya yükleyici kesmek denedim ve bir hata attı :)
pathfinder

4

Yapman gereken bu

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

Görüntü dosyası türlerini kontrol etmek istiyorsanız

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

Şu anda için çalışmıyor: iOS için Android için Firefox, Android için Opera ve Safari. developer.mozilla.org/tr-TR/docs/Web/API/File/type
Reid

3

İşte webp'i destekleyen bir dizgi uygulaması. Bu, Vitim.us'un JavaScript yanıtına dayanmaktadır.

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


1

Drake'in belirttiği gibi bu FileReader ile yapılabilir. Ancak, burada sunduğum işlevsel bir sürümüdür. JavaScript ile bunu yapmanın en büyük sorunun girdi dosyasını sıfırlamak olduğunu unutmayın. Bu sadece JPG ile sınırlıdır (diğer formatlar için mime türünü ve sihirli numarayı değiştirmeniz gerekir ):

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

Bunun Firefox ve Chrome'un en son sürümlerinde ve IExplore 10'da test edildiğini göz önünde bulundurun.

Mime tiplerinin tam listesi için Wikipedia'ya bakınız .

Sihirli sayının tam listesi için Wikipedia'ya bakınız .


Yukarıdaki Wikipedia bağlantıları artık geçerli değil.
Bob Quinn

@BobQuinn düzeltildi, thansk
lmiguelmh

0

Roberto14'ün cevabını aşağıdakileri yapan bir uzantısı:

BU SADECE GÖRÜNTÜLERİ İZİN VERECEK

FileReader'ın kullanılabilir olup olmadığını kontrol eder ve mevcut değilse uzantı kontrolüne geri döner.

Görüntü değilse hata uyarısı verir

Bir resim ise önizleme yükler

** Yine de sunucu tarafı doğrulaması yapmalısınız, bu son kullanıcı için her şeyden daha kolaylıktır. Ama kullanışlı!

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

-1

Kısa cevap hayır.

Not olarak, tarayıcılar typedosya uzantısından türetilir . Mac önizlemesi de uzantıyı bitirmiş gibi görünüyor. Ben onun daha hızlı bir şekilde işaretçi diskte dosyayı okumak yerine, işaretçide bulunan dosya adını okumak çünkü kabul ediyorum.

PNG ile yeniden adlandırılan bir jpg kopyası oluşturdum.

Chrome'da her iki resimden de tutarlı bir şekilde aşağıdakileri elde edebildim (modern tarayıcılarda çalışmalıdır).

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

Hangi bir String.indexOf ('jpeg') görüntü türü için kontrol kesmek.

İşte keşfetmek için bir keman http://jsfiddle.net/bamboo/jkZ2v/1/

Örnekte yorum yapmayı unuttuğum şüpheli hat

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

  • Base64 kodlu img verilerini böler ve görüntüde kalır
  • Base64 görüntünün kodunu çözer
  • Görüntü verilerinin yalnızca ilk satırıyla eşleşir

Keman kodu IE9'da çalışmayacak base64 kod çözme kullanır, IE'de çalışan VB komut dosyasını kullanarak güzel bir örnek buldum http://blog.nihilogic.dk/2008/08/imageinfo-reading-image-metadata-with.html

Görüntüyü yüklemek için kod yüklemeden önce istemci tarafını yeniden boyutlandıran harika bir görüntü tuvali yapan Joel Vardy'den alınmıştır ve bu ilgi çekici olabilir https://joelvardy.com/writing/javascript-image-upload


1
Lütfen "jpeg" alt dizesi için JPEG aramayın, bu sadece bir yorumda bulduğunuz bir tesadüf. JPEG dosyalarını içermesi gerekmez (ve JFIFbunun yerine arama yapmayı düşünüyorsanız APP0, EXIF-JPEG'lerde JFIF içermesi gerekmez, bu da dışarıdadır).
Kornel

Bkz. "Kısa cevap hayır".
Lex
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.