Skip to content

Commit 5e94b9d

Browse files
authored
feat(gateway): redirect ipns b58mh to cid (#236)
1 parent b3dc26e commit 5e94b9d

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

gateway/gateway_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,30 @@ func TestGoGetSupport(t *testing.T) {
546546
assert.NoError(t, err)
547547
assert.Equal(t, http.StatusOK, res.StatusCode)
548548
}
549+
550+
func TestIpnsBase58MultihashRedirect(t *testing.T) {
551+
ts, _, _ := newTestServerAndNode(t, nil)
552+
t.Logf("test server url: %s", ts.URL)
553+
554+
t.Run("ED25519 Base58-encoded key", func(t *testing.T) {
555+
t.Parallel()
556+
557+
req, err := http.NewRequest(http.MethodGet, ts.URL+"/ipns/12D3KooWRBy97UB99e3J6hiPesre1MZeuNQvfan4gBziswrRJsNK?keep=query", nil)
558+
assert.Nil(t, err)
559+
560+
res, err := doWithoutRedirect(req)
561+
assert.Nil(t, err)
562+
assert.Equal(t, "/ipns/k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8?keep=query", res.Header.Get("Location"))
563+
})
564+
565+
t.Run("RSA Base58-encoded key", func(t *testing.T) {
566+
t.Parallel()
567+
568+
req, err := http.NewRequest(http.MethodGet, ts.URL+"/ipns/QmcJM7PRfkSbcM5cf1QugM5R37TLRKyJGgBEhXjLTB8uA2?keep=query", nil)
569+
assert.Nil(t, err)
570+
571+
res, err := doWithoutRedirect(req)
572+
assert.Nil(t, err)
573+
assert.Equal(t, "/ipns/k2k4r8ol4m8kkcqz509c1rcjwunebj02gcnm5excpx842u736nja8ger?keep=query", res.Header.Get("Location"))
574+
})
575+
}

gateway/handler.go

+57
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
ipath "github.com/ipfs/boxo/coreiface/path"
2020
cid "github.com/ipfs/go-cid"
2121
logging "github.com/ipfs/go-log"
22+
"github.com/libp2p/go-libp2p/core/peer"
23+
"github.com/multiformats/go-multibase"
2224
prometheus "github.com/prometheus/client_golang/prometheus"
2325
"go.opentelemetry.io/otel/attribute"
2426
"go.opentelemetry.io/otel/trace"
@@ -204,6 +206,10 @@ func (i *handler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) {
204206
return
205207
}
206208

209+
if handleIpnsB58mhToCidRedirection(w, r) {
210+
return
211+
}
212+
207213
contentPath := ipath.New(r.URL.Path)
208214
ctx := context.WithValue(r.Context(), ContentPathKey, contentPath)
209215
r = r.WithContext(ctx)
@@ -728,6 +734,57 @@ func handleServiceWorkerRegistration(r *http.Request) (err *ErrorResponse) {
728734
return nil
729735
}
730736

737+
// handleIpnsB58mhToCidRedirection redirects from /ipns/b58mh to /ipns/cid in
738+
// the most cost-effective way.
739+
func handleIpnsB58mhToCidRedirection(w http.ResponseWriter, r *http.Request) bool {
740+
if _, dnslink := r.Context().Value(DNSLinkHostnameKey).(string); dnslink {
741+
// For DNSLink hostnames, do not perform redirection in order to not break
742+
// website. For example, if `example.net` is backed by `/ipns/base58`, we
743+
// must NOT redirect to `example.net/ipns/base36-id`.
744+
return false
745+
}
746+
747+
if w.Header().Get("Location") != "" {
748+
// Ignore this if there is already a redirection in place. This happens
749+
// if there is a subdomain redirection. In that case, the path is already
750+
// converted to CIDv1.
751+
return false
752+
}
753+
754+
pathParts := strings.Split(r.URL.Path, "/")
755+
if len(pathParts) < 3 {
756+
return false
757+
}
758+
759+
if pathParts[1] != "ipns" {
760+
return false
761+
}
762+
763+
id, err := peer.Decode(pathParts[2])
764+
if err != nil {
765+
return false
766+
}
767+
768+
// Convert the peer ID to a CIDv1.
769+
cid := peer.ToCid(id)
770+
771+
// Encode CID in base36 to match the subdomain URLs.
772+
encodedCID, err := cid.StringOfBase(multibase.Base36)
773+
if err != nil {
774+
return false
775+
}
776+
777+
// If the CID was already encoded, do not redirect.
778+
if encodedCID == pathParts[2] {
779+
return false
780+
}
781+
782+
pathParts[2] = encodedCID
783+
r.URL.Path = strings.Join(pathParts, "/")
784+
http.Redirect(w, r, r.URL.String(), http.StatusFound)
785+
return true
786+
}
787+
731788
// Attempt to fix redundant /ipfs/ namespace as long as resulting
732789
// 'intended' path is valid. This is in case gremlins were tickled
733790
// wrong way and user ended up at /ipfs/ipfs/{cid} or /ipfs/ipns/{id}

0 commit comments

Comments
 (0)