Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mobile toolkit v3 URL helper #139

Merged
merged 15 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading