edge/pkg/bundle/zim/content_entry.go

222 lines
5.2 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, blobSize)
var (
blobStart uint64
blobEnd uint64
)
if extended {
if err := e.reader.readRange(int64(startPos+blobOffset), data); err != nil {
return nil, errors.WithStack(err)
}
blobStart64, err := readUint64(data, binary.LittleEndian)
if err != nil {
return nil, errors.WithStack(err)
}
blobStart = blobStart64
if err := e.reader.readRange(int64(startPos+blobOffset+uint64(blobSize)), data); err != nil {
return nil, errors.WithStack(err)
}
blobEnd64, err := readUint64(data, binary.LittleEndian)
if err != nil {
return nil, errors.WithStack(err)
}
blobEnd = uint64(blobEnd64)
} else {
if err := e.reader.readRange(int64(startPos+blobOffset), data); err != nil {
return nil, errors.WithStack(err)
}
blobStart32, err := readUint32(data, binary.LittleEndian)
if err != nil {
return nil, errors.WithStack(err)
}
blobStart = uint64(blobStart32)
if err := e.reader.readRange(int64(startPos+blobOffset+uint64(blobSize)), data); err != nil {
return nil, errors.WithStack(err)
}
blobEnd32, err := readUint32(data, 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, 2)
if err := r.readRange(offset, data); err != nil {
return nil, errors.WithStack(err)
}
mimeTypeIndex, err := readUint16(data, 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]
data = make([]byte, 1)
if err := r.readRange(offset+3, data); err != nil {
return nil, errors.WithStack(err)
}
entry.namespace = Namespace(data[0])
data = make([]byte, 4)
if err := r.readRange(offset+8, data); err != nil {
return nil, errors.WithStack(err)
}
clusterIndex, err := readUint32(data, binary.LittleEndian)
if err != nil {
return nil, errors.WithStack(err)
}
entry.clusterIndex = clusterIndex
if err := r.readRange(offset+12, data); err != nil {
return nil, errors.WithStack(err)
}
blobIndex, err := readUint32(data, binary.LittleEndian)
if err != nil {
return nil, errors.WithStack(err)
}
entry.blobIndex = blobIndex
url, read, err := r.readStringAt(offset + 16)
if err != nil {
return nil, errors.WithStack(err)
}
entry.url = url
title, _, err := r.readStringAt(offset + 16 + read + 1)
if err != nil {
return nil, errors.WithStack(err)
}
entry.title = title
return entry, nil
}