Bug之旅:原来 Excel 文件加密后文件格式还不一样

是近在学习 Go 操作 Excel 的库 excelize 时,在官方文档工作簿一节列举了在读写电子表格时的选项如下:

go 复制代码
type Options struct {
    MaxCalcIterations uint
    Password          string
    RawCellValue      bool
    UnzipSizeLimit    int64
    UnzipXMLSizeLimit int64
    ShortDatePattern  string
    LongDatePattern   string
    LongTimePattern   string
    CultureInfo       CultureName
}

很多选项不知道有什么作用,唯一能立马明白的就是 Password 这个选项,于是创建了一个新的电子表格并设置了密码保存,然后在 Excelize 中使用如下代码打开:

go 复制代码
package main

import (
	"log"

	"github.com/xuri/excelize/v2"
)

func main() {
	f, err := excelize.OpenFile("E:\\ExcelDemo\\Book1.xlsx")

	if err != nil {
		log.Fatal(err)
		return
	}
	defer func() {
		if err := f.Close(); err != nil {
			log.Fatal(err)
		}
	}()
}

运行后得到了一个让人诧异的错误:

python 复制代码
2024/04/17 14:45:30 zip: not a valid zip file

很奇怪,不应该提示这个文件已经加密,请提供密码吗?然后我把设置的密码加上后,一切又正常了.

go 复制代码
f, err := excelize.OpenFile("E:\\ExcelDemo\\Book1.xlsx", excelize.Options{
	Password: "123456",
})

带着疑问,我去官方 Issue 中使用 "zip: not a valid zip file" 进行搜索,发现这个相关的问题还真不少,但是粗略查看了几个后,也没有和我这个类似的,也没有人提供什么好的解决方案,只看到的有的说复制一份,然后再打开可以,这似乎不是我想要的,于是我就用蹩脚英文提了一个 Issue,希望得到作者的帮助。

第二天上班发现 Issue 已经被关了,xuri 大佬给出了产生这个问题的原因,但是建议自己去解决:

Thanks for your issue. The unencrypted workbook is a compressed file with the ZIP format, but the encrypted workbook is a CFB (OLE) file, which is different from the ZIP format. You will get that error message not only after opening an encrypted workbook without specifying the correct password but also after opening any file format that isn't supported by this library. So I think this error message is expected. Note that, you can roughly determine if a file is in a CFB format by this identifier. I'll close this issue. If you have any questions, please let me know, and you can reopen this anytime.

加密之后怎么就变味了,于是在网上看了一下 CFB 相关的信息,比如:

扫了一下有点复杂,没必要去了解,于是又顺着 xuri 在回复中提及源码中的 identifier:

go 复制代码
const oleIdentifier = []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1}

然后立马下载了一个查看文件 16 进制的软件 WinHex,将一个加密和一个未加密的文件同时打开,其头部对比如下:

图中红色圈中的不就刚好是 oleIdentifier 吗,于是又根据文章:

写了一个判断的方法 isOleExcel(f io.ReadSeeker) bool

go 复制代码
func isOleExcel(f io.ReadSeeker) bool {
	oleIdentifier := []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1}
	buf := make([]byte, len(oleIdentifier))

	_, err := f.Read(buf)

	if err != nil {
		return false
	}

	f.Seek(0, io.SeekStart)

	return bytes.Compare(buf, oleIdentifier) == 0
}

上面的代码中,我们根据魔术字符串的长度读取了文件流的前 8 个字节到缓冲区,然后使用 f.Seek 方法将读写偏移重置为原始位置,不这样做的话,原始文件前 8 个字节会因为被读取了而变成空数据。然后使用 bytes.Compare 方法对比字节是否一样。

我们现在有了判断文件是否加密(如何有其它方式导致其变成 CFB 文件,提示信息就不一定正确了,所以官方不作统一处理,是权重考虑过了),如何用户提供了密码,Excelize 在验证时如果发现不正确会抛出 ErrWorkbookPassword 错误,下面是完整的实现参考:

go 复制代码
package main

import (
	"bytes"
	"io"
	"log"
	"os"

	"github.com/xuri/excelize/v2"
)

func main() {
	filePath := "E:/ExcelDemo/Book1.xlsx"

	bs, _ := os.Open(filePath)
	defer bs.Close()

	// f, err := excelize.OpenFile(filePath)
	f, err := excelize.OpenFile(filePath, excelize.Options{
		Password: "123456",
	})

	if err != nil {
		if err == excelize.ErrWorkbookPassword {
			// 也可以直接,使用 `log.Fatal(err)` 显示英文描述
			log.Fatal("Excel文件密码错误")
		} else {
			if isOleExcel(bs) {
				log.Fatal("文件已加密,请先解密后再操作")
			} else {
				log.Fatal(err)
			}
		}
		return
	}

	defer func() {
		if err := f.Close(); err != nil {
			log.Fatal(err)
		}
	}()
}

func isOleExcel(f io.ReadSeeker) bool {
	oleIdentifier := []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1}
	buf := make([]byte, len(oleIdentifier))

	_, err := f.Read(buf)

	if err != nil {
		return false
	}

	f.Seek(0, io.SeekStart)

	return bytes.Compare(buf, oleIdentifier) == 0
}
相关推荐
运维开发笔记17 小时前
5.2 Go 数组进阶学习笔记(多维、排序、搜索)
go
码云数智-园园1 天前
Python如何将律师的Excel噩梦变成自动化系统
python·自动化·excel
newerp1 天前
Redis 操作与缓存策略
后端·程序员·go
newerp1 天前
GORM ORM 基础
后端·程序员·go
newerp1 天前
CRUD 操作与预处理语句
后端·程序员·go
用户298698530141 天前
将 Excel 表格转换为图片的三种实用方法
人工智能·后端·excel
AI英德西牛仔1 天前
秘塔 导出word指令之外:AI导出鸭电脑版的底层逻辑与批量交付哲学
人工智能·word·excel·deepseek·ai导出鸭
AI英德西牛仔1 天前
让 AI 导出回归优雅:纳米AI excel 与“AI 导出鸭”的 PC 端批量导出技术拆解
人工智能·excel·deepseek·ai导出鸭
AI导出鸭1 天前
怎么让腾讯元宝做表格?AI导出鸭苹果版通过解析引擎将元宝表格HTML转为结构化数据,一键导出标准Excel或Word
人工智能·chatgpt·html·excel·ai导出鸭
程序员爱钓鱼1 天前
Go 编程实战:闭包 Closure——函数如何记住外部变量
后端·google·go