mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-26 15:42:21 +08:00
Merge pull request #1577 from lgierth/gateway-host-header
gateway: make IPNSHostname work on responses too
This commit is contained in:
@ -90,6 +90,19 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
||||
|
||||
urlPath := r.URL.Path
|
||||
|
||||
// IPNSHostnameOption might have constructed an IPNS path using the Host header.
|
||||
// In this case, we need the original path for constructing redirects
|
||||
// and links that match the requested URL.
|
||||
// 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
|
||||
originalUrlPath := urlPath
|
||||
ipnsHostname := false
|
||||
hdr := r.Header["X-IPNS-Original-Path"]
|
||||
if len(hdr) > 0 {
|
||||
originalUrlPath = hdr[0]
|
||||
ipnsHostname = true
|
||||
}
|
||||
|
||||
if i.config.BlockList != nil && i.config.BlockList.ShouldBlock(urlPath) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte("403 - Forbidden"))
|
||||
@ -112,10 +125,17 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
||||
w.Header().Set("X-IPFS-Path", urlPath)
|
||||
|
||||
// Suborigin header, sandboxes apps from each other in the browser (even
|
||||
// though they are served from the same gateway domain). NOTE: This is not
|
||||
// yet widely supported by browsers.
|
||||
pathRoot := strings.SplitN(urlPath, "/", 4)[2]
|
||||
w.Header().Set("Suborigin", pathRoot)
|
||||
// though they are served from the same gateway domain).
|
||||
//
|
||||
// Omited if the path was treated by IPNSHostnameOption(), for example
|
||||
// a request for http://example.net/ would be changed to /ipns/example.net/,
|
||||
// which would turn into an incorrect Suborigin: example.net header.
|
||||
//
|
||||
// NOTE: This is not yet widely supported by browsers.
|
||||
if !ipnsHostname {
|
||||
pathRoot := strings.SplitN(urlPath, "/", 4)[2]
|
||||
w.Header().Set("Suborigin", pathRoot)
|
||||
}
|
||||
|
||||
dr, err := uio.NewDagReader(ctx, nd, i.node.DAG)
|
||||
if err != nil && err != uio.ErrIsDir {
|
||||
@ -150,13 +170,16 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
||||
foundIndex := false
|
||||
for _, link := range nd.Links {
|
||||
if link.Name == "index.html" {
|
||||
log.Debugf("found index.html link for %s", urlPath)
|
||||
foundIndex = true
|
||||
|
||||
if urlPath[len(urlPath)-1] != '/' {
|
||||
http.Redirect(w, r, urlPath+"/", 302)
|
||||
// See comment above where originalUrlPath is declared.
|
||||
http.Redirect(w, r, originalUrlPath+"/", 302)
|
||||
log.Debugf("redirect to %s", originalUrlPath+"/")
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("found index")
|
||||
foundIndex = true
|
||||
// return index page instead.
|
||||
nd, err := core.Resolve(ctx, i.node, path.Path(urlPath+"/index.html"))
|
||||
if err != nil {
|
||||
@ -177,7 +200,8 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
||||
break
|
||||
}
|
||||
|
||||
di := directoryItem{link.Size, link.Name, gopath.Join(urlPath, link.Name)}
|
||||
// See comment above where originalUrlPath is declared.
|
||||
di := directoryItem{link.Size, link.Name, gopath.Join(originalUrlPath, link.Name)}
|
||||
dirListing = append(dirListing, di)
|
||||
}
|
||||
|
||||
@ -185,7 +209,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
||||
if r.Method != "HEAD" {
|
||||
// construct the correct back link
|
||||
// https://github.com/ipfs/go-ipfs/issues/1365
|
||||
var backLink string = r.URL.Path
|
||||
var backLink string = urlPath
|
||||
|
||||
// don't go further up than /ipfs/$hash/
|
||||
pathSplit := strings.Split(backLink, "/")
|
||||
@ -205,9 +229,20 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
}
|
||||
|
||||
// strip /ipfs/$hash from backlink if IPNSHostnameOption touched the path.
|
||||
if ipnsHostname {
|
||||
backLink = "/"
|
||||
if len(pathSplit) > 5 {
|
||||
// also strip the trailing segment, because it's a backlink
|
||||
backLinkParts := pathSplit[3 : len(pathSplit)-2]
|
||||
backLink += strings.Join(backLinkParts, "/") + "/"
|
||||
}
|
||||
}
|
||||
|
||||
// See comment above where originalUrlPath is declared.
|
||||
tplData := listingTemplateData{
|
||||
Listing: dirListing,
|
||||
Path: urlPath,
|
||||
Path: originalUrlPath,
|
||||
BackLink: backLink,
|
||||
}
|
||||
err := listingTemplate.Execute(w, tplData)
|
||||
|
@ -37,7 +37,7 @@ func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Pa
|
||||
return errors.New("not implemented for mockNamesys")
|
||||
}
|
||||
|
||||
func newNodeWithMockNamesys(t *testing.T, ns mockNamesys) *core.IpfsNode {
|
||||
func newNodeWithMockNamesys(ns mockNamesys) (*core.IpfsNode, error) {
|
||||
c := config.Config{
|
||||
Identity: config.Identity{
|
||||
PeerID: "Qmfoo", // required by offline node
|
||||
@ -49,10 +49,10 @@ func newNodeWithMockNamesys(t *testing.T, ns mockNamesys) *core.IpfsNode {
|
||||
}
|
||||
n, err := core.NewIPFSNode(context.Background(), core.Offline(r))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
n.Namesys = ns
|
||||
return n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
type delegatedHandler struct {
|
||||
@ -63,21 +63,30 @@ func (dh *delegatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
dh.Handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func TestGatewayGet(t *testing.T) {
|
||||
t.Skip("not sure whats going on here")
|
||||
ns := mockNamesys{}
|
||||
n := newNodeWithMockNamesys(t, ns)
|
||||
k, err := coreunix.Add(n, strings.NewReader("fnord"))
|
||||
func doWithoutRedirect(req *http.Request) (*http.Response, error) {
|
||||
tag := "without-redirect"
|
||||
c := &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return errors.New(tag)
|
||||
},
|
||||
}
|
||||
res, err := c.Do(req)
|
||||
if err != nil && !strings.Contains(err.Error(), tag) {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func newTestServerAndNode(t *testing.T, ns mockNamesys) (*httptest.Server, *core.IpfsNode) {
|
||||
n, err := newNodeWithMockNamesys(ns)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ns["example.com"] = path.FromString("/ipfs/" + k)
|
||||
|
||||
// need this variable here since we need to construct handler with
|
||||
// listener, and server with handler. yay cycles.
|
||||
dh := &delegatedHandler{}
|
||||
ts := httptest.NewServer(dh)
|
||||
defer ts.Close()
|
||||
|
||||
dh.Handler, err = makeHandler(n,
|
||||
ts.Listener,
|
||||
@ -88,6 +97,20 @@ func TestGatewayGet(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return ts, n
|
||||
}
|
||||
|
||||
func TestGatewayGet(t *testing.T) {
|
||||
ns := mockNamesys{}
|
||||
ts, n := newTestServerAndNode(t, ns)
|
||||
defer ts.Close()
|
||||
|
||||
k, err := coreunix.Add(n, strings.NewReader("fnord"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ns["/ipns/example.com"] = path.FromString("/ipfs/" + k)
|
||||
|
||||
t.Log(ts.URL)
|
||||
for _, test := range []struct {
|
||||
host string
|
||||
@ -130,3 +153,187 @@ func TestGatewayGet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPNSHostnameRedirect(t *testing.T) {
|
||||
ns := mockNamesys{}
|
||||
ts, n := newTestServerAndNode(t, ns)
|
||||
t.Logf("test server url: %s", ts.URL)
|
||||
defer ts.Close()
|
||||
|
||||
// create /ipns/example.net/foo/index.html
|
||||
_, dagn1, err := coreunix.AddWrapped(n, strings.NewReader("_"), "_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, dagn2, err := coreunix.AddWrapped(n, strings.NewReader("_"), "index.html")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dagn1.AddNodeLink("foo", dagn2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = n.DAG.AddRecursive(dagn1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
k, err := dagn1.Key()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("k: %s\n", k)
|
||||
ns["/ipns/example.net"] = path.FromString("/ipfs/" + k.String())
|
||||
|
||||
// make request to directory containing index.html
|
||||
req, err := http.NewRequest("GET", ts.URL+"/foo", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Host = "example.net"
|
||||
|
||||
res, err := doWithoutRedirect(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// expect 302 redirect to same path, but with 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] != "/foo/" {
|
||||
t.Errorf("location header is %v, expected /foo/", hdr[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPNSHostnameBacklinks(t *testing.T) {
|
||||
ns := mockNamesys{}
|
||||
ts, n := newTestServerAndNode(t, ns)
|
||||
t.Logf("test server url: %s", ts.URL)
|
||||
defer ts.Close()
|
||||
|
||||
// create /ipns/example.net/foo/
|
||||
_, dagn1, err := coreunix.AddWrapped(n, strings.NewReader("1"), "file.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, dagn2, err := coreunix.AddWrapped(n, strings.NewReader("2"), "file.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, dagn3, err := coreunix.AddWrapped(n, strings.NewReader("3"), "file.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dagn2.AddNodeLink("bar", dagn3)
|
||||
dagn1.AddNodeLink("foo", dagn2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = n.DAG.AddRecursive(dagn1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
k, err := dagn1.Key()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("k: %s\n", k)
|
||||
ns["/ipns/example.net"] = path.FromString("/ipfs/" + k.String())
|
||||
|
||||
// make request to directory listing
|
||||
req, err := http.NewRequest("GET", ts.URL+"/foo/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Host = "example.net"
|
||||
|
||||
res, err := doWithoutRedirect(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// expect correct backlinks
|
||||
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 /foo/") {
|
||||
t.Fatalf("expected a path in directory listing")
|
||||
}
|
||||
if !strings.Contains(s, "<a href=\"/\">") {
|
||||
t.Fatalf("expected backlink in directory listing")
|
||||
}
|
||||
if !strings.Contains(s, "<a href=\"/foo/file.txt\">") {
|
||||
t.Fatalf("expected file in directory listing")
|
||||
}
|
||||
|
||||
// make request to directory listing
|
||||
req, err = http.NewRequest("GET", ts.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Host = "example.net"
|
||||
|
||||
res, err = doWithoutRedirect(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// expect correct backlinks
|
||||
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 /") {
|
||||
t.Fatalf("expected a path in directory listing")
|
||||
}
|
||||
if !strings.Contains(s, "<a href=\"/\">") {
|
||||
t.Fatalf("expected backlink in directory listing")
|
||||
}
|
||||
if !strings.Contains(s, "<a href=\"/file.txt\">") {
|
||||
t.Fatalf("expected file in directory listing")
|
||||
}
|
||||
|
||||
// make request to directory listing
|
||||
req, err = http.NewRequest("GET", ts.URL+"/foo/bar/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Host = "example.net"
|
||||
|
||||
res, err = doWithoutRedirect(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// expect correct backlinks
|
||||
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 /foo/bar/") {
|
||||
t.Fatalf("expected a path in directory listing")
|
||||
}
|
||||
if !strings.Contains(s, "<a href=\"/foo/\">") {
|
||||
t.Fatalf("expected backlink in directory listing")
|
||||
}
|
||||
if !strings.Contains(s, "<a href=\"/foo/bar/file.txt\">") {
|
||||
t.Fatalf("expected file in directory listing")
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ func IPNSHostnameOption() ServeOption {
|
||||
if len(host) > 0 && isd.IsDomain(host) {
|
||||
name := "/ipns/" + host
|
||||
if _, err := n.Namesys.Resolve(ctx, name); err == nil {
|
||||
r.Header["X-IPNS-Original-Path"] = []string{r.URL.Path}
|
||||
r.URL.Path = name + r.URL.Path
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user