Skip to content

Commit c250ce3

Browse files
authored
fix(libp2phttp): Fix relative to absolute multiaddr URI logic (#3208)
1 parent 93014e1 commit c250ce3

File tree

2 files changed

+75
-31
lines changed

2 files changed

+75
-31
lines changed

p2p/http/libp2phttp.go

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -523,19 +523,18 @@ func (rt *streamRoundTripper) RoundTrip(r *http.Request) (*http.Response, error)
523523
}
524524
resp.Body = &streamReadCloser{resp.Body, s}
525525

526-
locUrl, err := resp.Location()
527-
if err == nil {
528-
// Location url in response. Is this a multiaddr uri? and is it relative?
529-
// If it's relative we want to convert it to an absolute multiaddr uri
530-
// so that the next request knows how to reach the endpoint.
531-
if locUrl.Scheme == "multiaddr" && resp.Request.URL.Scheme == "multiaddr" {
532-
// Check if it's a relative URI and turn it into an absolute one
533-
u, err := relativeMultiaddrURIToAbs(resp.Request.URL, locUrl)
534-
if err == nil {
535-
// It was a relative URI and we were able to convert it to an absolute one
536-
// Update the location header to be an absolute multiaddr uri
537-
resp.Header.Set("Location", u.String())
526+
if r.URL.Scheme == "multiaddr" {
527+
// This was a multiaddr uri, we may need to convert relative URI
528+
// references to absolute multiaddr ones so that the next request
529+
// knows how to reach the endpoint.
530+
locationHeader := resp.Header.Get("Location")
531+
if locationHeader != "" {
532+
u, err := locationHeaderToMultiaddrURI(r.URL, locationHeader)
533+
if err != nil {
534+
return nil, fmt.Errorf("failed to convert location header (%s) from request (%s) to multiaddr uri: %w", locationHeader, r.URL, err)
538535
}
536+
// Update the location header to be an absolute multiaddr uri
537+
resp.Header.Set("Location", u.String())
539538
}
540539
}
541540

@@ -544,39 +543,68 @@ func (rt *streamRoundTripper) RoundTrip(r *http.Request) (*http.Response, error)
544543
return resp, nil
545544
}
546545

547-
var errNotRelative = errors.New("not relative")
548-
549-
// relativeMultiaddrURIToAbs takes a relative multiaddr URI and turns it into an
550-
// absolute one. Useful, for example, when a server gives us a relative URI for a redirect.
551-
// It allows the following request (the one after redirected) to reach the correct server.
552-
func relativeMultiaddrURIToAbs(original *url.URL, relative *url.URL) (*url.URL, error) {
553-
// Is this a relative uri? We know if it is because non-relative URI's of the form:
554-
// "multiaddr:/ip4/1.2.3.4/tcp/9899" when parsed by Go's url package will have url.OmitHost == true
555-
// But if it is relative (just a path to an http resource e.g. /here-instead)
556-
// a redirect will inherit the multiaddr scheme, but set url.OmitHost == false. It will also stringify as something like
557-
// multiaddr://here-instead.
558-
if relative.OmitHost {
559-
// Not relative (at least we can't tell). Nothing we can do here
560-
return nil, errNotRelative
546+
// locationHeaderToMultiaddrURI takes our original URL and the response's Location header
547+
// and, if the location header is relative, turns it into an absolute multiaddr uri.
548+
// Refer to https://www.rfc-editor.org/rfc/rfc3986#section-4.2 for the
549+
// definition of a Relative Reference.
550+
func locationHeaderToMultiaddrURI(original *url.URL, locationHeader string) (*url.URL, error) {
551+
if locationHeader == "" {
552+
return nil, errors.New("location header is empty")
553+
}
554+
if strings.HasPrefix(locationHeader, "//") {
555+
// This is a network path reference. We don't support these.
556+
return nil, errors.New("network path reference not supported")
557+
}
558+
559+
firstSegment := strings.SplitN(locationHeader, "/", 2)[0]
560+
if strings.Contains(firstSegment, ":") {
561+
// This location contains a scheme, so it's an absolute uri.
562+
return url.Parse(locationHeader)
563+
}
564+
565+
// It's a relative reference. We need to resolve it against the original URL.
566+
if original.Scheme != "multiaddr" {
567+
return nil, errors.New("original uri is not a multiaddr")
561568
}
569+
570+
// Parse the original multiaddr
562571
originalStr := original.RawPath
563572
if originalStr == "" {
564573
originalStr = original.Path
565574
}
566575
originalMa, err := ma.NewMultiaddr(originalStr)
567576
if err != nil {
568-
return nil, errors.New("original uri is not a multiaddr")
577+
return nil, fmt.Errorf("original uri is not a valid multiaddr: %w", err)
578+
}
579+
580+
// Get the target http path
581+
var targetHTTPPath string
582+
for _, c := range originalMa {
583+
if c.Protocol().Code == ma.P_HTTP_PATH {
584+
targetHTTPPath = string(c.RawValue())
585+
break
586+
}
587+
}
588+
589+
// Resolve reference from targetURL and relativeURL
590+
targetURL := url.URL{Path: targetHTTPPath}
591+
relativeURL := url.URL{Path: locationHeader}
592+
resolved := targetURL.ResolveReference(&relativeURL)
593+
594+
resolvedHTTPPath := resolved.Path
595+
if len(resolvedHTTPPath) > 0 && resolvedHTTPPath[0] == '/' {
596+
resolvedHTTPPath = resolvedHTTPPath[1:] // trim leading slash. It's implied by the http-path component
569597
}
570598

571-
relativePathComponent, err := ma.NewComponent("http-path", relative.Path)
599+
resolvedHTTPPathComponent, err := ma.NewComponent("http-path", resolvedHTTPPath)
572600
if err != nil {
573-
return nil, errors.New("relative path is not a valid http-path")
601+
return nil, fmt.Errorf("relative path is not a valid http-path: %w", err)
574602
}
575603

576604
withoutPath, afterAndIncludingPath := ma.SplitFunc(originalMa, func(c ma.Component) bool {
577605
return c.Protocol().Code == ma.P_HTTP_PATH
578606
})
579-
withNewPath := withoutPath.AppendComponent(relativePathComponent)
607+
withNewPath := withoutPath.AppendComponent(resolvedHTTPPathComponent)
580608
if len(afterAndIncludingPath) > 1 {
581609
// Include after path since it may include other parts
582610
withNewPath = append(withNewPath, afterAndIncludingPath[1:]...)

p2p/http/libp2phttp_test.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,14 @@ func TestRedirects(t *testing.T) {
863863
w.Write([]byte("hello"))
864864
}))
865865

866+
serverHttpHost.SetHTTPHandlerAtPath("/redirect-1/0.0.1", "/foo/bar/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
867+
w.Header().Set("Location", "../baz/")
868+
w.WriteHeader(http.StatusMovedPermanently)
869+
}))
870+
serverHttpHost.SetHTTPHandlerAtPath("/redirect-1/0.0.1", "/foo/baz/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
871+
w.Write([]byte("hello"))
872+
}))
873+
866874
clientStreamHost, err := libp2p.New(libp2p.NoListenAddrs, libp2p.Transport(libp2pquic.NewTransport))
867875
require.NoError(t, err)
868876
client := http.Client{Transport: &libp2phttp.Host{StreamHost: clientStreamHost}}
@@ -879,9 +887,17 @@ func TestRedirects(t *testing.T) {
879887
u := fmt.Sprintf("multiaddr:%s/http-path/a%%2f", a)
880888
f := fmt.Sprintf("http://127.0.0.1:%s/d/", port)
881889
testCases = append(testCases, testCase{u, f})
890+
891+
u = fmt.Sprintf("multiaddr:%s/http-path/foo%%2Fbar", a)
892+
f = fmt.Sprintf("http://127.0.0.1:%s/foo/baz/", port)
893+
testCases = append(testCases, testCase{u, f})
882894
} else {
883895
u := fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/a%%2f", a, serverHost.ID())
884-
f := fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/%%2Fd%%2F", a, serverHost.ID())
896+
f := fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/d%%2F", a, serverHost.ID())
897+
testCases = append(testCases, testCase{u, f})
898+
899+
u = fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/foo%%2Fbar", a, serverHost.ID())
900+
f = fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/foo%%2Fbaz%%2F", a, serverHost.ID())
885901
testCases = append(testCases, testCase{u, f})
886902
}
887903
}

0 commit comments

Comments
 (0)