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

#### 问题现象

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

#### 背景知识

*   @ohos.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 });
  }
}
```