如何将ZIP压缩文件特性从UTF8修改为Descriptor UTF8?
提问
我用yazl库写了一个POC,用来生成包含XML文件的ZIP压缩包。目前压缩包的特性显示为“UTF8”,但我需要将其改为“Descriptor UTF8”。试过多个压缩库都没解决这个问题,求有经验的人帮忙。
格式对比
- 预期特性:Descriptor UTF8
- 当前特性:UTF8
POC代码
const yazl = require('yazl'); const fs = require('fs'); const path = require('path'); function test( location, ackId, responseContent, responsezipFileName, responsefileName ) { return new Promise((resolve, reject) => { const zipFile = new yazl.ZipFile(); // 确保内容是UTF-8编码的Buffer const contentBuffer = Buffer.isBuffer(responseContent) ? responseContent : Buffer.from(responseContent, 'utf8'); // 将日期转换为DOS日期/时间格式的函数 function dateToDosTime(d) { return { date: ((d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate(), time: (d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() / 2) }; } const now = new Date(); const { date: dosDate, time: dosTime } = dateToDosTime(now); // 将文件添加到ZIP,确保文件名是UTF-8编码 zipFile.addBuffer(contentBuffer, responsefileName, { mtime: now, mode: 0o100666, // FAT兼容模式 compress: true, forceZip64Format: false, dosPermissions: 0x20, // 设置存档位(普通文件) lastModFileTime: dosTime, lastModFileDate: dosDate }); // 完成ZIP文件并写入输出位置 const outputZipPath = path.join(location, `${responsezipFileName}`); const writeStream = fs.createWriteStream(outputZipPath); zipFile.outputStream.pipe(writeStream) .on('close', () => { console.info(`${responsezipFileName} 为FAT系统创建完成。`); resolve(); }) .on('error', (err) => { console.error('创建ZIP文件出错', err); reject(err); }); zipFile.end(); }).catch((error) => { console.error('test函数内部出错:::', error); throw new Error(error.message || '发生未知错误'); }); } // 示例调用 test( 'Downloads', '12345', `<?xml version="1.0" encoding="UTF-8"?> <note> <to>John</to> <from>Jane</from> <heading>Reminder</heading> <body>Don't forget the meeting at 10 AM tomorrow!</body> <date>2024-09-05</date> <unicodeExample>😊</unicodeExample> </note> `, 'response.zip', 'response.xml' ).then(() => { console.log('ZIP文件创建成功。'); }).catch((err) => { console.error('ZIP文件创建失败:', err); });
解决方案
要让ZIP压缩包的特性显示为“Descriptor UTF8”,需要同时满足两个核心条件:文件名采用UTF-8编码,并且ZIP条目附带Data Descriptor。以下是具体解决步骤:
1. 升级yazl到最新版本
旧版本的yazl可能存在UTF-8标志与Data Descriptor的兼容问题,先执行升级:
npm update yazl
2. 调整yazl代码配置
你的代码已经开启了UTF-8编码,但需要确保触发Data Descriptor的生成(yazl会在未预先提供文件CRC、压缩大小、未压缩大小的情况下自动生成Data Descriptor),优化后的代码如下:
const yazl = require('yazl'); const fs = require('fs'); const path = require('path'); function test( location, ackId, responseContent, responsezipFileName, responsefileName ) { return new Promise((resolve, reject) => { // 显式开启UTF-8文件名编码(默认已开启,显式设置更稳妥) const zipFile = new yazl.ZipFile({ encodeFileName: true }); const contentBuffer = Buffer.isBuffer(responseContent) ? responseContent : Buffer.from(responseContent, 'utf8'); function dateToDosTime(d) { return { date: ((d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate(), time: (d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() / 2) }; } const now = new Date(); const { date: dosDate, time: dosTime } = dateToDosTime(now); // 不提供crc32、compressedSize、uncompressedSize参数,触发Data Descriptor生成 zipFile.addBuffer(contentBuffer, responsefileName, { mtime: now, mode: 0o100666, compress: true, forceZip64Format: false, dosPermissions: 0x20, lastModFileTime: dosTime, lastModFileDate: dosDate }); const outputZipPath = path.join(location, `${responsezipFileName}`); const writeStream = fs.createWriteStream(outputZipPath); zipFile.outputStream.pipe(writeStream) .on('close', () => { console.info(`${responsezipFileName} 已创建(Descriptor UTF8格式)。`); resolve(); }) .on('error', (err) => { console.error('创建ZIP文件出错', err); reject(err); }); zipFile.end(); }).catch((error) => { console.error('test函数内部出错:', error); throw new Error(error.message || '发生未知错误'); }); } // 示例调用同前
3. 备选方案:使用archiver库
如果yazl始终无法达到预期效果,可以尝试archiver库,它支持显式配置Data Descriptor和UTF-8:
const archiver = require('archiver'); const fs = require('fs'); const path = require('path'); function test(location, ackId, responseContent, responsezipFileName, responsefileName) { return new Promise((resolve, reject) => { const outputPath = path.join(location, responsezipFileName); const outputStream = fs.createWriteStream(outputPath); // 显式启用UTF-8文件名编码和Data Descriptor const archive = archiver('zip', { zlib: { level: 9 }, forceZip64: false, utf8: true, dataDescriptor: true }); outputStream.on('close', () => { console.info(`${responsezipFileName} 创建成功。`); resolve(); }); archive.on('error', (err) => reject(err)); archive.pipe(outputStream); const contentBuffer = Buffer.isBuffer(responseContent) ? responseContent : Buffer.from(responseContent, 'utf8'); archive.append(contentBuffer, { name: responsefileName }); archive.finalize(); }); } // 示例调用同前
原理说明
- UTF-8文件名:通过开启对应配置,让ZIP条目设置通用位标志的第11位,告知解压工具文件名采用UTF-8编码。
- Data Descriptor:当ZIP条目未预先提供文件校验和与大小信息(yazl),或显式开启配置(archiver)时,会生成Data Descriptor并设置通用位标志的第3位,此时工具就会显示为“Descriptor UTF8”。
内容的提问来源于stack exchange,提问作者Aryan
相关产品推荐
相关产品推荐

