Skip to content

Commit

Permalink
feat(gateway): redirect ipns b58mh to cid (#236)
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias authored May 16, 2023
1 parent b3dc26e commit 5e94b9d
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
27 changes: 27 additions & 0 deletions gateway/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,3 +546,30 @@ func TestGoGetSupport(t *testing.T) {
assert.NoError(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/k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8?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/k2k4r8ol4m8kkcqz509c1rcjwunebj02gcnm5excpx842u736nja8ger?keep=query", res.Header.Get("Location"))
})
}
57 changes: 57 additions & 0 deletions gateway/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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-multibase"
prometheus "github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
Expand Down Expand Up @@ -204,6 +206,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)
Expand Down Expand Up @@ -728,6 +734,57 @@ 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 {
if _, dnslink := r.Context().Value(DNSLinkHostnameKey).(string); dnslink {
// For DNSLink hostnames, do not perform redirection in order to not break
// website. For example, if `example.net` is backed by `/ipns/base58`, we
// must NOT redirect to `example.net/ipns/base36-id`.
return false
}

if w.Header().Get("Location") != "" {
// Ignore this if there is already a redirection in place. This happens
// if there is a subdomain redirection. In that case, the path is already
// converted to CIDv1.
return false
}

pathParts := strings.Split(r.URL.Path, "/")
if len(pathParts) < 3 {
return false
}

if pathParts[1] != "ipns" {
return false
}

id, err := peer.Decode(pathParts[2])
if err != nil {
return false
}

// Convert the peer ID to a CIDv1.
cid := peer.ToCid(id)

// Encode CID in base36 to match the subdomain URLs.
encodedCID, err := cid.StringOfBase(multibase.Base36)
if err != nil {
return false
}

// If the CID was already encoded, do not redirect.
if encodedCID == pathParts[2] {
return false
}

pathParts[2] = encodedCID
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}
Expand Down

0 comments on commit 5e94b9d

Please sign in to comment.