Files
ARTEX/server/skill_zip.go
BaaRai e233c02314 Translate all Chinese text to English
Comments, doc comments, UI labels, log and error messages, LLM prompt
strings, markdown docs, test data and assertions. Identifiers, enum
keys, routes, placeholders and code structure are unchanged.

Notes:
- docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md
- tests that exercised CJK specific handling (GBK zip names, Chinese
  full text search) now use ASCII equivalents; go build passes and the
  test suite matches the pre translation baseline (remaining failures
  need a PostgreSQL DSN and fail identically on the original)
- frontend mention syntax now uses English kinds (finding, asset, ...)
  matching the backend, chat mentions tests pass
2026-10-08 23:14:34 +02:00

135 lines
4.3 KiB
Go

package server
import (
"archive/zip"
"bytes"
"compress/bzip2"
"fmt"
"io"
"path"
"strings"
"unicode/utf8"
"github.com/klauspost/compress/zstd"
"golang.org/x/text/encoding/simplifiedchinese"
)
// Go's archive/zip ships only Store(0) and Deflate(8) decompressors; any other
// method returns "zip: unsupported compression algorithm". Archivers at
// non-default settings often write other methods (7-Zip's bzip2, WinZip's
// zstd), so the two a pure-Go build can decode are registered here; the ones we
// truly cannot decode (Deflate64 / LZMA / XZ / PPMd / encrypted archives) get a
// clear error message before extraction instead of surfacing the raw
// underlying error to the user.
const (
zipMethodStore = 0
zipMethodDeflate = 8
zipMethodDeflate64 = 9
zipMethodBzip2 = 12
zipMethodLZMA = 14
zipMethodZstdPKW = 20 // the number PKWARE originally assigned to zstd
zipMethodZstd = 93
zipMethodXZ = 95
zipMethodJPEG = 96
zipMethodWavPack = 97
zipMethodPPMd = 98
zipMethodAES = 99
)
var zipMethodNames = map[uint16]string{
zipMethodStore: "Store",
zipMethodDeflate: "Deflate",
zipMethodDeflate64: "Deflate64",
zipMethodBzip2: "bzip2",
zipMethodLZMA: "LZMA",
zipMethodZstdPKW: "Zstandard",
zipMethodZstd: "Zstandard",
zipMethodXZ: "XZ",
zipMethodJPEG: "JPEG",
zipMethodWavPack: "WavPack",
zipMethodPPMd: "PPMd",
zipMethodAES: "AES encrypted",
}
func zipMethodName(m uint16) string {
if n, ok := zipMethodNames[m]; ok {
return n
}
return "unknown"
}
// newSkillZipReader parses an uploaded archive and registers the extra decompressors
// we can support beyond the stdlib's Store/Deflate.
func newSkillZipReader(buf []byte) (*zip.Reader, error) {
zr, err := zip.NewReader(bytes.NewReader(buf), int64(len(buf)))
if err != nil {
return nil, fmt.Errorf("cannot parse archive (must be zip format): %w", err)
}
zr.RegisterDecompressor(zipMethodBzip2, func(r io.Reader) io.ReadCloser {
return io.NopCloser(bzip2.NewReader(r))
})
zdec := zstd.ZipDecompressor(zstd.WithDecoderConcurrency(1))
zr.RegisterDecompressor(zipMethodZstd, zdec)
zr.RegisterDecompressor(zipMethodZstdPKW, zdec)
return zr, nil
}
// skillZipEntry pairs a zip entry with its decoded (UTF-8) name — f.Name may hold
// raw GBK bytes, see zipEntryName.
type skillZipEntry struct {
f *zip.File
name string
}
// skillZipEntries lists the archive's real files (no directory entries, no archiver
// junk) with their names decoded to UTF-8.
func skillZipEntries(zr *zip.Reader) []skillZipEntry {
out := make([]skillZipEntry, 0, len(zr.File))
for _, f := range zr.File {
if f.FileInfo().IsDir() {
continue
}
name := zipEntryName(f)
if strings.HasPrefix(name, "__MACOSX/") || strings.Contains(name, "/__MACOSX/") ||
path.Base(name) == ".DS_Store" {
continue // macOS archiving leftovers
}
out = append(out, skillZipEntry{f: f, name: name})
}
return out
}
// zipEntryName returns the entry path as UTF-8. 7-Zip / WinRAR / Explorer on
// Windows write Chinese filenames as GBK bytes when the UTF-8 flag is not set,
// and Go keeps those bytes verbatim, leaving a name that is neither valid UTF-8
// nor able to pass path validation — fall back to GBK decoding here.
func zipEntryName(f *zip.File) string {
if utf8.ValidString(f.Name) {
return f.Name
}
if dec, err := simplifiedchinese.GBK.NewDecoder().String(f.Name); err == nil && utf8.ValidString(dec) {
return dec
}
return f.Name
}
// checkSkillZipMethods rejects archives we cannot extract, naming the offending
// entry and method instead of letting f.Open() fail with an opaque English error.
func checkSkillZipMethods(entries []skillZipEntry) error {
for _, e := range entries {
if e.f.Flags&0x1 != 0 || e.f.Method == zipMethodAES {
return fmt.Errorf("archive is encrypted (%s); please upload an unencrypted zip", e.name)
}
switch e.f.Method {
case zipMethodStore, zipMethodDeflate, zipMethodBzip2, zipMethodZstd, zipMethodZstdPKW:
default:
return fmt.Errorf("archive uses unsupported compression method %s (method %d): %s. "+
"Please repack with \"Store\" or \"Deflate\" (choose Deflate as the compression "+
"method in 7-Zip/WinRAR, use the built-in \"Compress/Send to compressed folder\", "+
"or the zip -r command)",
zipMethodName(e.f.Method), e.f.Method, e.name)
}
}
return nil
}