139 lines
2.6 KiB
Go
139 lines
2.6 KiB
Go
|
package zim
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
type Entry interface {
|
||
|
Redirect() (*ContentEntry, error)
|
||
|
Namespace() Namespace
|
||
|
URL() string
|
||
|
FullURL() string
|
||
|
Title() string
|
||
|
}
|
||
|
|
||
|
type BaseEntry struct {
|
||
|
mimeTypeIndex uint16
|
||
|
namespace Namespace
|
||
|
url string
|
||
|
title string
|
||
|
reader *Reader
|
||
|
}
|
||
|
|
||
|
func (e *BaseEntry) Namespace() Namespace {
|
||
|
return e.namespace
|
||
|
}
|
||
|
|
||
|
func (e *BaseEntry) Title() string {
|
||
|
if e.title == "" {
|
||
|
return e.url
|
||
|
}
|
||
|
|
||
|
return e.title
|
||
|
}
|
||
|
|
||
|
func (e *BaseEntry) URL() string {
|
||
|
return e.url
|
||
|
}
|
||
|
|
||
|
func (e *BaseEntry) FullURL() string {
|
||
|
return toFullURL(e.Namespace(), e.URL())
|
||
|
}
|
||
|
|
||
|
func (r *Reader) parseBaseEntry(offset int64) (*BaseEntry, error) {
|
||
|
entry := &BaseEntry{
|
||
|
reader: r,
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
entry.mimeTypeIndex = mimeTypeIndex
|
||
|
|
||
|
data = make([]byte, 1)
|
||
|
if err := r.readRange(offset+3, data); err != nil {
|
||
|
return nil, errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
entry.namespace = Namespace(data[0])
|
||
|
|
||
|
return entry, nil
|
||
|
}
|
||
|
|
||
|
type RedirectEntry struct {
|
||
|
*BaseEntry
|
||
|
redirectIndex uint32
|
||
|
}
|
||
|
|
||
|
func (e *RedirectEntry) Redirect() (*ContentEntry, error) {
|
||
|
if e.redirectIndex >= uint32(len(e.reader.urlIndex)) {
|
||
|
return nil, errors.Wrapf(ErrInvalidEntryIndex, "entry index '%d' out of bounds", e.redirectIndex)
|
||
|
}
|
||
|
|
||
|
entryPtr := e.reader.urlIndex[e.redirectIndex]
|
||
|
entry, err := e.reader.parseEntryAt(int64(entryPtr))
|
||
|
if err != nil {
|
||
|
return nil, errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
entry, err = entry.Redirect()
|
||
|
if err != nil {
|
||
|
return nil, errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
contentEntry, ok := entry.(*ContentEntry)
|
||
|
if !ok {
|
||
|
return nil, errors.WithStack(ErrInvalidRedirect)
|
||
|
}
|
||
|
|
||
|
return contentEntry, nil
|
||
|
}
|
||
|
|
||
|
func (r *Reader) parseRedirectEntry(offset int64, base *BaseEntry) (*RedirectEntry, error) {
|
||
|
entry := &RedirectEntry{
|
||
|
BaseEntry: base,
|
||
|
}
|
||
|
|
||
|
data := make([]byte, 4)
|
||
|
if err := r.readRange(offset+8, data); err != nil {
|
||
|
return nil, errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
redirectIndex, err := readUint32(data, binary.LittleEndian)
|
||
|
if err != nil {
|
||
|
return nil, errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
entry.redirectIndex = redirectIndex
|
||
|
|
||
|
url, read, err := r.readStringAt(offset + 12)
|
||
|
if err != nil {
|
||
|
return nil, errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
entry.url = url
|
||
|
|
||
|
title, _, err := r.readStringAt(offset + 12 + read)
|
||
|
if err != nil {
|
||
|
return nil, errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
entry.title = title
|
||
|
|
||
|
return entry, nil
|
||
|
}
|
||
|
|
||
|
func toFullURL(ns Namespace, url string) string {
|
||
|
return fmt.Sprintf("%s/%s", ns, url)
|
||
|
}
|