Skip to content

Commit b997dc7

Browse files
authored
Add mobile toolkit v3 URL helper (#139)
1 parent 812faa3 commit b997dc7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+2986
-1455
lines changed

cmd/rwp/cmd/serve/api.go

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/readium/go-toolkit/pkg/manifest"
2525
"github.com/readium/go-toolkit/pkg/pub"
2626
"github.com/readium/go-toolkit/pkg/streamer"
27+
"github.com/readium/go-toolkit/pkg/util/url"
2728
"github.com/zeebo/xxh3"
2829
)
2930

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

70-
// TODO: Remove this after we make links relative in the go-toolkit
71-
for i, link := range pub.Manifest.Links {
72-
pub.Manifest.Links[i] = makeRelative(link)
73-
}
74-
for i, link := range pub.Manifest.Resources {
75-
pub.Manifest.Resources[i] = makeRelative(link)
76-
}
77-
for i, link := range pub.Manifest.ReadingOrder {
78-
pub.Manifest.ReadingOrder[i] = makeRelative(link)
79-
}
80-
for i, link := range pub.Manifest.TableOfContents {
81-
pub.Manifest.TableOfContents[i] = makeRelative(link)
82-
}
83-
var makeCollectionRelative func(mp manifest.PublicationCollectionMap)
84-
makeCollectionRelative = func(mp manifest.PublicationCollectionMap) {
85-
for i := range mp {
86-
for j := range mp[i] {
87-
for k := range mp[i][j].Links {
88-
mp[i][j].Links[k] = makeRelative(mp[i][j].Links[k])
89-
}
90-
makeCollectionRelative(mp[i][j].Subcollections)
91-
}
92-
}
93-
}
94-
makeCollectionRelative(pub.Manifest.Subcollections)
95-
9671
// Cache the publication
9772
encPub := &cache.CachedPublication{Publication: pub}
9873
s.lfu.Set(cp, encPub)
@@ -122,10 +97,19 @@ func (s *Server) getManifest(w http.ResponseWriter, req *http.Request) {
12297
scheme = "https://"
12398
}
12499
rPath, _ := s.router.Get("manifest").URLPath("path", vars["path"])
100+
conformsTo := conformsToAsMimetype(publication.Manifest.Metadata.ConformsTo)
101+
102+
selfUrl, err := url.AbsoluteURLFromString(scheme + req.Host + rPath.String())
103+
if err != nil {
104+
slog.Error("failed creating self URL", "error", err)
105+
w.WriteHeader(500)
106+
return
107+
}
108+
125109
selfLink := &manifest.Link{
126-
Rels: manifest.Strings{"self"},
127-
Type: conformsToAsMimetype(publication.Manifest.Metadata.ConformsTo),
128-
Href: scheme + req.Host + rPath.String(),
110+
Rels: manifest.Strings{"self"},
111+
MediaType: &conformsTo,
112+
Href: manifest.NewHREF(selfUrl),
129113
}
130114

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

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

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

177+
// Parse asset path from mux vars
178+
href, err := url.URLFromDecodedPath(path.Clean(vars["asset"]))
179+
if err != nil {
180+
slog.Error("failed parsing asset path as URL", "error", err)
181+
w.WriteHeader(400)
182+
return
183+
}
184+
rawHref := href.Raw()
185+
rawHref.RawQuery = r.URL.Query().Encode() // Add the query parameters of the URL
186+
href, _ = url.RelativeURLFromGo(rawHref) // Turn it back into a go-toolkit relative URL
187+
193188
// Make sure the asset exists in the publication
194-
href := path.Clean(vars["asset"])
195-
link := publication.Find(href)
189+
link := publication.LinkWithHref(href)
196190
if link == nil {
197191
w.WriteHeader(http.StatusNotFound)
198192
return
199193
}
200194
finalLink := *link
201195

202196
// Expand templated links to include URL query parameters
203-
if finalLink.Templated {
204-
finalLink = finalLink.ExpandTemplate(convertURLValuesToMap(r.URL.Query()))
197+
if finalLink.Href.IsTemplated() {
198+
finalLink.Href = manifest.NewHREF(finalLink.URL(nil, convertURLValuesToMap(r.URL.Query())))
205199
}
206200

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

219213
// Patch mimetype where necessary
220-
contentType := link.MediaType().String()
214+
contentType := link.MediaType.String()
221215
if sub, ok := mimeSubstitutions[contentType]; ok {
222216
contentType = sub
223217
}

cmd/rwp/cmd/serve/helpers.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,13 @@ var compressableMimes = []string{
5151
"application/vnd.ms-fontobject",
5252
}
5353

54-
func makeRelative(link manifest.Link) manifest.Link {
55-
link.Href = strings.TrimPrefix(link.Href, "/")
56-
for i, alt := range link.Alternates {
57-
link.Alternates[i].Href = strings.TrimPrefix(alt.Href, "/")
58-
}
59-
return link
60-
}
61-
62-
func conformsToAsMimetype(conformsTo manifest.Profiles) string {
63-
mime := mediatype.ReadiumWebpubManifest.String()
54+
func conformsToAsMimetype(conformsTo manifest.Profiles) mediatype.MediaType {
55+
mime := mediatype.ReadiumWebpubManifest
6456
for _, profile := range conformsTo {
6557
if profile == manifest.ProfileDivina {
66-
mime = mediatype.ReadiumDivinaManifest.String()
58+
mime = mediatype.ReadiumDivinaManifest
6759
} else if profile == manifest.ProfileAudiobook {
68-
mime = mediatype.ReadiumAudiobookManifest.String()
60+
mime = mediatype.ReadiumAudiobookManifest
6961
} else {
7062
continue
7163
}

pkg/asset/asset_file.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ func (a *FileAsset) CreateFetcher(dependencies Dependencies, credentials string)
7070
return nil, err
7171
}
7272
if stat.IsDir() {
73-
return fetcher.NewFileFetcher("/", a.filepath), nil
73+
return fetcher.NewFileFetcher("", a.filepath), nil
7474
} else {
7575
af, err := fetcher.NewArchiveFetcherFromPathWithFactory(a.filepath, dependencies.ArchiveFactory)
7676
if err == nil {
7777
return af, nil
7878
}
79-
// logrus.Warnf("couldn't open %s as archive: %v", a.filepath, err)
80-
return fetcher.NewFileFetcher("/"+a.Name(), a.filepath), nil
79+
80+
return fetcher.NewFileFetcher(a.Name(), a.filepath), nil
8181
}
8282
}

pkg/content/element/element.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ func (e AudioElement) Locator() manifest.Locator {
7474

7575
// Implements EmbeddedElement
7676
func (e AudioElement) EmbeddedLink() manifest.Link {
77-
e.embeddedLink.Href = strings.TrimPrefix(e.embeddedLink.Href, "/")
7877
return e.embeddedLink
7978
}
8079

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

116115
// Implements EmbeddedElement
117116
func (e VideoElement) EmbeddedLink() manifest.Link {
118-
e.embeddedLink.Href = strings.TrimPrefix(e.embeddedLink.Href, "/")
119117
return e.embeddedLink
120118
}
121119

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

159157
// Implements EmbeddedElement
160158
func (e ImageElement) EmbeddedLink() manifest.Link {
161-
e.embeddedLink.Href = strings.TrimPrefix(e.embeddedLink.Href, "/")
162159
return e.embeddedLink
163160
}
164161

pkg/content/iterator/html.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func NewHTML(resource fetcher.Resource, locator manifest.Locator) *HTMLContentIt
3636

3737
func HTMLFactory() ResourceContentIteratorFactory {
3838
return func(resource fetcher.Resource, locator manifest.Locator) Iterator {
39-
if resource.Link().MediaType().Matches(&mediatype.HTML, &mediatype.XHTML) {
39+
if resource.Link().MediaType.Matches(&mediatype.HTML, &mediatype.XHTML) {
4040
return NewHTML(resource, locator)
4141
}
4242
return nil
@@ -129,20 +129,20 @@ func (it *HTMLContentIterator) elements() (*ParsedElements, error) {
129129
func (it *HTMLContentIterator) parseElements() (*ParsedElements, error) {
130130
raw, rerr := it.resource.ReadAsString()
131131
if rerr != nil {
132-
return nil, errors.Wrap(rerr, "failed reading HTML string of "+it.resource.Link().Href)
132+
return nil, errors.Wrap(rerr, "failed reading HTML string of "+it.resource.Link().Href.String())
133133
}
134134

135135
document, err := html.ParseWithOptions(
136136
strings.NewReader(raw),
137137
html.ParseOptionEnableScripting(false),
138138
)
139139
if err != nil {
140-
return nil, errors.Wrap(err, "failed parsing HTML of "+it.resource.Link().Href)
140+
return nil, errors.Wrap(err, "failed parsing HTML of "+it.resource.Link().Href.String())
141141
}
142142

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

148148
contentConverter := HTMLConverter{

pkg/content/iterator/html_converter.go

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package iterator
22

33
import (
4-
"net/url"
4+
nurl "net/url"
55
"strings"
66
"unicode"
77
"unicode/utf8"
88

99
"github.com/readium/go-toolkit/pkg/content/element"
1010
iutil "github.com/readium/go-toolkit/pkg/internal/util"
1111
"github.com/readium/go-toolkit/pkg/manifest"
12-
"github.com/readium/go-toolkit/pkg/util"
12+
"github.com/readium/go-toolkit/pkg/mediatype"
13+
"github.com/readium/go-toolkit/pkg/util/url"
1314
"golang.org/x/net/html"
1415
"golang.org/x/net/html/atom"
1516
)
@@ -72,14 +73,15 @@ func getAttr(n *html.Node, key string) string {
7273
return ""
7374
}
7475

75-
func srcRelativeToHref(n *html.Node, base string) *string {
76+
func srcRelativeToHref(n *html.Node, base url.URL) url.URL {
7677
if n == nil {
7778
return nil
7879
}
7980

8081
if v := getAttr(n, "src"); v != "" {
81-
h, _ := util.NewHREF(v, base).String()
82-
return &h
82+
if u, _ := url.URLFromString(v); u != nil {
83+
return base.Resolve(u)
84+
}
8385
}
8486
return nil
8587
}
@@ -336,10 +338,10 @@ func (c *HTMLConverter) Head(n *html.Node, depth int) {
336338
cssSelector = &cs
337339
}
338340
elementLocator := manifest.Locator{
339-
Href: c.baseLocator.Href,
340-
Type: c.baseLocator.Type,
341-
Title: c.baseLocator.Title,
342-
Text: c.baseLocator.Text,
341+
Href: c.baseLocator.Href,
342+
MediaType: c.baseLocator.MediaType,
343+
Title: c.baseLocator.Title,
344+
Text: c.baseLocator.Text,
343345
Locations: manifest.Locations{
344346
OtherLocations: map[string]interface{}{
345347
"cssSelector": cssSelector,
@@ -361,7 +363,7 @@ func (c *HTMLConverter) Head(n *html.Node, depth int) {
361363
c.elements = append(c.elements, element.NewImageElement(
362364
elementLocator,
363365
manifest.Link{
364-
Href: *href,
366+
Href: manifest.NewHREF(href),
365367
},
366368
"", // FIXME: Get the caption from figcaption
367369
atlist,
@@ -372,18 +374,20 @@ func (c *HTMLConverter) Head(n *html.Node, depth int) {
372374
var link *manifest.Link
373375
if href != nil {
374376
link = &manifest.Link{
375-
Href: *href,
377+
Href: manifest.NewHREF(href),
376378
}
377379
} else {
378380
sourceNodes := childrenOfType(n, atom.Source, 1)
379381
sources := make([]manifest.Link, len(sourceNodes))
380382
for _, source := range sourceNodes {
381383
if src := srcRelativeToHref(source, c.baseLocator.Href); src != nil {
382384
l := manifest.Link{
383-
Href: *src,
385+
Href: manifest.NewHREF(href),
384386
}
385387
if typ := getAttr(source, "type"); typ != "" {
386-
l.Type = typ
388+
if mt, err := mediatype.NewOfString(typ); err == nil {
389+
l.MediaType = &mt
390+
}
387391
}
388392
sources = append(sources, l)
389393
}
@@ -495,7 +499,7 @@ func (c *HTMLConverter) flushText() {
495499
quote := element.Quote{}
496500
for _, at := range el.Attr {
497501
if at.Key == "cite" {
498-
quote.ReferenceURL, _ = url.Parse(at.Val)
502+
quote.ReferenceURL, _ = nurl.Parse(at.Val)
499503
}
500504
if at.Key == "title" {
501505
quote.ReferenceTitle = at.Val
@@ -512,9 +516,9 @@ func (c *HTMLConverter) flushText() {
512516
}
513517
el := element.NewTextElement(
514518
manifest.Locator{
515-
Href: c.baseLocator.Href,
516-
Type: c.baseLocator.Type,
517-
Title: c.baseLocator.Title,
519+
Href: c.baseLocator.Href,
520+
MediaType: c.baseLocator.MediaType,
521+
Title: c.baseLocator.Title,
518522
Locations: manifest.Locations{
519523
OtherLocations: map[string]interface{}{},
520524
},
@@ -563,9 +567,9 @@ func (c *HTMLConverter) flushSegment() {
563567
}
564568
seg := element.TextSegment{
565569
Locator: manifest.Locator{
566-
Href: c.baseLocator.Href,
567-
Type: c.baseLocator.Type,
568-
Title: c.baseLocator.Title,
570+
Href: c.baseLocator.Href,
571+
MediaType: c.baseLocator.MediaType,
572+
Title: c.baseLocator.Title,
569573
Locations: manifest.Locations{
570574
// TODO fix: needs to use baseLocator locations too!
571575
OtherLocations: map[string]interface{}{},

pkg/fetcher/fetcher_archive.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"errors"
55
"io"
66
"path"
7-
"strings"
87

98
"github.com/readium/go-toolkit/pkg/archive"
109
"github.com/readium/go-toolkit/pkg/manifest"
@@ -23,17 +22,18 @@ func (f *ArchiveFetcher) Links() (manifest.LinkList, error) {
2322
links := make(manifest.LinkList, 0, len(entries))
2423
for _, af := range entries {
2524
fp := path.Clean(af.Path())
26-
if !strings.HasPrefix(fp, "/") {
27-
fp = "/" + fp
25+
href, err := manifest.NewHREFFromString(fp, false)
26+
if err != nil {
27+
return nil, err
2828
}
2929
link := manifest.Link{
30-
Href: fp,
30+
Href: href,
3131
}
3232
ext := path.Ext(fp)
3333
if ext != "" {
3434
mt := mediatype.OfExtension(ext[1:]) // Remove leading "."
3535
if mt != nil {
36-
link.Type = mt.String()
36+
link.MediaType = mt
3737
}
3838
}
3939
links = append(links, link)
@@ -43,7 +43,7 @@ func (f *ArchiveFetcher) Links() (manifest.LinkList, error) {
4343

4444
// Get implements Fetcher
4545
func (f *ArchiveFetcher) Get(link manifest.Link) Resource {
46-
entry, err := f.archive.Entry(strings.TrimPrefix(link.Href, "/"))
46+
entry, err := f.archive.Entry(link.Href.String())
4747
if err != nil {
4848
return NewFailureResource(link, NotFound(err))
4949
}

0 commit comments

Comments
 (0)