194 lines
4.6 KiB
Go
194 lines
4.6 KiB
Go
package zim
|
|
|
|
import (
|
|
"encoding/binary"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type zimCompression int
|
|
|
|
const (
|
|
zimCompressionNoneZeno zimCompression = 0
|
|
zimCompressionNone zimCompression = 1
|
|
zimCompressionNoneZLib zimCompression = 2
|
|
zimCompressionNoneBZip2 zimCompression = 3
|
|
zimCompressionNoneXZ zimCompression = 4
|
|
zimCompressionNoneZStandard zimCompression = 5
|
|
)
|
|
|
|
type ContentEntry struct {
|
|
*BaseEntry
|
|
mimeType string
|
|
clusterIndex uint32
|
|
blobIndex uint32
|
|
}
|
|
|
|
func (e *ContentEntry) Compression() (int, error) {
|
|
clusterHeader, _, _, err := e.readClusterInfo()
|
|
if err != nil {
|
|
return 0, errors.WithStack(err)
|
|
}
|
|
|
|
return int((clusterHeader << 4) >> 4), nil
|
|
}
|
|
|
|
func (e *ContentEntry) MimeType() string {
|
|
return e.mimeType
|
|
}
|
|
|
|
func (e *ContentEntry) Reader() (BlobReader, error) {
|
|
clusterHeader, clusterStartOffset, clusterEndOffset, err := e.readClusterInfo()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
compression := (clusterHeader << 4) >> 4
|
|
extended := (clusterHeader<<3)>>7 == 1
|
|
|
|
blobSize := 4
|
|
if extended {
|
|
blobSize = 8
|
|
}
|
|
|
|
switch compression {
|
|
|
|
// Uncompressed blobs
|
|
case uint8(zimCompressionNoneZeno):
|
|
fallthrough
|
|
case uint8(zimCompressionNone):
|
|
startPos := clusterStartOffset + 1
|
|
blobOffset := uint64(e.blobIndex * uint32(blobSize))
|
|
|
|
data := make([]byte, 2*blobSize)
|
|
if err := e.reader.readRange(int64(startPos+blobOffset), data); err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
var (
|
|
blobStart uint64
|
|
blobEnd uint64
|
|
)
|
|
|
|
if extended {
|
|
blobStart64, err := readUint64(data[0:blobSize], binary.LittleEndian)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
blobStart = blobStart64
|
|
|
|
blobEnd64, err := readUint64(data[blobSize:blobSize*2], binary.LittleEndian)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
blobEnd = uint64(blobEnd64)
|
|
} else {
|
|
blobStart32, err := readUint32(data[0:blobSize], binary.LittleEndian)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
blobStart = uint64(blobStart32)
|
|
|
|
blobEnd32, err := readUint32(data[blobSize:blobSize*2], binary.LittleEndian)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
blobEnd = uint64(blobEnd32)
|
|
}
|
|
|
|
return NewUncompressedBlobReader(e.reader, startPos+blobStart, startPos+blobEnd, blobSize), nil
|
|
|
|
// Supported compression algorithms
|
|
case uint8(zimCompressionNoneXZ):
|
|
return NewXZBlobReader(e.reader, clusterStartOffset, clusterEndOffset, e.blobIndex, blobSize), nil
|
|
|
|
case uint8(zimCompressionNoneZStandard):
|
|
return NewZStdBlobReader(e.reader, clusterStartOffset, clusterEndOffset, e.blobIndex, blobSize), nil
|
|
|
|
// Unsupported compression algorithms
|
|
case uint8(zimCompressionNoneZLib):
|
|
fallthrough
|
|
case uint8(zimCompressionNoneBZip2):
|
|
fallthrough
|
|
default:
|
|
return nil, errors.Wrapf(ErrCompressionAlgorithmNotSupported, "unexpected compression algorithm '%d'", compression)
|
|
}
|
|
}
|
|
|
|
func (e *ContentEntry) Redirect() (*ContentEntry, error) {
|
|
return e, nil
|
|
}
|
|
|
|
func (e *ContentEntry) readClusterInfo() (uint8, uint64, uint64, error) {
|
|
startClusterOffset, clusterEndOffset, err := e.reader.getClusterOffsets(int(e.clusterIndex))
|
|
if err != nil {
|
|
return 0, 0, 0, errors.WithStack(err)
|
|
}
|
|
|
|
data := make([]byte, 1)
|
|
if err := e.reader.readRange(int64(startClusterOffset), data); err != nil {
|
|
return 0, 0, 0, errors.WithStack(err)
|
|
}
|
|
|
|
clusterHeader := uint8(data[0])
|
|
|
|
return clusterHeader, startClusterOffset, clusterEndOffset, nil
|
|
}
|
|
|
|
func (r *Reader) parseContentEntry(offset int64, base *BaseEntry) (*ContentEntry, error) {
|
|
entry := &ContentEntry{
|
|
BaseEntry: base,
|
|
}
|
|
|
|
data := make([]byte, 16)
|
|
if err := r.readRange(offset, data); err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
mimeTypeIndex, err := readUint16(data[0:2], binary.LittleEndian)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
if mimeTypeIndex >= uint16(len(r.mimeTypes)) {
|
|
return nil, errors.Errorf("mime type index '%d' greater than mime types length '%d'", mimeTypeIndex, len(r.mimeTypes))
|
|
}
|
|
|
|
entry.mimeType = r.mimeTypes[mimeTypeIndex]
|
|
|
|
entry.namespace = Namespace(data[3:4])
|
|
|
|
clusterIndex, err := readUint32(data[8:12], binary.LittleEndian)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
entry.clusterIndex = clusterIndex
|
|
|
|
blobIndex, err := readUint32(data[12:16], binary.LittleEndian)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
entry.blobIndex = blobIndex
|
|
|
|
strs, _, err := r.readStringsAt(offset+16, 2, 1024)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
if len(strs) > 0 {
|
|
entry.url = strs[0]
|
|
}
|
|
|
|
if len(strs) > 1 {
|
|
entry.title = strs[1]
|
|
}
|
|
|
|
return entry, nil
|
|
}
|