blob: 29c15c8c4efe59449cd2028aacd636c377731c74 [file] [log] [blame]
khenaidoo7d3c5582021-08-11 18:09:44 -04001// Copyright 2019+ Klaus Post. All rights reserved.
2// License information can be found in the LICENSE file.
3
4package zstd
5
6import (
7 "errors"
8 "io"
9 "sync"
10)
11
12// ZipMethodWinZip is the method for Zstandard compressed data inside Zip files for WinZip.
13// See https://www.winzip.com/win/en/comp_info.html
14const ZipMethodWinZip = 93
15
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +053016// ZipMethodPKWare is the original method number used by PKWARE to indicate Zstandard compression.
17// Deprecated: This has been deprecated by PKWARE, use ZipMethodWinZip instead for compression.
18// See https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.9.TXT
khenaidoo7d3c5582021-08-11 18:09:44 -040019const ZipMethodPKWare = 20
20
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +053021// zipReaderPool is the default reader pool.
22var zipReaderPool = sync.Pool{New: func() interface{} {
23 z, err := NewReader(nil, WithDecoderLowmem(true), WithDecoderMaxWindow(128<<20), WithDecoderConcurrency(1))
24 if err != nil {
25 panic(err)
khenaidoo7d3c5582021-08-11 18:09:44 -040026 }
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +053027 return z
28}}
29
30// newZipReader creates a pooled zip decompressor.
31func newZipReader(opts ...DOption) func(r io.Reader) io.ReadCloser {
32 pool := &zipReaderPool
33 if len(opts) > 0 {
34 opts = append([]DOption{WithDecoderLowmem(true), WithDecoderMaxWindow(128 << 20)}, opts...)
35 // Force concurrency 1
36 opts = append(opts, WithDecoderConcurrency(1))
37 // Create our own pool
38 pool = &sync.Pool{}
39 }
40 return func(r io.Reader) io.ReadCloser {
41 dec, ok := pool.Get().(*Decoder)
42 if ok {
43 dec.Reset(r)
44 } else {
45 d, err := NewReader(r, opts...)
46 if err != nil {
47 panic(err)
48 }
49 dec = d
50 }
51 return &pooledZipReader{dec: dec, pool: pool}
52 }
khenaidoo7d3c5582021-08-11 18:09:44 -040053}
54
55type pooledZipReader struct {
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +053056 mu sync.Mutex // guards Close and Read
57 pool *sync.Pool
58 dec *Decoder
khenaidoo7d3c5582021-08-11 18:09:44 -040059}
60
61func (r *pooledZipReader) Read(p []byte) (n int, err error) {
62 r.mu.Lock()
63 defer r.mu.Unlock()
64 if r.dec == nil {
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +053065 return 0, errors.New("read after close or EOF")
khenaidoo7d3c5582021-08-11 18:09:44 -040066 }
67 dec, err := r.dec.Read(p)
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +053068 if err == io.EOF {
69 r.dec.Reset(nil)
70 r.pool.Put(r.dec)
71 r.dec = nil
72 }
khenaidoo7d3c5582021-08-11 18:09:44 -040073 return dec, err
74}
75
76func (r *pooledZipReader) Close() error {
77 r.mu.Lock()
78 defer r.mu.Unlock()
79 var err error
80 if r.dec != nil {
81 err = r.dec.Reset(nil)
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +053082 r.pool.Put(r.dec)
khenaidoo7d3c5582021-08-11 18:09:44 -040083 r.dec = nil
84 }
85 return err
86}
87
88type pooledZipWriter struct {
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +053089 mu sync.Mutex // guards Close and Read
90 enc *Encoder
91 pool *sync.Pool
khenaidoo7d3c5582021-08-11 18:09:44 -040092}
93
94func (w *pooledZipWriter) Write(p []byte) (n int, err error) {
95 w.mu.Lock()
96 defer w.mu.Unlock()
97 if w.enc == nil {
98 return 0, errors.New("Write after Close")
99 }
100 return w.enc.Write(p)
101}
102
103func (w *pooledZipWriter) Close() error {
104 w.mu.Lock()
105 defer w.mu.Unlock()
106 var err error
107 if w.enc != nil {
108 err = w.enc.Close()
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +0530109 w.pool.Put(w.enc)
khenaidoo7d3c5582021-08-11 18:09:44 -0400110 w.enc = nil
111 }
112 return err
113}
114
115// ZipCompressor returns a compressor that can be registered with zip libraries.
116// The provided encoder options will be used on all encodes.
117func ZipCompressor(opts ...EOption) func(w io.Writer) (io.WriteCloser, error) {
118 var pool sync.Pool
119 return func(w io.Writer) (io.WriteCloser, error) {
120 enc, ok := pool.Get().(*Encoder)
121 if ok {
122 enc.Reset(w)
123 } else {
124 var err error
125 enc, err = NewWriter(w, opts...)
126 if err != nil {
127 return nil, err
128 }
129 }
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +0530130 return &pooledZipWriter{enc: enc, pool: &pool}, nil
khenaidoo7d3c5582021-08-11 18:09:44 -0400131 }
132}
133
134// ZipDecompressor returns a decompressor that can be registered with zip libraries.
135// See ZipCompressor for example.
Akash Reddy Kankanalac28f0e22025-06-16 11:00:55 +0530136// Options can be specified. WithDecoderConcurrency(1) is forced,
137// and by default a 128MB maximum decompression window is specified.
138// The window size can be overridden if required.
139func ZipDecompressor(opts ...DOption) func(r io.Reader) io.ReadCloser {
140 return newZipReader(opts...)
khenaidoo7d3c5582021-08-11 18:09:44 -0400141}