Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more functions to CompressedResource #123

Merged
merged 7 commits into from
Oct 9, 2024
Merged
19 changes: 14 additions & 5 deletions cmd/rwp/cmd/serve/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,20 @@ func (s *Server) getAsset(w http.ResponseWriter, r *http.Request) {
}

cres, ok := res.(fetcher.CompressedResource)
if ok && cres.CompressedAs(archive.CompressionMethodDeflate) && start == 0 && end == 0 && supportsDeflate(r) {
// Stream the asset in compressed format
w.Header().Set("content-encoding", "deflate")
w.Header().Set("content-length", strconv.FormatInt(cres.CompressedLength(), 10))
_, err = cres.StreamCompressed(w)
if ok && cres.CompressedAs(archive.CompressionMethodDeflate) && start == 0 && end == 0 {
// Stream the asset in compressed format if supported by the user agent
if supportsEncoding(r, "deflate") {
w.Header().Set("content-encoding", "deflate")
w.Header().Set("content-length", strconv.FormatInt(cres.CompressedLength(), 10))
_, err = cres.StreamCompressed(w)
} else if supportsEncoding(r, "gzip") && l <= archive.GzipMaxLength {
w.Header().Set("content-encoding", "gzip")
w.Header().Set("content-length", strconv.FormatInt(cres.CompressedLength()+archive.GzipWrapperLength, 10))
_, err = cres.StreamCompressedGzip(w)
} else {
// Fall back to normal streaming
_, rerr = res.Stream(w, start, end)
}
} else {
// Stream the asset
_, rerr = res.Stream(w, start, end)
Expand Down
4 changes: 2 additions & 2 deletions cmd/rwp/cmd/serve/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ func conformsToAsMimetype(conformsTo manifest.Profiles) string {
return mime
}

func supportsDeflate(r *http.Request) bool {
func supportsEncoding(r *http.Request, encoding string) bool {
vv := r.Header.Values("Accept-Encoding")
for _, v := range vv {
for _, sv := range strings.Split(v, ",") {
coding := parseCoding(sv)
if coding == "" {
continue
}
if coding == "deflate" {
if coding == encoding {
return true
}
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/archive/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ type Entry interface {
CompressedAs(compressionMethod CompressionMethod) bool // Whether the entry is compressed using the given method.
Read(start int64, end int64) ([]byte, error) // Reads the whole content of this entry, or a portion when [start] or [end] are specified.
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.
StreamCompressed(w io.Writer) (int64, error) // Streams the compressed content of this entry to a writer.

StreamCompressed(w io.Writer) (int64, error) // Streams the compressed content of this entry to a writer.
StreamCompressedGzip(w io.Writer) (int64, error) // Streams the compressed content of this entry to a writer in a GZIP container.
ReadCompressed() ([]byte, error) // Reads the compressed content of this entry.
ReadCompressedGzip() ([]byte, error) // Reads the compressed content of this entry inside a GZIP container.

}

// Represents an immutable archive.
Expand Down
12 changes: 12 additions & 0 deletions pkg/archive/archive_exploded.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,18 @@ func (e explodedArchiveEntry) StreamCompressed(w io.Writer) (int64, error) {
return -1, errors.New("entry is not compressed")
}

func (e explodedArchiveEntry) StreamCompressedGzip(w io.Writer) (int64, error) {
return -1, errors.New("entry is not compressed")
}

func (e explodedArchiveEntry) ReadCompressed() ([]byte, error) {
return nil, errors.New("entry is not compressed")
}

func (e explodedArchiveEntry) ReadCompressedGzip() ([]byte, error) {
return nil, errors.New("entry is not compressed")
}

// An archive exploded on the file system as a directory.
type explodedArchive struct {
directory string // Directory, already cleaned!
Expand Down
93 changes: 92 additions & 1 deletion pkg/archive/archive_zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"archive/zip"
"bytes"
"compress/flate"
"errors"
"encoding/binary"
"io"
"io/fs"
"math"
"path"
"sync"

"github.com/pkg/errors"
)

type gozipArchiveEntry struct {
Expand Down Expand Up @@ -164,6 +167,94 @@ func (e gozipArchiveEntry) StreamCompressed(w io.Writer) (int64, error) {
return io.Copy(w, f)
}

func (e gozipArchiveEntry) StreamCompressedGzip(w io.Writer) (int64, error) {
if e.file.Method != zip.Deflate {
return -1, errors.New("not a compressed resource")
}
if e.file.UncompressedSize64 > math.MaxUint32 {
return -1, errors.New("uncompressed size > 2^32 too large for GZIP")
}
f, err := e.file.OpenRaw()
if err != nil {
return -1, err
}

// Header
buf := [10]byte{0: gzipID1, 1: gzipID2, 2: gzipDeflate, 9: 255}
// No extra, no name, no comment, no mod time, no compress level hint, unknown OS

n, err := w.Write(buf[:10])
if err != nil {
return -1, errors.Wrap(err, "failed to write GZIP header")
}

nn, err := io.Copy(w, f)
if err != nil {
return int64(n), errors.Wrap(err, "failed copying deflated bytes")
}

// Trailer
binary.LittleEndian.PutUint32(buf[:4], e.file.CRC32)
binary.LittleEndian.PutUint32(buf[4:8], uint32(e.file.UncompressedSize64))
nnn, err := w.Write(buf[:8])
if err != nil {
return int64(n) + nn, errors.Wrap(err, "failed writing GZIP trailer")
}
return int64(n) + nn + int64(nnn), nil
}

func (e gozipArchiveEntry) ReadCompressed() ([]byte, error) {
if e.file.Method != zip.Deflate {
return nil, errors.New("not a compressed resource")
}
f, err := e.file.OpenRaw()
if err != nil {
return nil, err
}

compressedData := make([]byte, e.file.CompressedSize64)
_, err = io.ReadFull(f, compressedData)
if err != nil {
return nil, err
}

return compressedData, nil
}

func (e gozipArchiveEntry) ReadCompressedGzip() ([]byte, error) {
if e.file.Method != zip.Deflate {
return nil, errors.New("not a compressed resource")
}
if e.file.UncompressedSize64 > math.MaxUint32 {
return nil, errors.New("uncompressed size > 2^32 too large for GZIP")
}
f, err := e.file.OpenRaw()
if err != nil {
return nil, err
}

compressedData := make([]byte, e.file.CompressedSize64+GzipWrapperLength) // Size of file + header + trailer

// Deflated data
_, err = io.ReadAtLeast(f, compressedData[10:], int(e.file.CompressedSize64))
if err != nil {
return nil, err
}

// Header
compressedData[0] = gzipID1
compressedData[1] = gzipID2
compressedData[2] = gzipDeflate
compressedData[9] = 255
// No extra, no name, no comment, no mod time, no compress level hint, unknown OS

// Trailer
binary.LittleEndian.PutUint32(compressedData[10+e.file.CompressedSize64:], e.file.CRC32)
binary.LittleEndian.PutUint32(compressedData[10+e.file.CompressedSize64+4:], uint32(e.file.UncompressedSize64))

return compressedData, nil
}

// An archive from a zip file using go's stdlib
type gozipArchive struct {
zip *zip.Reader
Expand Down
12 changes: 12 additions & 0 deletions pkg/archive/gzip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package archive

import "math"

const (
gzipID1 = 0x1f
gzipID2 = 0x8b
gzipDeflate = 8
)

const GzipWrapperLength = 18
const GzipMaxLength = math.MaxUint32
27 changes: 27 additions & 0 deletions pkg/fetcher/fetcher_archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,33 @@ func (r *entryResource) StreamCompressed(w io.Writer) (int64, *ResourceError) {
return -1, Other(err)
}

// StreamCompressedGzip implements CompressedResource
func (r *entryResource) StreamCompressedGzip(w io.Writer) (int64, *ResourceError) {
i, err := r.entry.StreamCompressedGzip(w)
if err == nil {
return i, nil
}
return -1, Other(err)
}

// ReadCompressed implements CompressedResource
func (r *entryResource) ReadCompressed() ([]byte, *ResourceError) {
i, err := r.entry.ReadCompressed()
if err == nil {
return i, nil
}
return nil, Other(err)
}

// ReadCompressedGzip implements CompressedResource
func (r *entryResource) ReadCompressedGzip() ([]byte, *ResourceError) {
i, err := r.entry.ReadCompressedGzip()
if err == nil {
return i, nil
}
return nil, Other(err)
}

// Length implements Resource
func (r *entryResource) Length() (int64, *ResourceError) {
return int64(r.entry.Length()), nil
Expand Down
27 changes: 27 additions & 0 deletions pkg/fetcher/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,33 @@ func (r ProxyResource) StreamCompressed(w io.Writer) (int64, *ResourceError) {
return cres.StreamCompressed(w)
}

// StreamCompressedGzip implements CompressedResource
func (r ProxyResource) StreamCompressedGzip(w io.Writer) (int64, *ResourceError) {
cres, ok := r.Res.(CompressedResource)
if !ok {
return -1, Other(errors.New("resource is not compressed"))
}
return cres.StreamCompressedGzip(w)
}

// ReadCompressed implements CompressedResource
func (r ProxyResource) ReadCompressed() ([]byte, *ResourceError) {
cres, ok := r.Res.(CompressedResource)
if !ok {
return nil, Other(errors.New("resource is not compressed"))
}
return cres.ReadCompressed()
}

// ReadCompressedGzip implements CompressedResource
func (r ProxyResource) ReadCompressedGzip() ([]byte, *ResourceError) {
cres, ok := r.Res.(CompressedResource)
if !ok {
return nil, Other(errors.New("resource is not compressed"))
}
return cres.ReadCompressedGzip()
}

/**
* Transforms the bytes of [resource] on-the-fly.
*
Expand Down
3 changes: 3 additions & 0 deletions pkg/fetcher/traits.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ type CompressedResource interface {
CompressedAs(compressionMethod archive.CompressionMethod) bool
CompressedLength() int64
StreamCompressed(w io.Writer) (int64, *ResourceError)
StreamCompressedGzip(w io.Writer) (int64, *ResourceError)
ReadCompressed() ([]byte, *ResourceError)
ReadCompressedGzip() ([]byte, *ResourceError)
}
36 changes: 36 additions & 0 deletions pkg/parser/epub/deobfuscator.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ func (d DeobfuscatingResource) Stream(w io.Writer, start int64, end int64) (int6
shasum := sha1.Sum([]byte(d.identifier))
obfuscationKey = shasum[:]
}

// If getHashKeyAdobe() is blank, meaning the hex decoding of the UUID failed
if len(obfuscationKey) == 0 {
return 0, fetcher.Other(errors.New("error deriving font deobfuscation key"))
}

deobfuscateFont(obfuscatedPortion, start, obfuscationKey, v)

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

// StreamCompressedGzip implements CompressedResource
func (d DeobfuscatingResource) StreamCompressedGzip(w io.Writer) (int64, *fetcher.ResourceError) {
_, v := d.obfuscation()
if v > 0 {
return 0, fetcher.Other(errors.New("cannot stream compressed resource when obfuscated"))
}

return d.ProxyResource.StreamCompressedGzip(w)
}

// ReadCompressed implements CompressedResource
func (d DeobfuscatingResource) ReadCompressed() ([]byte, *fetcher.ResourceError) {
_, v := d.obfuscation()
if v > 0 {
return nil, fetcher.Other(errors.New("cannot read compressed resource when obfuscated"))
}

return d.ProxyResource.ReadCompressed()
}

// ReadCompressedGzip implements CompressedResource
func (d DeobfuscatingResource) ReadCompressedGzip() ([]byte, *fetcher.ResourceError) {
_, v := d.obfuscation()
if v > 0 {
return nil, fetcher.Other(errors.New("cannot read compressed resource when obfuscated"))
}

return d.ProxyResource.ReadCompressedGzip()
}

func (d DeobfuscatingResource) getHashKeyAdobe() []byte {
hexbytes, _ := hex.DecodeString(
strings.Replace(
Expand Down