From 8db6f86de6085b78bc03bccdc26d11a7350058d4 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Tue, 18 Apr 2017 16:22:53 +0100 Subject: [PATCH 1/2] gateway: use CID as an ETag strong validator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Always use the fully resolved CID from api.ResolveNode as the ETag (also for IPNS). * Format the result as a valid "Strong Validator" (double quotes around the encoded CID). Fixes #3868 License: MIT Signed-off-by: Remco Bloemen --- core/corehttp/gateway_handler.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 264f074d9..00e87eb66 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -183,7 +183,16 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr return } - etag := gopath.Base(urlPath) + // Resolve path to the final DAG node for the ETag + dagnode, err := i.api.ResolveNode(ctx, parsedPath) + if err != nil { + // Unixfs().Cat() also calls ResolveNode, so it should not fail here. + webError(w, "could not resolve ipfs path", err, http.StatusBadRequest) + return + } + + // Check etag send back to us + etag := "\"" + dagnode.Cid().String() + "\"" if r.Header.Get("If-None-Match") == etag { w.WriteHeader(http.StatusNotModified) return @@ -191,6 +200,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr i.addUserHeaders(w) // ok, _now_ write user's headers. w.Header().Set("X-IPFS-Path", urlPath) + w.Header().Set("Etag", etag) // set 'allowed' headers w.Header().Set("Access-Control-Allow-Headers", "X-Stream-Output, X-Chunked-Output") @@ -203,7 +213,6 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr // TODO: break this out when we split /ipfs /ipns routes. modtime := time.Now() if strings.HasPrefix(urlPath, ipfsPathPrefix) { - w.Header().Set("Etag", etag) w.Header().Set("Cache-Control", "public, max-age=29030400, immutable") // set modtime to a really long time ago, since files are immutable and should stay cached From a90f4967e30fc5f16d362f1b9844196e1bd24101 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Tue, 18 Apr 2017 17:22:01 +0100 Subject: [PATCH 2/2] gateway: re-use resolved path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of resolving a node, we resolve a path. This resolved path is then re-used for Cat and Ls. This way, a resolve operation is only done once. The error messages for a failed resolve is changed from `ipfs cat …` to `ipfs resolve …` to better reflect the API calls. The test is updated accordingly. License: MIT Signed-off-by: Remco Bloemen --- core/corehttp/gateway_handler.go | 35 ++++++++++++++++---------------- core/corehttp/gateway_test.go | 2 +- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 00e87eb66..3d282c27f 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -164,7 +164,22 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr return } - dr, err := i.api.Unixfs().Cat(ctx, parsedPath) + // Resolve path to the final DAG node for the ETag + resolvedPath, err := i.api.ResolvePath(ctx, parsedPath) + switch err { + case nil: + case coreiface.ErrOffline: + if !i.node.OnlineMode() { + webError(w, "ipfs resolve -r "+urlPath, err, http.StatusServiceUnavailable) + return + } + fallthrough + default: + webError(w, "ipfs resolve -r "+urlPath, err, http.StatusNotFound) + return + } + + dr, err := i.api.Unixfs().Cat(ctx, resolvedPath) dir := false switch err { case nil: @@ -172,27 +187,13 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr defer dr.Close() case coreiface.ErrIsDir: dir = true - case coreiface.ErrOffline: - if !i.node.OnlineMode() { - webError(w, "ipfs cat "+urlPath, err, http.StatusServiceUnavailable) - return - } - fallthrough default: webError(w, "ipfs cat "+urlPath, err, http.StatusNotFound) return } - // Resolve path to the final DAG node for the ETag - dagnode, err := i.api.ResolveNode(ctx, parsedPath) - if err != nil { - // Unixfs().Cat() also calls ResolveNode, so it should not fail here. - webError(w, "could not resolve ipfs path", err, http.StatusBadRequest) - return - } - // Check etag send back to us - etag := "\"" + dagnode.Cid().String() + "\"" + etag := "\"" + resolvedPath.Cid().String() + "\"" if r.Header.Get("If-None-Match") == etag { w.WriteHeader(http.StatusNotModified) return @@ -225,7 +226,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr return } - links, err := i.api.Unixfs().Ls(ctx, parsedPath) + links, err := i.api.Unixfs().Ls(ctx, resolvedPath) if err != nil { internalWebError(w, err) return diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index 8f307b2c7..8b639f1f2 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -136,7 +136,7 @@ func TestGatewayGet(t *testing.T) { {"localhost:5001", "/", http.StatusNotFound, "404 page not found\n"}, {"localhost:5001", "/" + k, http.StatusNotFound, "404 page not found\n"}, {"localhost:5001", "/ipfs/" + k, http.StatusOK, "fnord"}, - {"localhost:5001", "/ipns/nxdomain.example.com", http.StatusNotFound, "ipfs cat /ipns/nxdomain.example.com: " + namesys.ErrResolveFailed.Error() + "\n"}, + {"localhost:5001", "/ipns/nxdomain.example.com", http.StatusNotFound, "ipfs resolve -r /ipns/nxdomain.example.com: " + namesys.ErrResolveFailed.Error() + "\n"}, {"localhost:5001", "/ipns/example.com", http.StatusOK, "fnord"}, {"example.com", "/", http.StatusOK, "fnord"}, } {