mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-08 01:20:27 +00:00
fix: gbk filename encoding in ZIP extraction (#1260)
This commit is contained in:
1 parent
93088b22f1
commit
4c90b38247
4 files changed
+129
-10
No files matched your search
+19
-1
@@ -12,6 +12,7 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/mholt/archives"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
)
|
||||
|
||||
// supportedArchiveExtensions contains file extensions supported by mholt/archives library
|
||||
@@ -70,6 +71,16 @@ type ArchivePartInfo struct {
|
||||
// ExtractProgressCallback is called to report extraction progress
|
||||
type ExtractProgressCallback func(extractedFiles int, totalFiles int, progress int)
|
||||
|
||||
// newZipFormat creates a Zip format with proper character encoding support.
|
||||
// It uses GB18030 encoding to handle Chinese characters in filenames that may
|
||||
// be encoded with legacy GBK/GB18030 instead of UTF-8.
|
||||
func newZipFormat() archives.Zip {
|
||||
return archives.Zip{
|
||||
// GB18030 is a superset of GBK and handles Chinese characters correctly
|
||||
TextEncoding: simplifiedchinese.GB18030,
|
||||
}
|
||||
}
|
||||
|
||||
// isArchiveFile checks if a file is a supported archive format
|
||||
func isArchiveFile(filename string) bool {
|
||||
lowerName := strings.ToLower(filename)
|
||||
@@ -109,7 +120,8 @@ func openArchive(archivePath string, password string) (*archiveInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle password-protected archives
|
||||
// Configure format-specific settings
|
||||
// Handle password-protected archives and character encoding
|
||||
if password != "" {
|
||||
if rar, ok := format.(archives.Rar); ok {
|
||||
rar.Password = password
|
||||
@@ -121,6 +133,12 @@ func openArchive(archivePath string, password string) (*archiveInfo, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// For ZIP files, configure character encoding to handle non-UTF8 filenames
|
||||
// This is essential for Chinese characters encoded in GBK/GB18030
|
||||
if _, ok := format.(archives.Zip); ok {
|
||||
format = newZipFormat()
|
||||
}
|
||||
|
||||
return &archiveInfo{
|
||||
file: file,
|
||||
stat: stat,
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
)
|
||||
|
||||
func TestIsArchiveFile(t *testing.T) {
|
||||
@@ -243,6 +245,69 @@ func createTestZipWithMultipleFiles(path string, numFiles int) error {
|
||||
return w.Close()
|
||||
}
|
||||
|
||||
// createTestZipWithChineseFilenames creates a test ZIP file with Chinese filenames encoded in GBK
|
||||
func createTestZipWithChineseFilenames(path string) error {
|
||||
zipFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
|
||||
w := zip.NewWriter(zipFile)
|
||||
|
||||
// Encode Chinese filenames in GBK (as some legacy Windows applications do)
|
||||
encoder := simplifiedchinese.GBK.NewEncoder()
|
||||
|
||||
// Add a file with Chinese filename
|
||||
chineseFilename := "测试文件.txt"
|
||||
gbkFilename, err := encoder.String(chineseFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a FileHeader and manually set the Name with GBK encoding
|
||||
// We need to mark it as non-UTF8 by not setting the UTF-8 flag
|
||||
header := &zip.FileHeader{
|
||||
Name: gbkFilename,
|
||||
Method: zip.Deflate,
|
||||
}
|
||||
// Clear the UTF-8 bit (bit 11) to indicate non-UTF8 encoding
|
||||
header.Flags = 0
|
||||
|
||||
f, err := w.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Write([]byte("这是测试内容"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add a file in a subdirectory with Chinese name
|
||||
chineseDirAndFile := "文件夹/中文内容.txt"
|
||||
gbkDirAndFile, err := encoder.String(chineseDirAndFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header2 := &zip.FileHeader{
|
||||
Name: gbkDirAndFile,
|
||||
Method: zip.Deflate,
|
||||
}
|
||||
header2.Flags = 0
|
||||
|
||||
f2, err := w.CreateHeader(header2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f2.Write([]byte("中文子文件内容"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Close()
|
||||
}
|
||||
|
||||
func TestOpenArchive_NonExistentFile(t *testing.T) {
|
||||
_, err := openArchive("/nonexistent/path/file.zip", "")
|
||||
if err == nil {
|
||||
@@ -2382,3 +2447,47 @@ func TestExtractZipMultiPart_Progress(t *testing.T) {
|
||||
t.Error("Expected progress callbacks")
|
||||
}
|
||||
}
|
||||
|
||||
// Test extracting ZIP files with Chinese filenames encoded in GBK/GB18030
|
||||
func TestExtractArchive_ChineseFilenames(t *testing.T) {
|
||||
tempDir, err := os.MkdirTemp("", "extract_chinese_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create a test ZIP file with Chinese filenames encoded in GBK
|
||||
zipPath := filepath.Join(tempDir, "chinese.zip")
|
||||
destDir := filepath.Join(tempDir, "extracted")
|
||||
|
||||
if err := createTestZipWithChineseFilenames(zipPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Extract the archive
|
||||
err = extractArchive(zipPath, destDir, "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("extractArchive failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify the extracted files with proper Chinese filenames
|
||||
expectedFiles := []string{
|
||||
filepath.Join(destDir, "测试文件.txt"),
|
||||
filepath.Join(destDir, "文件夹", "中文内容.txt"),
|
||||
}
|
||||
|
||||
for _, path := range expectedFiles {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
t.Errorf("expected file %q not found after extraction", path)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify content of the Chinese file
|
||||
content, err := os.ReadFile(filepath.Join(destDir, "测试文件.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(content) != "这是测试内容" {
|
||||
t.Errorf("unexpected content: %q", string(content))
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func extractZipMultiPart(firstPartPath string, destDir string, password string,
|
||||
|
||||
// First pass: count files for progress
|
||||
totalFiles := 0
|
||||
zip := archives.Zip{}
|
||||
zip := newZipFormat()
|
||||
err = zip.Extract(context.Background(), io.NewSectionReader(multiReader, 0, totalSize), func(ctx context.Context, fileInfo archives.FileInfo) error {
|
||||
if !fileInfo.IsDir() {
|
||||
totalFiles++
|
||||
|
||||
Reference in new issue
Block a user