Skip to content

Commit

Permalink
rework templated URI logic to fix services with query params
Browse files Browse the repository at this point in the history
  • Loading branch information
chocolatkey committed Dec 9, 2024
1 parent 37b495e commit 9817353
Show file tree
Hide file tree
Showing 12 changed files with 660 additions and 177 deletions.
5 changes: 4 additions & 1 deletion cmd/rwp/cmd/serve/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,16 @@ func (s *Server) getAsset(w http.ResponseWriter, r *http.Request) {
return
}

// Parse asset path
// 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
link := publication.LinkWithHref(href)
Expand Down
20 changes: 14 additions & 6 deletions pkg/manifest/href.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package manifest

import (
"github.com/readium/go-toolkit/pkg/util/url"
"github.com/readium/go-toolkit/pkg/util/url/uritemplates"
)

// An hypertext reference points to a resource in a [Publication].
Expand Down Expand Up @@ -31,7 +32,11 @@ func MustNewHREFFromString(href string, templated bool) HREF {
func NewHREFFromString(href string, templated bool) (HREF, error) {
if templated {
// Check that the produced URL is valid
_, err := url.URLFromString(url.NewURITemplate(href).Expand(map[string]string{}))
eurl, _, err := uritemplates.Expand(href, nil)
if err != nil {
return HREF{}, err
}
_, err = url.URLFromString(eurl)
if err != nil {
return HREF{}, err
}
Expand All @@ -51,11 +56,13 @@ func NewHREFFromString(href string, templated bool) (HREF, error) {
// If the HREF is a template, the [parameters] are used to expand it according to RFC 6570.
func (h HREF) Resolve(base url.URL, parameters map[string]string) url.URL {
if h.IsTemplated() {
u, err := url.URLFromString(
url.NewURITemplate(h.template).Expand(parameters),
)
exp, _, err := uritemplates.Expand(h.template, parameters)
if err != nil {
panic("Invalid URL template expansion: " + err.Error())
}
u, err := url.URLFromString(exp)
if err != nil {
panic("Invalid URL template expansion")
panic("Invalid URL template expansion: " + err.Error())
}
if base == nil {
return u
Expand All @@ -77,7 +84,8 @@ func (h HREF) IsTemplated() bool {
// List of URI template parameter keys, if the HREF is templated.
func (h HREF) Parameters() []string {
if h.IsTemplated() {
return url.NewURITemplate(h.template).Parameters()
v, _ := uritemplates.Values(h.template)
return v
}
return []string{}
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/manifest/href_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ func TestConvertTemplatedHREFToURL(t *testing.T) {
"foo": "bar",
}

u, _ := url.URLFromString("url?x=&hello=&y=name")
u, _ := url.URLFromString("urlname")
assert.Equal(t, u, template.Resolve(nil, nil))

u, _ = url.URLFromString("http://readium/publication/url?x=&hello=&y=name")
u, _ = url.URLFromString("http://readium/publication/urlname")
assert.Equal(t, u, template.Resolve(base, nil))

u, _ = url.URLFromString("http://readium/publication/url?x=aaa&hello=Hello,%20world&y=bname")
u, _ = url.URLFromString("http://readium/publication/url?x=aaa&hello=Hello%2C%20world&y=bname")
assert.Equal(t, u, template.Resolve(base, parameters))
}

Expand Down
66 changes: 65 additions & 1 deletion pkg/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package manifest

import (
"encoding/json"
"slices"

"github.com/pkg/errors"
"github.com/readium/go-toolkit/pkg/internal/extensions"
Expand Down Expand Up @@ -56,12 +57,75 @@ func (m Manifest) ConformsTo(profile Profile) bool {
// Searches through (in order) the reading order, resources and links recursively following alternate and children links.
// If there's no match, tries again after removing any query parameter and anchor from the given href.
func (m Manifest) LinkWithHref(href url.URL) *Link {
href = href.Normalize() // Normalize HREF here instead of in the loop

var deepLinkWithHref func(ll LinkList, href url.URL) *Link
deepLinkWithHref = func(ll LinkList, href url.URL) *Link {
for _, l := range ll {
if l.URL(nil, nil).Equivalent(href) {
nu := l.URL(nil, nil).Normalize() // Normalized version of the href

if nu.Equivalent(href) {
// Exactly equivalent after normalization
return &l
} else {
// Check if they have the same relative path after resolving,
// and no fragment, meaning only the query could be different
if nu.Path() == href.Path() && href.Fragment() == "" {
// Check for a possible fit in a templated href
// This is a special fast path for web services accepting arbitrary query parameters in the URL
if l.Href.IsTemplated() { // Templated URI
if params := l.Href.Parameters(); len(params) > 0 {
// At least one parameter in the URI template
matches := true

// Check that every parameter in the URI template is present by key in the query
for _, p := range params {
if !href.Query().Has(p) {
matches = false
break
}
}
if matches {
// All template parameters are present in the query parameters
return &l
}
}
} else {
// Check for a possible fit in an href with query parameters
// This is a special fast path for web services accepting arbitrary query parameters in the URL
if len(nu.Query()) > 0 && len(href.Query()) > 0 {
// Both the give href and the one we're checking have query parameters
// If the given href has all the key/value pairs in the query that the
// one we're checking has, then they're equivalent!
matches := true
q := href.Query()
for k, v := range nu.Query() {
slices.Sort(v)
if qv, ok := q[k]; ok {
if len(qv) > 1 {
slices.Sort(qv)
if !slices.Equal(qv, v) {
matches = false
break
}
} else {
if qv[0] != v[0] {
matches = false
break
}
}
} else {
matches = false
break
}
}
if matches {
return &l
}
}
}
}

if link := deepLinkWithHref(l.Alternates, href); link != nil {
return link
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/pub/service_guided_navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type GuidedNavigationService interface {
func GetForGuidedNavigationService(service GuidedNavigationService, link manifest.Link) (fetcher.Resource, bool) {
u := link.URL(nil, nil)

if !u.Equivalent(resolvedGuidedNavigation) {
if u.Path() != resolvedGuidedNavigation.Path() {
// Not the guided navigation link
return nil, false
}
Expand Down
110 changes: 0 additions & 110 deletions pkg/util/url/uri_template.go

This file was deleted.

55 changes: 0 additions & 55 deletions pkg/util/url/uri_template_test.go

This file was deleted.

2 changes: 2 additions & 0 deletions pkg/util/url/uritemplates/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Copied from https://github.com/googleapis/google-api-go-client/tree/main/internal/third_party/uritemplates
Modified to have a Values function
Loading

0 comments on commit 9817353

Please sign in to comment.