我是靠谱客的博主 名字长了才好记,最近开发中收集的这篇文章主要介绍golang实现上传的压缩包使用统一的编码格式,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

问题分析

在解压 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 编码的文件名,对于其他编码格式的文件名就无能为力了。

解决方案

  1. 要求上传的压缩包使用统一的编码格式(推荐)

    最简单和可靠的方法是要求用户上传的压缩包文件名必须使用 UTF-8 编码。如果检测到文件名不是 UTF-8 编码,则直接返回错误。

  2. 使用 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实现上传的压缩包使用统一的编码格式所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(122)

评论列表共有 0 条评论

立即
投稿
返回
顶部