222 lines
5.2 KiB
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
|
||
|
}
|