如何实现准确判断压缩文件类型
==============

#### 问题现象

应用内下载压缩文件在本地解压，简单通过文件扩展名去判断压缩文件类型会有被篡改的风险，应该如何准确判断压缩文件类型？

#### 背景知识

*   [@ohos.file.fs (文件管理)](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs)：为基础文件操作API，提供基础文件操作能力，包括文件基本管理、文件目录管理、文件信息统计、文件流式读写等常用功能。
*   文件头信息（File Header）：是位于文件开头的一段数据，用于描述文件的重要属性和格式信息。它通常包含文件的类型、大小、创建时间、修改时间、版本号、编码方式等元数据，这些信息对于操作系统或应用程序正确解析和处理文件至关重要。
*   文件“魔法数字”（Magic Number）：也称为幻数，是指在文件起始部分的一段固定字节序列，用于标识该文件的格式。它们就像文件的“身份证”，让计算机能够快速准确地识别文件类型，以便正确处理。

#### 解决方案

判断压缩文件的类型不能单一的依赖于文件后缀名，因为文件后缀名可以被轻易修改，导致基于后缀名的判断方法不可靠。

实际上可以通过检查文件的开头几个字节（称为魔法数字）来确定文件类型。每种类型的压缩文件都有其独特的魔法数字。这种方法需要程序具有访问和读取文件二进制数据的能力。

常见压缩文件格式魔数字典如下表：

| 
格式名称

 | 

扩展名

 | 

魔法数字（十六进制）

 |
| :-- | :-- | :-- |
| 

ZIP

 | 

.zip

 | 

0x504B0304

 |
| 

RAR

 | 

.rar

 | 

0x52617221

 |
| 

7-Zip

 | 

.7z

 | 

0x377ABCAF271C

 |
| 

GZIP

 | 

.gz

 | 

0x1F8B

 |
| 

TAR

 | 

.tar

 | 

0x75737461

 |

完整示例参考如下：

import { fileIo as fs, fileUri } from '@kit.CoreFileKit';

_//_ _常见压缩格式的魔数字典_
const MAGIC\_NUMBERS: Record<string, string\> = {
  'zip': '504B0304',
  'rar': '52617221',
  '7z': '377ABCAF271C',
  'gz': '1F8B',
  'tar': '75737461',
};

function detectCompressionFormat(uri: string): string {
  const file \= fs.openSync(uri, fs.OpenMode.READ\_ONLY);
  const buffer \= new ArrayBuffer(8); _// 读取前8字节_
  fs.readSync(file.fd, buffer, { offset: 0, length: 8 });
  const uint8Array \= new Uint8Array(buffer);
  const hexHeader \= Array.from(uint8Array).map(b \=\> b.toString(16).padStart(2, '0')).join('').toUpperCase();

  for (const item of Object.entries(MAGIC\_NUMBERS)) {
    if (hexHeader.startsWith(item\[1\])) {
      fs.closeSync(file.fd);
      return item\[0\];
    }
  }
  fs.closeSync(file.fd);

  return 'unknown';
}

@Entry
@Component
struct FileTypeDemo {
  getFileType() {
    let context \= this.getUIContext().getHostContext();
    let dirPath \= \`${context?.filesDir}/test.zip\`;
  _  // 从目录rawfile获取本地测试文件_
    const arrayBuff \= context?.resourceManager.getRawFileContentSync('test.zip').buffer;
    let file \= fs.openSync(dirPath, fs.OpenMode.READ\_WRITE | fs.OpenMode.CREATE);
    fs.writeSync(file.fd, arrayBuff);
    fs.closeSync(file.fd);
  _  // 获取文件uri_
    const srcFileUri \= fileUri.getUriFromPath(dirPath);
    const format \= detectCompressionFormat(srcFileUri);
    return format;
  }

  build() {
    Column() {
      Row() {
        Button('判断压缩文件类型')
          .width('50%')
          .height(50)
          .margin({ top: 5 })
          .onClick(() \=\> {
            this.getFileType();
          });
      };
    }
    .width('100%')
    .height(500)
    .margin({ top: 5 });
  }
}