问题分析
在解压 ZIP 文件时,文件名可能不是 UTF-8 编码的,如果只处理了 GBK 编码:
// 处理文件名编码
var fileName string
if utf8.ValidString(file.Name) {
fileName = file.Name
} else {
var bytes []byte
bytes, err = simplifiedchinese.GBK.NewDecoder().Bytes([]byte(file.Name))
if err != nil {
return err
}
fileName = string(bytes)
}这种方式只能处理 GBK 编码的文件名,对于其他编码格式的文件名就无能为力了。
解决方案
要求上传的压缩包使用统一的编码格式(推荐)
最简单和可靠的方法是要求用户上传的压缩包文件名必须使用 UTF-8 编码。如果检测到文件名不是 UTF-8 编码,则直接返回错误。
使用
golang.org/x/text/encoding包尝试自动检测并转换编码如果需要支持多种编码格式,可以使用
golang.org/x/text/encoding包尝试使用多种常见编码进行解码,直到成功为止。
方法一:强制使用 UTF-8 编码
首先,修改代码,检测文件名是否为 UTF-8 编码,如果不是,则返回错误提示。
修改后的代码如下:
import (
"archive/zip"
"errors"
"io"
"os"
"path/filepath"
"strings"
"unicode/utf8"
)
// unzipFile 解压zip文件
func unzipFile(zipPath, extractPath string) error {
// 打开 ZIP 文件
reader, err := zip.OpenReader(zipPath)
if err != nil {
return err
}
defer reader.Close()
// 压缩包中人脸数量超过最大数量
if len(reader.File) > consts.MaxFaceNum {
return errors.New("人脸数量超过限制,最多1000张")
}
// 遍历处理每个文件
for _, file := range reader.File {
// 如果是目录
if file.FileInfo().IsDir() {
continue
}
// 检查文件名是否为 UTF-8 编码
if !utf8.ValidString(file.Name) {
return errors.New("压缩包文件名必须使用 UTF-8 编码")
}
// 解压文件
if err = extractFile(file, extractPath); err != nil {
return err
}
}
return nil
}
// extractFile 解压单个文件
func extractFile(file *zip.File, extractPath string) error {
// 获取文件名
fileName := file.Name
// 规范化路径,防止路径遍历攻击
targetPath := filepath.Join(extractPath, fileName)
targetPath = filepath.Clean(targetPath)
if !strings.HasPrefix(targetPath, filepath.Clean(extractPath)+string(os.PathSeparator)) {
return errors.New("非法的文件路径:" + targetPath)
}
// 创建目标目录
if err := os.MkdirAll(filepath.Dir(targetPath), os.ModePerm); err != nil {
return err
}
// 打开压缩文件中的文件
rc, err := file.Open()
if err != nil {
return err
}
defer rc.Close()
// 创建目标文件
fw, err := os.Create(targetPath)
if err != nil {
return err
}
defer fw.Close()
// 复制文件内容
if _, err = io.Copy(fw, rc); err != nil {
return err
}
return nil
}说明:
使用
utf8.ValidString(file.Name)检查文件名是否为有效的 UTF-8 编码。如果文件名不是 UTF-8 编码,返回错误,提示用户压缩包文件名必须使用 UTF-8 编码。
通过这种方式,确保所有文件名都是 UTF-8 编码,简化后续处理。
优点:
简单可靠,避免了复杂的编码检测和转换。
可以在 API 文档中明确要求用户上传的压缩包使用 UTF-8 编码。
方法二:使用 golang.org/x/text/encoding 尝试多种编码进行解码
如果您需要支持多种编码格式的文件名,可以使用 golang.org/x/text/encoding 包尝试用多种常见的编码进行解码,直到成功为止。
修改代码如下:
import (
"archive/zip"
"bytes"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/charmap"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/encoding/korean"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/encoding/traditionalchinese"
"golang.org/x/text/transform"
)
// 定义常用的编码列表
var encodings = []encoding.Encoding{
simplifiedchinese.GBK,
simplifiedchinese.HZGB2312,
traditionalchinese.Big5,
japanese.ShiftJIS,
korean.EUCKR,
charmap.ISO8859_1,
charmap.ISO8859_2,
charmap.ISO8859_15,
}
func unzipFile(zipPath, extractPath string) error {
// 打开 ZIP 文件
reader, err := zip.OpenReader(zipPath)
if err != nil {
return err
}
defer reader.Close()
// 压缩包中人脸数量超过最大数量
if len(reader.File) > consts.MaxFaceNum {
return errors.New("人脸数量超过限制,最多1000张")
}
// 遍历处理每个文件
for _, file := range reader.File {
// 如果是目录
if file.FileInfo().IsDir() {
continue
}
// 解码文件名
fileName, err := decodeFileName(file.Name)
if err != nil {
return err
}
// 解压文件
if err = extractFile(file, extractPath, fileName); err != nil {
return err
}
}
return nil
}
func decodeFileName(rawName string) (string, error) {
// 如果是有效的 UTF-8,直接返回
if utf8.ValidString(rawName) {
return rawName, nil
}
rawBytes := []byte(rawName)
for _, enc := range encodings {
decoder := enc.NewDecoder()
name, err := decoder.String(rawName)
if err == nil && utf8.ValidString(name) {
return name, nil
}
// 尝试使用 transform
reader := transform.NewReader(bytes.NewReader(rawBytes), decoder)
decodedBytes, err := ioutil.ReadAll(reader)
if err == nil && utf8.Valid(decodedBytes) {
return string(decodedBytes), nil
}
}
return "", errors.New("无法解码文件名,可能使用了未知的编码格式")
}
func extractFile(file *zip.File, extractPath, fileName string) error {
// 规范化路径,防止路径遍历攻击
targetPath := filepath.Join(extractPath, fileName)
targetPath = filepath.Clean(targetPath)
if !strings.HasPrefix(targetPath, filepath.Clean(extractPath)+string(os.PathSeparator)) {
return errors.New("非法的文件路径:" + targetPath)
}
// 创建目标目录
if err := os.MkdirAll(filepath.Dir(targetPath), os.ModePerm); err != nil {
return err
}
// 打开压缩文件中的文件
rc, err := file.Open()
if err != nil {
return err
}
defer rc.Close()
// 创建目标文件
fw, err := os.Create(targetPath)
if err != nil {
return err
}
defer fw.Close()
// 复制文件内容
if _, err = io.Copy(fw, rc); err != nil {
return err
}
return nil
}说明:
定义一个常用编码的列表
encodings,包括 GBK、Big5、ShiftJIS 等。在
decodeFileName函数中,尝试使用这些编码进行解码,直到成功为止。使用
utf8.ValidString检查解码后的字符串是否是有效的 UTF-8。如果所有编码都无法解码文件名,则返回错误。
注意事项:
这种方法无法保证一定能正确解码文件名,因为文件名编码可能未知或不在常用编码列表中。
解码后的文件名可能仍然不正确,尤其是在编码检测失败的情况下。
自动检测编码并不可靠,可能会出现误判。
建议
优先选择方法一:要求用户上传的压缩包使用 UTF-8 编码,这是最简单可靠的方案。
更新 API 文档:在接口文档中明确说明要求上传的压缩包文件名必须使用 UTF-8 编码。
错误提示友好化:在返回错误信息时,提示用户如何解决问题,例如建议用户重新打包压缩文件,并使用 UTF-8 编码的文件名。
总结
处理文件名编码问题:可以通过要求统一使用 UTF-8 编码,或者尝试使用多种编码解码文件名。
安全性考虑:在处理文件路径时,要注意防止路径遍历攻击,使用
filepath.Clean和检查路径前缀。代码简化和优化:优先选择简单可靠的方法,避免复杂的编码检测逻辑。
示例:更新后的 unzipFile 函数(使用 UTF-8 编码)
func unzipFile(zipPath, extractPath string) error {
// 打开 ZIP 文件
reader, err := zip.OpenReader(zipPath)
if err != nil {
return err
}
defer reader.Close()
// 压缩包中人脸数量超过最大数量
if len(reader.File) > consts.MaxFaceNum {
return errors.New("人脸数量超过限制,最多1000张")
}
// 遍历处理每个文件
for _, file := range reader.File {
// 如果是目录
if file.FileInfo().IsDir() {
continue
}
// 检查文件名是否为 UTF-8 编码
if !utf8.ValidString(file.Name) {
return errors.New("压缩包文件名必须使用 UTF-8 编码")
}
// 解压文件
if err = extractFile(file, extractPath); err != nil {
return err
}
}
return nil
}后续步骤
测试代码:使用不同编码的压缩包进行测试,确保代码能够正确处理 UTF-8 编码的文件名,并在检测到非 UTF-8 编码时返回友好的错误信息。
更新文档:在 API 文档和用户手册中明确要求,并提供如何生成 UTF-8 编码的压缩包的指导。
用户提示:在前端界面或上传说明中,提示用户如果遇到文件名编码问题,该如何处理。
最后
以上就是名字长了才好记最近收集整理的关于golang实现上传的压缩包使用统一的编码格式的全部内容,更多相关golang实现上传内容请搜索靠谱客的其他文章。
发表评论 取消回复