Skip to content

Commit 9305ff9

Browse files
authoredOct 9, 2024··
Add more functions to CompressedResource (#123)
1 parent 49642a2 commit 9305ff9

File tree

10 files changed

+231
-9
lines changed

10 files changed

+231
-9
lines changed
 

‎cmd/rwp/cmd/serve/api.go

+14-5
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,20 @@ func (s *Server) getAsset(w http.ResponseWriter, r *http.Request) {
256256
}
257257

258258
cres, ok := res.(fetcher.CompressedResource)
259-
if ok && cres.CompressedAs(archive.CompressionMethodDeflate) && start == 0 && end == 0 && supportsDeflate(r) {
260-
// Stream the asset in compressed format
261-
w.Header().Set("content-encoding", "deflate")
262-
w.Header().Set("content-length", strconv.FormatInt(cres.CompressedLength(), 10))
263-
_, err = cres.StreamCompressed(w)
259+
if ok && cres.CompressedAs(archive.CompressionMethodDeflate) && start == 0 && end == 0 {
260+
// Stream the asset in compressed format if supported by the user agent
261+
if supportsEncoding(r, "deflate") {
262+
w.Header().Set("content-encoding", "deflate")
263+
w.Header().Set("content-length", strconv.FormatInt(cres.CompressedLength(), 10))
264+
_, err = cres.StreamCompressed(w)
265+
} else if supportsEncoding(r, "gzip") && l <= archive.GzipMaxLength {
266+
w.Header().Set("content-encoding", "gzip")
267+
w.Header().Set("content-length", strconv.FormatInt(cres.CompressedLength()+archive.GzipWrapperLength, 10))
268+
_, err = cres.StreamCompressedGzip(w)
269+
} else {
270+
// Fall back to normal streaming
271+
_, rerr = res.Stream(w, start, end)
272+
}
264273
} else {
265274
// Stream the asset
266275
_, rerr = res.Stream(w, start, end)

‎cmd/rwp/cmd/serve/helpers.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,15 @@ func conformsToAsMimetype(conformsTo manifest.Profiles) string {
7474
return mime
7575
}
7676

77-
func supportsDeflate(r *http.Request) bool {
77+
func supportsEncoding(r *http.Request, encoding string) bool {
7878
vv := r.Header.Values("Accept-Encoding")
7979
for _, v := range vv {
8080
for _, sv := range strings.Split(v, ",") {
8181
coding := parseCoding(sv)
8282
if coding == "" {
8383
continue
8484
}
85-
if coding == "deflate" {
85+
if coding == encoding {
8686
return true
8787
}
8888
}

‎pkg/archive/archive.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ type Entry interface {
5858
CompressedAs(compressionMethod CompressionMethod) bool // Whether the entry is compressed using the given method.
5959
Read(start int64, end int64) ([]byte, error) // Reads the whole content of this entry, or a portion when [start] or [end] are specified.
6060
Stream(w io.Writer, start int64, end int64) (int64, error) // Streams the whole content of this entry to a writer, or a portion when [start] or [end] are specified.
61-
StreamCompressed(w io.Writer) (int64, error) // Streams the compressed content of this entry to a writer.
61+
62+
StreamCompressed(w io.Writer) (int64, error) // Streams the compressed content of this entry to a writer.
63+
StreamCompressedGzip(w io.Writer) (int64, error) // Streams the compressed content of this entry to a writer in a GZIP container.
64+
ReadCompressed() ([]byte, error) // Reads the compressed content of this entry.
65+
ReadCompressedGzip() ([]byte, error) // Reads the compressed content of this entry inside a GZIP container.
66+
6267
}
6368

6469
// Represents an immutable archive.

‎pkg/archive/archive_exploded.go

+12
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ func (e explodedArchiveEntry) StreamCompressed(w io.Writer) (int64, error) {
9090
return -1, errors.New("entry is not compressed")
9191
}
9292

93+
func (e explodedArchiveEntry) StreamCompressedGzip(w io.Writer) (int64, error) {
94+
return -1, errors.New("entry is not compressed")
95+
}
96+
97+
func (e explodedArchiveEntry) ReadCompressed() ([]byte, error) {
98+
return nil, errors.New("entry is not compressed")
99+
}
100+
101+
func (e explodedArchiveEntry) ReadCompressedGzip() ([]byte, error) {
102+
return nil, errors.New("entry is not compressed")
103+
}
104+
93105
// An archive exploded on the file system as a directory.
94106
type explodedArchive struct {
95107
directory string // Directory, already cleaned!

‎pkg/archive/archive_zip.go

+92-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"archive/zip"
55
"bytes"
66
"compress/flate"
7-
"errors"
7+
"encoding/binary"
88
"io"
99
"io/fs"
10+
"math"
1011
"path"
1112
"sync"
13+
14+
"github.com/pkg/errors"
1215
)
1316

1417
type gozipArchiveEntry struct {
@@ -164,6 +167,94 @@ func (e gozipArchiveEntry) StreamCompressed(w io.Writer) (int64, error) {
164167
return io.Copy(w, f)
165168
}
166169

170+
func (e gozipArchiveEntry) StreamCompressedGzip(w io.Writer) (int64, error) {
171+
if e.file.Method != zip.Deflate {
172+
return -1, errors.New("not a compressed resource")
173+
}
174+
if e.file.UncompressedSize64 > math.MaxUint32 {
175+
return -1, errors.New("uncompressed size > 2^32 too large for GZIP")
176+
}
177+
f, err := e.file.OpenRaw()
178+
if err != nil {
179+
return -1, err
180+
}
181+
182+
// Header
183+
buf := [10]byte{0: gzipID1, 1: gzipID2, 2: gzipDeflate, 9: 255}
184+
// No extra, no name, no comment, no mod time, no compress level hint, unknown OS
185+
186+
n, err := w.Write(buf[:10])
187+
if err != nil {
188+
return -1, errors.Wrap(err, "failed to write GZIP header")
189+
}
190+
191+
nn, err := io.Copy(w, f)
192+
if err != nil {
193+
return int64(n), errors.Wrap(err, "failed copying deflated bytes")
194+
}
195+
196+
// Trailer
197+
binary.LittleEndian.PutUint32(buf[:4], e.file.CRC32)
198+
binary.LittleEndian.PutUint32(buf[4:8], uint32(e.file.UncompressedSize64))
199+
nnn, err := w.Write(buf[:8])
200+
if err != nil {
201+
return int64(n) + nn, errors.Wrap(err, "failed writing GZIP trailer")
202+
}
203+
return int64(n) + nn + int64(nnn), nil
204+
}
205+
206+
func (e gozipArchiveEntry) ReadCompressed() ([]byte, error) {
207+
if e.file.Method != zip.Deflate {
208+
return nil, errors.New("not a compressed resource")
209+
}
210+
f, err := e.file.OpenRaw()
211+
if err != nil {
212+
return nil, err
213+
}
214+
215+
compressedData := make([]byte, e.file.CompressedSize64)
216+
_, err = io.ReadFull(f, compressedData)
217+
if err != nil {
218+
return nil, err
219+
}
220+
221+
return compressedData, nil
222+
}
223+
224+
func (e gozipArchiveEntry) ReadCompressedGzip() ([]byte, error) {
225+
if e.file.Method != zip.Deflate {
226+
return nil, errors.New("not a compressed resource")
227+
}
228+
if e.file.UncompressedSize64 > math.MaxUint32 {
229+
return nil, errors.New("uncompressed size > 2^32 too large for GZIP")
230+
}
231+
f, err := e.file.OpenRaw()
232+
if err != nil {
233+
return nil, err
234+
}
235+
236+
compressedData := make([]byte, e.file.CompressedSize64+GzipWrapperLength) // Size of file + header + trailer
237+
238+
// Deflated data
239+
_, err = io.ReadAtLeast(f, compressedData[10:], int(e.file.CompressedSize64))
240+
if err != nil {
241+
return nil, err
242+
}
243+
244+
// Header
245+
compressedData[0] = gzipID1
246+
compressedData[1] = gzipID2
247+
compressedData[2] = gzipDeflate
248+
compressedData[9] = 255
249+
// No extra, no name, no comment, no mod time, no compress level hint, unknown OS
250+
251+
// Trailer
252+
binary.LittleEndian.PutUint32(compressedData[10+e.file.CompressedSize64:], e.file.CRC32)
253+
binary.LittleEndian.PutUint32(compressedData[10+e.file.CompressedSize64+4:], uint32(e.file.UncompressedSize64))
254+
255+
return compressedData, nil
256+
}
257+
167258
// An archive from a zip file using go's stdlib
168259
type gozipArchive struct {
169260
zip *zip.Reader

‎pkg/archive/gzip.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package archive
2+
3+
import "math"
4+
5+
const (
6+
gzipID1 = 0x1f
7+
gzipID2 = 0x8b
8+
gzipDeflate = 8
9+
)
10+
11+
const GzipWrapperLength = 18
12+
const GzipMaxLength = math.MaxUint32

‎pkg/fetcher/fetcher_archive.go

+27
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,33 @@ func (r *entryResource) StreamCompressed(w io.Writer) (int64, *ResourceError) {
171171
return -1, Other(err)
172172
}
173173

174+
// StreamCompressedGzip implements CompressedResource
175+
func (r *entryResource) StreamCompressedGzip(w io.Writer) (int64, *ResourceError) {
176+
i, err := r.entry.StreamCompressedGzip(w)
177+
if err == nil {
178+
return i, nil
179+
}
180+
return -1, Other(err)
181+
}
182+
183+
// ReadCompressed implements CompressedResource
184+
func (r *entryResource) ReadCompressed() ([]byte, *ResourceError) {
185+
i, err := r.entry.ReadCompressed()
186+
if err == nil {
187+
return i, nil
188+
}
189+
return nil, Other(err)
190+
}
191+
192+
// ReadCompressedGzip implements CompressedResource
193+
func (r *entryResource) ReadCompressedGzip() ([]byte, *ResourceError) {
194+
i, err := r.entry.ReadCompressedGzip()
195+
if err == nil {
196+
return i, nil
197+
}
198+
return nil, Other(err)
199+
}
200+
174201
// Length implements Resource
175202
func (r *entryResource) Length() (int64, *ResourceError) {
176203
return int64(r.entry.Length()), nil

‎pkg/fetcher/resource.go

+27
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,33 @@ func (r ProxyResource) StreamCompressed(w io.Writer) (int64, *ResourceError) {
394394
return cres.StreamCompressed(w)
395395
}
396396

397+
// StreamCompressedGzip implements CompressedResource
398+
func (r ProxyResource) StreamCompressedGzip(w io.Writer) (int64, *ResourceError) {
399+
cres, ok := r.Res.(CompressedResource)
400+
if !ok {
401+
return -1, Other(errors.New("resource is not compressed"))
402+
}
403+
return cres.StreamCompressedGzip(w)
404+
}
405+
406+
// ReadCompressed implements CompressedResource
407+
func (r ProxyResource) ReadCompressed() ([]byte, *ResourceError) {
408+
cres, ok := r.Res.(CompressedResource)
409+
if !ok {
410+
return nil, Other(errors.New("resource is not compressed"))
411+
}
412+
return cres.ReadCompressed()
413+
}
414+
415+
// ReadCompressedGzip implements CompressedResource
416+
func (r ProxyResource) ReadCompressedGzip() ([]byte, *ResourceError) {
417+
cres, ok := r.Res.(CompressedResource)
418+
if !ok {
419+
return nil, Other(errors.New("resource is not compressed"))
420+
}
421+
return cres.ReadCompressedGzip()
422+
}
423+
397424
/**
398425
* Transforms the bytes of [resource] on-the-fly.
399426
*

‎pkg/fetcher/traits.go

+3
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ type CompressedResource interface {
1010
CompressedAs(compressionMethod archive.CompressionMethod) bool
1111
CompressedLength() int64
1212
StreamCompressed(w io.Writer) (int64, *ResourceError)
13+
StreamCompressedGzip(w io.Writer) (int64, *ResourceError)
14+
ReadCompressed() ([]byte, *ResourceError)
15+
ReadCompressedGzip() ([]byte, *ResourceError)
1316
}

‎pkg/parser/epub/deobfuscator.go

+36
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ func (d DeobfuscatingResource) Stream(w io.Writer, start int64, end int64) (int6
119119
shasum := sha1.Sum([]byte(d.identifier))
120120
obfuscationKey = shasum[:]
121121
}
122+
123+
// If getHashKeyAdobe() is blank, meaning the hex decoding of the UUID failed
124+
if len(obfuscationKey) == 0 {
125+
return 0, fetcher.Other(errors.New("error deriving font deobfuscation key"))
126+
}
127+
122128
deobfuscateFont(obfuscatedPortion, start, obfuscationKey, v)
123129

124130
defer pr.Close()
@@ -174,6 +180,36 @@ func (d DeobfuscatingResource) StreamCompressed(w io.Writer) (int64, *fetcher.Re
174180
return d.ProxyResource.StreamCompressed(w)
175181
}
176182

183+
// StreamCompressedGzip implements CompressedResource
184+
func (d DeobfuscatingResource) StreamCompressedGzip(w io.Writer) (int64, *fetcher.ResourceError) {
185+
_, v := d.obfuscation()
186+
if v > 0 {
187+
return 0, fetcher.Other(errors.New("cannot stream compressed resource when obfuscated"))
188+
}
189+
190+
return d.ProxyResource.StreamCompressedGzip(w)
191+
}
192+
193+
// ReadCompressed implements CompressedResource
194+
func (d DeobfuscatingResource) ReadCompressed() ([]byte, *fetcher.ResourceError) {
195+
_, v := d.obfuscation()
196+
if v > 0 {
197+
return nil, fetcher.Other(errors.New("cannot read compressed resource when obfuscated"))
198+
}
199+
200+
return d.ProxyResource.ReadCompressed()
201+
}
202+
203+
// ReadCompressedGzip implements CompressedResource
204+
func (d DeobfuscatingResource) ReadCompressedGzip() ([]byte, *fetcher.ResourceError) {
205+
_, v := d.obfuscation()
206+
if v > 0 {
207+
return nil, fetcher.Other(errors.New("cannot read compressed resource when obfuscated"))
208+
}
209+
210+
return d.ProxyResource.ReadCompressedGzip()
211+
}
212+
177213
func (d DeobfuscatingResource) getHashKeyAdobe() []byte {
178214
hexbytes, _ := hex.DecodeString(
179215
strings.Replace(

0 commit comments

Comments
 (0)
Please sign in to comment.