解析 PNG 与 ZIP:从二进制里抠出结构化数据

本节目标

PNG 结构:8 字节签名 + 若干 chunk,每个 chunk = 4 字节长度 + 4 字节类型 + 数据 + 4 字节 CRC。IHDR 是第一个 chunk,存宽高/色深。

// 运行环境:Node.js 14+
// 保存为 aj-l7.js,执行:node aj-l7.js(需有一个真实 png 文件,或见函数逻辑)
const fs = require('fs')

function parsePNG(buf) {
  // 跳过 8 字节签名
  let off = 8
  const chunks = []
  while (off < buf.length) {
    const len = buf.readUInt32BE(off); off += 4
    const type = buf.toString('ascii', off, off + 4); off += 4
    const data = buf.slice(off, off + len); off += len
    off += 4 // 跳过 CRC
    chunks.push({ type, data })
    if (type === 'IHDR') {
      return {
        width: data.readUInt32BE(0),
        height: data.readUInt32BE(4),
        bitDepth: data[8],
        colorType: data[9]
      }
    }
  }
  return { chunks }
}

function parseZIP(buf) {
  // 中央目录结尾记录 EOCD 签名 0x06054b50,从末尾向前找
  let off = buf.length - 22
  while (off >= 0) {
    if (buf.readUInt32LE(off) === 0x06054b50) break
    off--
  }
  const total = buf.readUInt16LE(off + 10)
  let p = off + 46 // 跳到文件名列表起点(简化:假设无注释/扩展)
  const names = []
  for (let i = 0; i < total; i++) {
    const nlen = buf.readUInt16LE(p + 28)
    names.push(buf.toString('ascii', p + 46, p + 46 + nlen))
    const clen = buf.readUInt32LE(p + 20)
    const elen = buf.readUInt16LE(p + 30)
    p += 46 + nlen + elen + clen // 粗略前进到下一 central directory header
  }
  return names
}

// 调用示例(需真实文件):
// const png = parsePNG(fs.readFileSync('a.png')); console.log(png)
// const zips = parseZIP(fs.readFileSync('a.zip')); console.log(zips)
console.log('parsePNG/parseZIP 已就绪,传入 Buffer 即可')

名词解释

课后练习

  1. PNG 为什么每个 chunk 都带长度字段?
    • 答案:让解析器能"跳过不关心/不认识的 chunk"前进到下一块,实现向前兼容(旧程序读新格式不会卡死)。
  2. ZIP 的 EOCD 为什么从文件末尾向前找?
    • 答案:中央目录在文件尾部,EOCD 标记其结束;从末尾反向扫描固定签名 0x06054b50 可快速定位,再向前读目录。

总结

解析 PNG/ZIP 让你第一次"用程序的眼睛看二进制"。你会发现,再复杂的文件格式,拆开无非是"签名 + 一连串带长度的类型化块 + 校验"。PNG 的 chunk 带长度,所以你能优雅地跳过不认识的块——这体现了格式设计的前向兼容智慧;ZIP 把目录放在末尾并用 EOCD 收尾,则展示了"索引后置"的检索巧思。我特别想强调端序(大端/小端)这个隐形杀手:readUInt32BE 与 readUInt32LE 一字之差,读出来的可能是天壤之别的数。手写这两个解析器,你会真正理解"文件格式"不是魔法,而是一份你和工具都遵守的"字节约定"。这种能力在逆向、数据恢复、自定义存储格式时价值连城。