Skip to content

Commit

Permalink
Add mobile toolkit v3 URL helper (#139)
Browse files Browse the repository at this point in the history
  • Loading branch information
chocolatkey authored Dec 16, 2024
1 parent 812faa3 commit b997dc7
Show file tree
Hide file tree
Showing 83 changed files with 2,986 additions and 1,455 deletions.
64 changes: 29 additions & 35 deletions cmd/rwp/cmd/serve/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/readium/go-toolkit/pkg/manifest"
"github.com/readium/go-toolkit/pkg/pub"
"github.com/readium/go-toolkit/pkg/streamer"
"github.com/readium/go-toolkit/pkg/util/url"
"github.com/zeebo/xxh3"
)

Expand Down Expand Up @@ -67,32 +68,6 @@ func (s *Server) getPublication(filename string) (*pub.Publication, error) {
return nil, errors.Wrap(err, "failed opening "+cp)
}

// TODO: Remove this after we make links relative in the go-toolkit
for i, link := range pub.Manifest.Links {
pub.Manifest.Links[i] = makeRelative(link)
}
for i, link := range pub.Manifest.Resources {
pub.Manifest.Resources[i] = makeRelative(link)
}
for i, link := range pub.Manifest.ReadingOrder {
pub.Manifest.ReadingOrder[i] = makeRelative(link)
}
for i, link := range pub.Manifest.TableOfContents {
pub.Manifest.TableOfContents[i] = makeRelative(link)
}
var makeCollectionRelative func(mp manifest.PublicationCollectionMap)
makeCollectionRelative = func(mp manifest.PublicationCollectionMap) {
for i := range mp {
for j := range mp[i] {
for k := range mp[i][j].Links {
mp[i][j].Links[k] = makeRelative(mp[i][j].Links[k])
}
makeCollectionRelative(mp[i][j].Subcollections)
}
}
}
makeCollectionRelative(pub.Manifest.Subcollections)

// Cache the publication
encPub := &cache.CachedPublication{Publication: pub}
s.lfu.Set(cp, encPub)
Expand Down Expand Up @@ -122,10 +97,19 @@ func (s *Server) getManifest(w http.ResponseWriter, req *http.Request) {
scheme = "https://"
}
rPath, _ := s.router.Get("manifest").URLPath("path", vars["path"])
conformsTo := conformsToAsMimetype(publication.Manifest.Metadata.ConformsTo)

selfUrl, err := url.AbsoluteURLFromString(scheme + req.Host + rPath.String())
if err != nil {
slog.Error("failed creating self URL", "error", err)
w.WriteHeader(500)
return
}

selfLink := &manifest.Link{
Rels: manifest.Strings{"self"},
Type: conformsToAsMimetype(publication.Manifest.Metadata.ConformsTo),
Href: scheme + req.Host + rPath.String(),
Rels: manifest.Strings{"self"},
MediaType: &conformsTo,
Href: manifest.NewHREF(selfUrl),
}

// Marshal the manifest
Expand Down Expand Up @@ -155,7 +139,7 @@ func (s *Server) getManifest(w http.ResponseWriter, req *http.Request) {
}

// Add headers
w.Header().Set("content-type", conformsToAsMimetype(publication.Manifest.Metadata.ConformsTo)+"; charset=utf-8")
w.Header().Set("content-type", conformsTo.String()+"; charset=utf-8")
w.Header().Set("cache-control", "private, must-revalidate")
w.Header().Set("access-control-allow-origin", "*") // TODO: provide options?

Expand Down Expand Up @@ -190,18 +174,28 @@ func (s *Server) getAsset(w http.ResponseWriter, r *http.Request) {
return
}

// Parse asset path from mux vars
href, err := url.URLFromDecodedPath(path.Clean(vars["asset"]))
if err != nil {
slog.Error("failed parsing asset path as URL", "error", err)
w.WriteHeader(400)
return
}
rawHref := href.Raw()
rawHref.RawQuery = r.URL.Query().Encode() // Add the query parameters of the URL
href, _ = url.RelativeURLFromGo(rawHref) // Turn it back into a go-toolkit relative URL

// Make sure the asset exists in the publication
href := path.Clean(vars["asset"])
link := publication.Find(href)
link := publication.LinkWithHref(href)
if link == nil {
w.WriteHeader(http.StatusNotFound)
return
}
finalLink := *link

// Expand templated links to include URL query parameters
if finalLink.Templated {
finalLink = finalLink.ExpandTemplate(convertURLValuesToMap(r.URL.Query()))
if finalLink.Href.IsTemplated() {
finalLink.Href = manifest.NewHREF(finalLink.URL(nil, convertURLValuesToMap(r.URL.Query())))
}

// Get the asset from the publication
Expand All @@ -217,7 +211,7 @@ func (s *Server) getAsset(w http.ResponseWriter, r *http.Request) {
}

// Patch mimetype where necessary
contentType := link.MediaType().String()
contentType := link.MediaType.String()
if sub, ok := mimeSubstitutions[contentType]; ok {
contentType = sub
}
Expand Down
16 changes: 4 additions & 12 deletions cmd/rwp/cmd/serve/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,13 @@ var compressableMimes = []string{
"application/vnd.ms-fontobject",
}

func makeRelative(link manifest.Link) manifest.Link {
link.Href = strings.TrimPrefix(link.Href, "/")
for i, alt := range link.Alternates {
link.Alternates[i].Href = strings.TrimPrefix(alt.Href, "/")
}
return link
}

func conformsToAsMimetype(conformsTo manifest.Profiles) string {
mime := mediatype.ReadiumWebpubManifest.String()
func conformsToAsMimetype(conformsTo manifest.Profiles) mediatype.MediaType {
mime := mediatype.ReadiumWebpubManifest
for _, profile := range conformsTo {
if profile == manifest.ProfileDivina {
mime = mediatype.ReadiumDivinaManifest.String()
mime = mediatype.ReadiumDivinaManifest
} else if profile == manifest.ProfileAudiobook {
mime = mediatype.ReadiumAudiobookManifest.String()
mime = mediatype.ReadiumAudiobookManifest
} else {
continue
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/asset/asset_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ func (a *FileAsset) CreateFetcher(dependencies Dependencies, credentials string)
return nil, err
}
if stat.IsDir() {
return fetcher.NewFileFetcher("/", a.filepath), nil
return fetcher.NewFileFetcher("", a.filepath), nil
} else {
af, err := fetcher.NewArchiveFetcherFromPathWithFactory(a.filepath, dependencies.ArchiveFactory)
if err == nil {
return af, nil
}
// logrus.Warnf("couldn't open %s as archive: %v", a.filepath, err)
return fetcher.NewFileFetcher("/"+a.Name(), a.filepath), nil

return fetcher.NewFileFetcher(a.Name(), a.filepath), nil
}
}
3 changes: 0 additions & 3 deletions pkg/content/element/element.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ func (e AudioElement) Locator() manifest.Locator {

// Implements EmbeddedElement
func (e AudioElement) EmbeddedLink() manifest.Link {
e.embeddedLink.Href = strings.TrimPrefix(e.embeddedLink.Href, "/")
return e.embeddedLink
}

Expand Down Expand Up @@ -115,7 +114,6 @@ func (e VideoElement) Locator() manifest.Locator {

// Implements EmbeddedElement
func (e VideoElement) EmbeddedLink() manifest.Link {
e.embeddedLink.Href = strings.TrimPrefix(e.embeddedLink.Href, "/")
return e.embeddedLink
}

Expand Down Expand Up @@ -158,7 +156,6 @@ func (e ImageElement) Locator() manifest.Locator {

// Implements EmbeddedElement
func (e ImageElement) EmbeddedLink() manifest.Link {
e.embeddedLink.Href = strings.TrimPrefix(e.embeddedLink.Href, "/")
return e.embeddedLink
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/content/iterator/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func NewHTML(resource fetcher.Resource, locator manifest.Locator) *HTMLContentIt

func HTMLFactory() ResourceContentIteratorFactory {
return func(resource fetcher.Resource, locator manifest.Locator) Iterator {
if resource.Link().MediaType().Matches(&mediatype.HTML, &mediatype.XHTML) {
if resource.Link().MediaType.Matches(&mediatype.HTML, &mediatype.XHTML) {
return NewHTML(resource, locator)
}
return nil
Expand Down Expand Up @@ -129,20 +129,20 @@ func (it *HTMLContentIterator) elements() (*ParsedElements, error) {
func (it *HTMLContentIterator) parseElements() (*ParsedElements, error) {
raw, rerr := it.resource.ReadAsString()
if rerr != nil {
return nil, errors.Wrap(rerr, "failed reading HTML string of "+it.resource.Link().Href)
return nil, errors.Wrap(rerr, "failed reading HTML string of "+it.resource.Link().Href.String())
}

document, err := html.ParseWithOptions(
strings.NewReader(raw),
html.ParseOptionEnableScripting(false),
)
if err != nil {
return nil, errors.Wrap(err, "failed parsing HTML of "+it.resource.Link().Href)
return nil, errors.Wrap(err, "failed parsing HTML of "+it.resource.Link().Href.String())
}

body := childOfType(document, atom.Body, true)
if body == nil {
return nil, errors.New("HTML of " + it.resource.Link().Href + " doesn't have a <body>")
return nil, errors.New("HTML of " + it.resource.Link().Href.String() + " doesn't have a <body>")
}

contentConverter := HTMLConverter{
Expand Down
44 changes: 24 additions & 20 deletions pkg/content/iterator/html_converter.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package iterator

import (
"net/url"
nurl "net/url"
"strings"
"unicode"
"unicode/utf8"

"github.com/readium/go-toolkit/pkg/content/element"
iutil "github.com/readium/go-toolkit/pkg/internal/util"
"github.com/readium/go-toolkit/pkg/manifest"
"github.com/readium/go-toolkit/pkg/util"
"github.com/readium/go-toolkit/pkg/mediatype"
"github.com/readium/go-toolkit/pkg/util/url"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
Expand Down Expand Up @@ -72,14 +73,15 @@ func getAttr(n *html.Node, key string) string {
return ""
}

func srcRelativeToHref(n *html.Node, base string) *string {
func srcRelativeToHref(n *html.Node, base url.URL) url.URL {
if n == nil {
return nil
}

if v := getAttr(n, "src"); v != "" {
h, _ := util.NewHREF(v, base).String()
return &h
if u, _ := url.URLFromString(v); u != nil {
return base.Resolve(u)
}
}
return nil
}
Expand Down Expand Up @@ -336,10 +338,10 @@ func (c *HTMLConverter) Head(n *html.Node, depth int) {
cssSelector = &cs
}
elementLocator := manifest.Locator{
Href: c.baseLocator.Href,
Type: c.baseLocator.Type,
Title: c.baseLocator.Title,
Text: c.baseLocator.Text,
Href: c.baseLocator.Href,
MediaType: c.baseLocator.MediaType,
Title: c.baseLocator.Title,
Text: c.baseLocator.Text,
Locations: manifest.Locations{
OtherLocations: map[string]interface{}{
"cssSelector": cssSelector,
Expand All @@ -361,7 +363,7 @@ func (c *HTMLConverter) Head(n *html.Node, depth int) {
c.elements = append(c.elements, element.NewImageElement(
elementLocator,
manifest.Link{
Href: *href,
Href: manifest.NewHREF(href),
},
"", // FIXME: Get the caption from figcaption
atlist,
Expand All @@ -372,18 +374,20 @@ func (c *HTMLConverter) Head(n *html.Node, depth int) {
var link *manifest.Link
if href != nil {
link = &manifest.Link{
Href: *href,
Href: manifest.NewHREF(href),
}
} else {
sourceNodes := childrenOfType(n, atom.Source, 1)
sources := make([]manifest.Link, len(sourceNodes))
for _, source := range sourceNodes {
if src := srcRelativeToHref(source, c.baseLocator.Href); src != nil {
l := manifest.Link{
Href: *src,
Href: manifest.NewHREF(href),
}
if typ := getAttr(source, "type"); typ != "" {
l.Type = typ
if mt, err := mediatype.NewOfString(typ); err == nil {
l.MediaType = &mt
}
}
sources = append(sources, l)
}
Expand Down Expand Up @@ -495,7 +499,7 @@ func (c *HTMLConverter) flushText() {
quote := element.Quote{}
for _, at := range el.Attr {
if at.Key == "cite" {
quote.ReferenceURL, _ = url.Parse(at.Val)
quote.ReferenceURL, _ = nurl.Parse(at.Val)
}
if at.Key == "title" {
quote.ReferenceTitle = at.Val
Expand All @@ -512,9 +516,9 @@ func (c *HTMLConverter) flushText() {
}
el := element.NewTextElement(
manifest.Locator{
Href: c.baseLocator.Href,
Type: c.baseLocator.Type,
Title: c.baseLocator.Title,
Href: c.baseLocator.Href,
MediaType: c.baseLocator.MediaType,
Title: c.baseLocator.Title,
Locations: manifest.Locations{
OtherLocations: map[string]interface{}{},
},
Expand Down Expand Up @@ -563,9 +567,9 @@ func (c *HTMLConverter) flushSegment() {
}
seg := element.TextSegment{
Locator: manifest.Locator{
Href: c.baseLocator.Href,
Type: c.baseLocator.Type,
Title: c.baseLocator.Title,
Href: c.baseLocator.Href,
MediaType: c.baseLocator.MediaType,
Title: c.baseLocator.Title,
Locations: manifest.Locations{
// TODO fix: needs to use baseLocator locations too!
OtherLocations: map[string]interface{}{},
Expand Down
12 changes: 6 additions & 6 deletions pkg/fetcher/fetcher_archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"io"
"path"
"strings"

"github.com/readium/go-toolkit/pkg/archive"
"github.com/readium/go-toolkit/pkg/manifest"
Expand All @@ -23,17 +22,18 @@ func (f *ArchiveFetcher) Links() (manifest.LinkList, error) {
links := make(manifest.LinkList, 0, len(entries))
for _, af := range entries {
fp := path.Clean(af.Path())
if !strings.HasPrefix(fp, "/") {
fp = "/" + fp
href, err := manifest.NewHREFFromString(fp, false)
if err != nil {
return nil, err
}
link := manifest.Link{
Href: fp,
Href: href,
}
ext := path.Ext(fp)
if ext != "" {
mt := mediatype.OfExtension(ext[1:]) // Remove leading "."
if mt != nil {
link.Type = mt.String()
link.MediaType = mt
}
}
links = append(links, link)
Expand All @@ -43,7 +43,7 @@ func (f *ArchiveFetcher) Links() (manifest.LinkList, error) {

// Get implements Fetcher
func (f *ArchiveFetcher) Get(link manifest.Link) Resource {
entry, err := f.archive.Entry(strings.TrimPrefix(link.Href, "/"))
entry, err := f.archive.Entry(link.Href.String())
if err != nil {
return NewFailureResource(link, NotFound(err))
}
Expand Down
Loading

0 comments on commit b997dc7

Please sign in to comment.