From 55d12581e5f6aaea1d6a7ccd823be6200d19bd25 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Thu, 30 Mar 2023 14:42:36 +0200 Subject: [PATCH] feat(gateway): redirect ipns b58mh to cid --- .github/workflows/gateway-sharness.yml | 2 +- gateway/gateway_test.go | 27 ++++++++++++++++++ gateway/handler.go | 38 ++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gateway-sharness.yml b/.github/workflows/gateway-sharness.yml index 3da1845c24..aacaeca72d 100644 --- a/.github/workflows/gateway-sharness.yml +++ b/.github/workflows/gateway-sharness.yml @@ -28,7 +28,7 @@ jobs: with: repository: ipfs/kubo path: kubo - ref: 503edee648e29c62888f05fa146ab13d9c65077d + ref: master - name: Install Missing Tools run: sudo apt install -y socat net-tools fish libxml2-utils - name: Restore Go Cache diff --git a/gateway/gateway_test.go b/gateway/gateway_test.go index aa36b47e9a..83452fdfed 100644 --- a/gateway/gateway_test.go +++ b/gateway/gateway_test.go @@ -548,3 +548,30 @@ func TestGoGetSupport(t *testing.T) { assert.Nil(t, err) assert.Equal(t, http.StatusOK, res.StatusCode) } + +func TestIpnsBase58MultihashRedirect(t *testing.T) { + ts, _, _ := newTestServerAndNode(t, nil) + t.Logf("test server url: %s", ts.URL) + + t.Run("ED25519 Base58-encoded key", func(t *testing.T) { + t.Parallel() + + req, err := http.NewRequest(http.MethodGet, ts.URL+"/ipns/12D3KooWRBy97UB99e3J6hiPesre1MZeuNQvfan4gBziswrRJsNK?keep=query", nil) + assert.Nil(t, err) + + res, err := doWithoutRedirect(req) + assert.Nil(t, err) + assert.Equal(t, "/ipns/bafzaajaiaejcbzdibmxyzdjbbehgvizh6g5tikvy47mshdy6gwbruvgwvd24seje?keep=query", res.Header.Get("Location")) + }) + + t.Run("RSA Base58-encoded key", func(t *testing.T) { + t.Parallel() + + req, err := http.NewRequest(http.MethodGet, ts.URL+"/ipns/QmcJM7PRfkSbcM5cf1QugM5R37TLRKyJGgBEhXjLTB8uA2?keep=query", nil) + assert.Nil(t, err) + + res, err := doWithoutRedirect(req) + assert.Nil(t, err) + assert.Equal(t, "/ipns/bafzbeigpnshhtgeigvxxovxerb3vb7m45upncgaobvxtqtlaa54yb73lcm?keep=query", res.Header.Get("Location")) + }) +} diff --git a/gateway/handler.go b/gateway/handler.go index 4c3fe29fdb..bcf5d8577e 100644 --- a/gateway/handler.go +++ b/gateway/handler.go @@ -19,6 +19,8 @@ import ( ipath "github.com/ipfs/boxo/coreiface/path" cid "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multihash" prometheus "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -325,6 +327,10 @@ func (i *handler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) { return } + if handleIpnsB58mhToCidRedirection(w, r) { + return + } + contentPath := ipath.New(r.URL.Path) ctx := context.WithValue(r.Context(), ContentPathKey, contentPath) r = r.WithContext(ctx) @@ -844,6 +850,38 @@ func handleServiceWorkerRegistration(r *http.Request) (err *ErrorResponse) { return nil } +// handleIpnsB58mhToCidRedirection redirects from /ipns/b58mh to /ipns/cid in +// the most cost-effective way. +func handleIpnsB58mhToCidRedirection(w http.ResponseWriter, r *http.Request) bool { + pathParts := strings.Split(r.URL.Path, "/") + if len(pathParts) < 3 { + return false + } + + if pathParts[1] != "ipns" { + return false + } + + s := pathParts[2] + + // Similarly to peer.Decode, check the prefix first as it is + // less computationally expensive. + if !strings.HasPrefix(s, "Qm") && !strings.HasPrefix(s, "1") { + return false + } + + // Decode the base58 encoded sha256 or identity multihash. + m, err := multihash.FromB58String(s) + if err != nil { + return false + } + + pathParts[2] = peer.ToCid(peer.ID(m)).String() + r.URL.Path = strings.Join(pathParts, "/") + http.Redirect(w, r, r.URL.String(), http.StatusFound) + return true +} + // Attempt to fix redundant /ipfs/ namespace as long as resulting // 'intended' path is valid. This is in case gremlins were tickled // wrong way and user ended up at /ipfs/ipfs/{cid} or /ipfs/ipns/{id}