mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-26 15:42:21 +08:00
gateway: add path prefix for directory listings
License: MIT Signed-off-by: Lars Gierth <larsg@systemli.org>
This commit is contained in:
@ -92,16 +92,24 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
urlPath := r.URL.Path
|
urlPath := r.URL.Path
|
||||||
|
|
||||||
|
// If the gateway is behind a reverse proxy and mounted at a sub-path,
|
||||||
|
// the prefix header can be set to signal this sub-path.
|
||||||
|
// It will be prepended to links in directory listings and the index.html redirect.
|
||||||
|
prefix := ""
|
||||||
|
if prefixHdr := r.Header["X-Ipfs-Gateway-Prefix"]; len(prefixHdr) > 0 {
|
||||||
|
log.Debugf("X-Ipfs-Gateway-Prefix: %s", prefixHdr[0])
|
||||||
|
prefix = prefixHdr[0]
|
||||||
|
}
|
||||||
|
|
||||||
// IPNSHostnameOption might have constructed an IPNS path using the Host header.
|
// IPNSHostnameOption might have constructed an IPNS path using the Host header.
|
||||||
// In this case, we need the original path for constructing redirects
|
// In this case, we need the original path for constructing redirects
|
||||||
// and links that match the requested URL.
|
// and links that match the requested URL.
|
||||||
// For example, http://example.net would become /ipns/example.net, and
|
// For example, http://example.net would become /ipns/example.net, and
|
||||||
// the redirects and links would end up as http://example.net/ipns/example.net
|
// the redirects and links would end up as http://example.net/ipns/example.net
|
||||||
originalUrlPath := urlPath
|
originalUrlPath := prefix + urlPath
|
||||||
ipnsHostname := false
|
ipnsHostname := false
|
||||||
hdr := r.Header["X-IPNS-Original-Path"]
|
if hdr := r.Header["X-Ipns-Original-Path"]; len(hdr) > 0 {
|
||||||
if len(hdr) > 0 {
|
originalUrlPath = prefix + hdr[0]
|
||||||
originalUrlPath = hdr[0]
|
|
||||||
ipnsHostname = true
|
ipnsHostname = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +219,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
|||||||
if r.Method != "HEAD" {
|
if r.Method != "HEAD" {
|
||||||
// construct the correct back link
|
// construct the correct back link
|
||||||
// https://github.com/ipfs/go-ipfs/issues/1365
|
// https://github.com/ipfs/go-ipfs/issues/1365
|
||||||
var backLink string = urlPath
|
var backLink string = prefix + urlPath
|
||||||
|
|
||||||
// don't go further up than /ipfs/$hash/
|
// don't go further up than /ipfs/$hash/
|
||||||
pathSplit := strings.Split(backLink, "/")
|
pathSplit := strings.Split(backLink, "/")
|
||||||
@ -233,7 +241,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
// strip /ipfs/$hash from backlink if IPNSHostnameOption touched the path.
|
// strip /ipfs/$hash from backlink if IPNSHostnameOption touched the path.
|
||||||
if ipnsHostname {
|
if ipnsHostname {
|
||||||
backLink = "/"
|
backLink = prefix + "/"
|
||||||
if len(pathSplit) > 5 {
|
if len(pathSplit) > 5 {
|
||||||
// also strip the trailing segment, because it's a backlink
|
// also strip the trailing segment, because it's a backlink
|
||||||
backLinkParts := pathSplit[3 : len(pathSplit)-2]
|
backLinkParts := pathSplit[3 : len(pathSplit)-2]
|
||||||
|
@ -213,6 +213,30 @@ func TestIPNSHostnameRedirect(t *testing.T) {
|
|||||||
} else if hdr[0] != "/foo/" {
|
} else if hdr[0] != "/foo/" {
|
||||||
t.Errorf("location header is %v, expected /foo/", hdr[0])
|
t.Errorf("location header is %v, expected /foo/", hdr[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make request with prefix to directory containing index.html
|
||||||
|
req, err = http.NewRequest("GET", ts.URL+"/foo", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
req.Host = "example.net"
|
||||||
|
req.Header.Set("X-Ipfs-Gateway-Prefix", "/prefix")
|
||||||
|
|
||||||
|
res, err = doWithoutRedirect(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect 302 redirect to same path, but with prefix and trailing slash
|
||||||
|
if res.StatusCode != 302 {
|
||||||
|
t.Errorf("status is %d, expected 302", res.StatusCode)
|
||||||
|
}
|
||||||
|
hdr = res.Header["Location"]
|
||||||
|
if len(hdr) < 1 {
|
||||||
|
t.Errorf("location header not present")
|
||||||
|
} else if hdr[0] != "/prefix/foo/" {
|
||||||
|
t.Errorf("location header is %v, expected /prefix/foo/", hdr[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIPNSHostnameBacklinks(t *testing.T) {
|
func TestIPNSHostnameBacklinks(t *testing.T) {
|
||||||
@ -282,7 +306,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
|
|||||||
t.Fatalf("expected file in directory listing")
|
t.Fatalf("expected file in directory listing")
|
||||||
}
|
}
|
||||||
|
|
||||||
// make request to directory listing
|
// make request to directory listing at root
|
||||||
req, err = http.NewRequest("GET", ts.URL, nil)
|
req, err = http.NewRequest("GET", ts.URL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -294,7 +318,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// expect correct backlinks
|
// expect correct backlinks at root
|
||||||
body, err = ioutil.ReadAll(res.Body)
|
body, err = ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error reading response: %s", err)
|
t.Fatalf("error reading response: %s", err)
|
||||||
@ -341,4 +365,35 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
|
|||||||
if !strings.Contains(s, "<a href=\"/foo/bar/file.txt\">") {
|
if !strings.Contains(s, "<a href=\"/foo/bar/file.txt\">") {
|
||||||
t.Fatalf("expected file in directory listing")
|
t.Fatalf("expected file in directory listing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make request to directory listing with prefix
|
||||||
|
req, err = http.NewRequest("GET", ts.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
req.Host = "example.net"
|
||||||
|
req.Header.Set("X-Ipfs-Gateway-Prefix", "/prefix")
|
||||||
|
|
||||||
|
res, err = doWithoutRedirect(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect correct backlinks with prefix
|
||||||
|
body, err = ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error reading response: %s", err)
|
||||||
|
}
|
||||||
|
s = string(body)
|
||||||
|
t.Logf("body: %s\n", string(body))
|
||||||
|
|
||||||
|
if !strings.Contains(s, "Index of /prefix") {
|
||||||
|
t.Fatalf("expected a path in directory listing")
|
||||||
|
}
|
||||||
|
if !strings.Contains(s, "<a href=\"/prefix/\">") {
|
||||||
|
t.Fatalf("expected backlink in directory listing")
|
||||||
|
}
|
||||||
|
if !strings.Contains(s, "<a href=\"/prefix/file.txt\">") {
|
||||||
|
t.Fatalf("expected file in directory listing")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ func IPNSHostnameOption() ServeOption {
|
|||||||
if len(host) > 0 && isd.IsDomain(host) {
|
if len(host) > 0 && isd.IsDomain(host) {
|
||||||
name := "/ipns/" + host
|
name := "/ipns/" + host
|
||||||
if _, err := n.Namesys.Resolve(ctx, name); err == nil {
|
if _, err := n.Namesys.Resolve(ctx, name); err == nil {
|
||||||
r.Header["X-IPNS-Original-Path"] = []string{r.URL.Path}
|
r.Header["X-Ipns-Original-Path"] = []string{r.URL.Path}
|
||||||
r.URL.Path = name + r.URL.Path
|
r.URL.Path = name + r.URL.Path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user