+10

Xác định kiểu của 1 loại tệp bất kỳ bằng JavaScript - Detect File Type 😊 (Series: Bí kíp Javascript - PHẦN 42)

Trong công việc hàng ngày, load lên tệp là một chức năng rất phổ biến. Trong một số trường hợp, chúng ta muốn có thể hạn chế loại tệp load lên, chẳng hạn như hạn chế load lên hình ảnh ở định dạng PNG. Đối với vấn đề này, chúng ta sẽ nghĩ đến việc giới hạn các loại tệp được load lên thông qua việc accept vào Properties của phần tử input:

<input type="file" id="inputFile" accept="image/png" />

Mặc dù giải pháp này có thể đáp ứng hầu hết các trường hợp, nhưng nếu user thay đổi hậu tố (suffix) của hình ảnh định dạng .jpeg thành .png, giúp file đó có thể vượt qua chức năng giới hạn đơn giản ở trên. Vậy nên giải quyết vấn đề này như thế nào? Chúng ta có thể xác định loại tệp chính xác bằng cách đọc binary data của tệp. Trước khi giới thiệu sơ đồ implement thực tế, hãy giới thiệu một số kiến ​​thức liên quan.

Sẽ có bạn bảo ngay là: cái này có cả đống thư viện support rồi mà chỉ cần dùng là được. Ok cái hoàn toàn đúng và chúng ta cũng hoàn toàn không cần viết lại nó. Tuy nhiên, những bài viết của mình về những vấn đề tương tự như thế này nhằm giải thích cách nó hoạt động. Dùng cái thứ mình hiểu nó vẫn sướng hơn đúng ko ae.

OK GÉT GÔ!!

Làm cách nào để xem binary data của một bức ảnh?

Để xem binary data tương ứng với một hình ảnh, bạn có thể sử dụng một số Editors, chẳng hạn như WinHex trên nền tảng Windows hoặc Synalyze It! Editors hex chuyên nghiệp trên nền tảng macOS. Tuy nhiên, ở đây mình sử dụng Binary Viewer extension trong Editors Visual Studio Code để xem binary data tương ứng với avatar dưới đây.

image.png

Làm thế nào để phân biệt các loại hình ảnh?

Máy tính không phân biệt các loại hình ảnh khác nhau bằng tên hậu tố của hình ảnh, mà bằng “Magic Number”. Đối với một số loại tệp, nội dung của một vài byte đầu tiên được cố định và loại tệp có thể được đánh giá theo nội dung của các byte này.

Các Magic Number tương ứng với các loại hình ảnh phổ biến được hiển thị trong hình sau:

image.png

Tiếp theo, hãy sử dụng Binary Viewer extension để xác minh loại hình ảnh đại diện của mình có đúng không?

image.png

Như có thể thấy từ hình trên, 8 byte đầu tiên của hình ảnh loại PNG là 0x89 50 4E 47 0D 0A 1A 0A. Khi bạn thay đổi tệp bytefer-avatar.png thành bytefer-avatar.jpeg, và mở tệp bằng Editors để xem nội dung nhị phân của hình ảnh, bạn sẽ thấy rằng 8 byte đầu tiên của tệp không thay đổi. Nhưng nếu bạn sử dụng phần tử input[type=”file”] để đọc thông tin tệp, kết quả sau đây sẽ được xuất ra:

File
  lastModified: 1658647747405
  lastModifiedDate: Sun Jul 24 2022 15:29:07 
  name: "bytefer-avatar.jpeg"
  size: 47318
  type: "image/jpeg"
  webkitRelativePath: ""
  [[Prototype]]: File

Loại tệp chính xác không được phần mở rộng tệp hoặc loại MIME của tệp nhận dạng. Tiếp theo, cùng xem cách đảm bảo loại hình ảnh chính xác bằng cách đọc thông tin nhị phân của hình ảnh khi load hình ảnh lên.

Làm cách nào để xác định loại hình ảnh?

1. tạo hàm readBuffer

Sau khi nhận được đối tượng tệp, chúng ta có thể đọc nội dung của tệp thông qua API FileReader. Bởi vì chúng ta không cần đọc toàn bộ thông tin của tệp, nên chúng ta tạo một hàm readBuffer để đọc phạm vi binary data được chỉ định trong tệp. Chỉ đọc từ byte vị trí start tới end:

function readBuffer(file, start = 0, end = 2) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      resolve(reader.result);
    };
    reader.onerror = reject;
    reader.readAsArrayBuffer(file.slice(start, end));
  });
}

Đối với hình ảnh loại PNG, 8 byte đầu tiên của tệp là 0x89 50 4E 47 0D 0A 1A 0A. Do đó, khi chúng ta kiểm tra xem tệp đã chọn có phải là hình ảnh loại PNG hay không, chúng ta chỉ cần đọc 8 byte dữ liệu đầu tiên và xác định xem nội dung của từng byte có nhất quán hay không.

2. Tạo chức hàm check

Để so sánh từng byte và tái sử dụng code tốt hơn. Hãy tiếp tục và tạo một hàm có tên là check:

function check(headers) {
  return (buffers, options = { offset: 0 }) =>
    headers.every(
      (header, index) => header === buffers[options.offset + index]
    );
}

3. Xác định loại hình ảnh có phải là PNG

Dựa vào hàm readBuffercheck đã được định nghĩa trước đó, chúng ta có thể thực hiện xác định loại hình ảnh có phải là PNG:

Code HTML

<div>
   Choose File:<input type="file" id="inputFile" accept="image/*"
     onchange="handleChange(event)" />
   <p id="realFileType"></p>
</div>

Code JavaScript

const isPNG = check([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); 
const realFileElement = document.querySelector("#realFileType");
async function handleChange(event) {
  const file = event.target.files[0];
  const buffers = await readBuffer(file, 0, 8);
  const uint8Array = new Uint8Array(buffers);
  realFileElement.innerText = `The type of ${file.name} is:${
    isPNG(uint8Array) ? "image/png" : file.type
  }`;
}

Sau khi ví dụ trên được chạy thành công, kết quả xác định tương ứng được hiển thị trong hình sau:

image.png

Code hoàn chỉnh như sau:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>File Type Detect Demo</title>
    </head>
    <body>
        <div>
            <input type="file" id="inputFile" accept="image/*" onchange="handleChange(event)"/>
            <p id="realFileType"></p>
        </div>
        <script>
            function check(headers) {
                return (buffers,options={
                    offset: 0
                })=>headers.every((header,index)=>header === buffers[options.offset + index]);
            }

            function readBuffer(file, start=0, end=2) {
                return new Promise((resolve,reject)=>{
                    const reader = new FileReader();
                    reader.onload = ()=>{
                        resolve(reader.result);
                    }
                    ;
                    reader.onerror = reject;
                    reader.readAsArrayBuffer(file.slice(start, end));
                }
                );
            }

            const isPNG = check([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
            const realFileElement = document.querySelector("#realFileType");

            async function handleChange(event) {
                const file = event.target.files[0];
                const buffers = await readBuffer(file, 0, 8);
                const uint8Array = new Uint8Array(buffers);
                realFileElement.innerText = `The type of ${file.name} is:${isPNG(uint8Array) ? "image/png" : file.type}`;
            }
        </script>
    </body>
</html>

Nếu bạn muốn xác định định dạng tệp JPEG, bạn cần định nghĩa hàm isJPEG.

const isJPEG = check([0xff, 0xd8, 0xff]);

Tuy nhiên, nếu bạn muốn xác định các loại tệp khác, chẳng hạn như tệp PDF thì sao? Ở đây, trước tiên chúng ta sử dụng Binary Viewer extension để xem nội dung nhị phân của tệp PDF:

image.png

Như có thể thấy từ hình trên, 4 byte đầu tiên của tệp PDF là 0x25 50 44 46 và chuỗi tương ứng là %PDF. Để cho phép user xác định loại một cách trực quan hơn, chúng ta có thể định nghĩa hàm stringToBytes:

function stringToBytes(string) {
  return [...string].map((character) => character.charCodeAt(0));
}

Dựa vào hàm stringToBytes, chúng ta có thể dễ dàng định nghĩa một hàm isPDF như sau:

const isPDF = check(stringToBytes("%PDF"));

Sử dụng hàm isPDF, bạn có thể thực hiện chức năng xác định tệp PDF. Nhưng trong công việc thực tế, có rất nhiều loại tệp khác nhau. Đối với tình huống này, bạn có thể sử dụng thư viện thứ ba tuyệt vời để thực hiện chức năng xác định loại tệp, chẳng hạn như thư viện file-type .

Ok, đó là cách xác định các loại tệp bằng JavaScript. Trong công việc thực tế, đối với các tình huống load tệp lên, vì lý do bảo mật, bạn nên giới hạn các loại tệp load lên trong quá trình phát triển. Đối với các trường hợp nghiêm ngặt hơn, bạn có thể cân nhắc sử dụng hàm được mô tả trong bài viết này để xác minh loại tệp.

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẤN - 0374226770

TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí