package bookfile import ( "archive/zip" "errors" "fmt" "io" "path" "sort" "strconv" "strings" ) var ErrNotZip = errors.New("not a readable zip") var ErrUnsafeZip = errors.New("unsafe zip entry") func unsafeEntry(n string) bool { return strings.HasPrefix(n, "/") || strings.Contains(n, "..") || strings.ContainsRune(n, '\\') || strings.ContainsAny(n, "\n\r") } func isImage(name string) bool { switch strings.ToLower(path.Ext(name)) { case ".jpg", ".jpeg", ".png", ".webp", ".gif", ".avif": return true } return false } // junkEntry: macOS 打包混入的资源叉垃圾(__MACOSX/ 目录与 ._* AppleDouble),不是页 func junkEntry(name string) bool { if strings.HasPrefix(strings.ToLower(name), "__macosx/") { return true } return strings.HasPrefix(path.Base(name), "._") } func PageIndex(f io.ReaderAt, size int64) ([]string, error) { zr, err := zip.NewReader(f, size) if err != nil { return nil, ErrNotZip } var names []string for _, zf := range zr.File { if unsafeEntry(zf.Name) { return nil, fmt.Errorf("%w: %s", ErrUnsafeZip, zf.Name) } if junkEntry(zf.Name) { continue } if isImage(zf.Name) { names = append(names, zf.Name) } } sort.Slice(names, func(i, j int) bool { return NaturalLess(names[i], names[j]) }) return names, nil } func ReadEntry(f io.ReaderAt, size int64, name string) ([]byte, error) { zr, err := zip.NewReader(f, size) if err != nil { return nil, ErrNotZip } for _, zf := range zr.File { if zf.Name == name { rc, err := zf.Open() if err != nil { return nil, err } defer rc.Close() return io.ReadAll(io.LimitReader(rc, 64<<20)) } } return nil, errors.New("no such entry") } func NaturalLess(a, b string) bool { i, j := 0, 0 for i < len(a) && j < len(b) { da, db := isDigit(a[i]), isDigit(b[j]) switch { case da && db: si, sj := i, j for i < len(a) && isDigit(a[i]) { i++ } for j < len(b) && isDigit(b[j]) { j++ } na, _ := strconv.Atoi(a[si:i]) nb, _ := strconv.Atoi(b[sj:j]) if na != nb { return na < nb } if a[si:i] != b[sj:j] { return a[si:i] < b[sj:j] } case !da && !db: if a[i] != b[j] { return a[i] < b[j] } i++ j++ default: return da // 数字段排在字母前 } } return j < len(b) } func isDigit(c byte) bool { return c >= '0' && c <= '9' }