mirror of
https://github.com/ipfs/kubo.git
synced 2025-08-06 11:31:54 +08:00

This ensures we exclude valid PeerIDs from code paths that require DNSLink names. Ref. https://github.com/ipfs/go-ipfs/pull/8071#pullrequestreview-639409245
302 lines
14 KiB
Go
302 lines
14 KiB
Go
package corehttp
|
||
|
||
import (
|
||
"errors"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"testing"
|
||
|
||
cid "github.com/ipfs/go-cid"
|
||
config "github.com/ipfs/go-ipfs-config"
|
||
files "github.com/ipfs/go-ipfs-files"
|
||
coreapi "github.com/ipfs/go-ipfs/core/coreapi"
|
||
path "github.com/ipfs/go-path"
|
||
)
|
||
|
||
func TestToSubdomainURL(t *testing.T) {
|
||
ns := mockNamesys{}
|
||
n, err := newNodeWithMockNamesys(ns)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
coreAPI, err := coreapi.NewCoreAPI(n)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
testCID, err := coreAPI.Unixfs().Add(n.Context(), files.NewBytesFile([]byte("fnord")))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
ns["/ipns/dnslink.long-name.example.com"] = path.FromString(testCID.String())
|
||
ns["/ipns/dnslink.too-long.f1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5o.example.com"] = path.FromString(testCID.String())
|
||
httpRequest := httptest.NewRequest("GET", "http://127.0.0.1:8080", nil)
|
||
httpsRequest := httptest.NewRequest("GET", "https://https-request-stub.example.com", nil)
|
||
httpsProxiedRequest := httptest.NewRequest("GET", "http://proxied-https-request-stub.example.com", nil)
|
||
httpsProxiedRequest.Header.Set("X-Forwarded-Proto", "https")
|
||
|
||
for _, test := range []struct {
|
||
// in:
|
||
request *http.Request
|
||
gwHostname string
|
||
path string
|
||
// out:
|
||
url string
|
||
err error
|
||
}{
|
||
// DNSLink
|
||
{httpRequest, "localhost", "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost/", nil},
|
||
// Hostname with port
|
||
{httpRequest, "localhost:8080", "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost:8080/", nil},
|
||
// CIDv0 → CIDv1base32
|
||
{httpRequest, "localhost", "/ipfs/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "http://bafybeif7a7gdklt6hodwdrmwmxnhksctcuav6lfxlcyfz4khzl3qfmvcgu.ipfs.localhost/", nil},
|
||
// CIDv1 with long sha512
|
||
{httpRequest, "localhost", "/ipfs/bafkrgqe3ohjcjplc6n4f3fwunlj6upltggn7xqujbsvnvyw764srszz4u4rshq6ztos4chl4plgg4ffyyxnayrtdi5oc4xb2332g645433aeg", "", errors.New("CID incompatible with DNS label length limit of 63: kf1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5oj")},
|
||
// PeerID as CIDv1 needs to have libp2p-key multicodec
|
||
{httpRequest, "localhost", "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", "http://k2k4r8n0flx3ra0y5dr8fmyvwbzy3eiztmtq6th694k5a3rznayp3e4o.ipns.localhost/", nil},
|
||
{httpRequest, "localhost", "/ipns/bafybeickencdqw37dpz3ha36ewrh4undfjt2do52chtcky4rxkj447qhdm", "http://k2k4r8l9ja7hkzynavdqup76ou46tnvuaqegbd04a4o1mpbsey0meucb.ipns.localhost/", nil},
|
||
// PeerID: ed25519+identity multihash → CIDv1Base36
|
||
{httpRequest, "localhost", "/ipns/12D3KooWFB51PRY9BxcXSH6khFXw1BZeszeLDy7C8GciskqCTZn5", "http://k51qzi5uqu5di608geewp3nqkg0bpujoasmka7ftkyxgcm3fh1aroup0gsdrna.ipns.localhost/", nil},
|
||
{httpRequest, "sub.localhost", "/ipfs/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "http://bafybeif7a7gdklt6hodwdrmwmxnhksctcuav6lfxlcyfz4khzl3qfmvcgu.ipfs.sub.localhost/", nil},
|
||
// HTTPS requires DNSLink name to fit in a single DNS label – see "Option C" from https://github.com/ipfs/in-web-browsers/issues/169
|
||
{httpRequest, "dweb.link", "/ipns/dnslink.long-name.example.com", "http://dnslink.long-name.example.com.ipns.dweb.link/", nil},
|
||
{httpsRequest, "dweb.link", "/ipns/dnslink.long-name.example.com", "https://dnslink-long--name-example-com.ipns.dweb.link/", nil},
|
||
{httpsProxiedRequest, "dweb.link", "/ipns/dnslink.long-name.example.com", "https://dnslink-long--name-example-com.ipns.dweb.link/", nil},
|
||
} {
|
||
url, err := toSubdomainURL(test.gwHostname, test.path, test.request, coreAPI)
|
||
if url != test.url || !equalError(err, test.err) {
|
||
t.Errorf("(%s, %s) returned (%s, %v), expected (%s, %v)", test.gwHostname, test.path, url, err, test.url, test.err)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestToDNSLinkDNSLabel(t *testing.T) {
|
||
for _, test := range []struct {
|
||
in string
|
||
out string
|
||
err error
|
||
}{
|
||
{"dnslink.long-name.example.com", "dnslink-long--name-example-com", nil},
|
||
{"dnslink.too-long.f1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5o.example.com", "", errors.New("DNSLink representation incompatible with DNS label length limit of 63: dnslink-too--long-f1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5o-example-com")},
|
||
} {
|
||
out, err := toDNSLinkDNSLabel(test.in)
|
||
if out != test.out || !equalError(err, test.err) {
|
||
t.Errorf("(%s) returned (%s, %v), expected (%s, %v)", test.in, out, err, test.out, test.err)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestToDNSLinkFQDN(t *testing.T) {
|
||
for _, test := range []struct {
|
||
in string
|
||
out string
|
||
}{
|
||
{"singlelabel", "singlelabel"},
|
||
{"docs-ipfs-io", "docs.ipfs.io"},
|
||
{"dnslink-long--name-example-com", "dnslink.long-name.example.com"},
|
||
} {
|
||
out := toDNSLinkFQDN(test.in)
|
||
if out != test.out {
|
||
t.Errorf("(%s) returned (%s), expected (%s)", test.in, out, test.out)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestIsHTTPSRequest(t *testing.T) {
|
||
httpRequest := httptest.NewRequest("GET", "http://127.0.0.1:8080", nil)
|
||
httpsRequest := httptest.NewRequest("GET", "https://https-request-stub.example.com", nil)
|
||
httpsProxiedRequest := httptest.NewRequest("GET", "http://proxied-https-request-stub.example.com", nil)
|
||
httpsProxiedRequest.Header.Set("X-Forwarded-Proto", "https")
|
||
httpProxiedRequest := httptest.NewRequest("GET", "http://proxied-http-request-stub.example.com", nil)
|
||
httpProxiedRequest.Header.Set("X-Forwarded-Proto", "http")
|
||
oddballRequest := httptest.NewRequest("GET", "foo://127.0.0.1:8080", nil)
|
||
for _, test := range []struct {
|
||
in *http.Request
|
||
out bool
|
||
}{
|
||
{httpRequest, false},
|
||
{httpsRequest, true},
|
||
{httpsProxiedRequest, true},
|
||
{httpProxiedRequest, false},
|
||
{oddballRequest, false},
|
||
} {
|
||
out := isHTTPSRequest(test.in)
|
||
if out != test.out {
|
||
t.Errorf("(%+v): returned %t, expected %t", test.in, out, test.out)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestHasPrefix(t *testing.T) {
|
||
for _, test := range []struct {
|
||
prefixes []string
|
||
path string
|
||
out bool
|
||
}{
|
||
{[]string{"/ipfs"}, "/ipfs/cid", true},
|
||
{[]string{"/ipfs/"}, "/ipfs/cid", true},
|
||
{[]string{"/version/"}, "/version", true},
|
||
{[]string{"/version"}, "/version", true},
|
||
} {
|
||
out := hasPrefix(test.path, test.prefixes...)
|
||
if out != test.out {
|
||
t.Errorf("(%+v, %s) returned '%t', expected '%t'", test.prefixes, test.path, out, test.out)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestIsDomainNameAndNotPeerID(t *testing.T) {
|
||
for _, test := range []struct {
|
||
hostname string
|
||
out bool
|
||
}{
|
||
{"", false},
|
||
{"example.com", true},
|
||
{"non-icann.something", true},
|
||
{"..", false},
|
||
{"12D3KooWFB51PRY9BxcXSH6khFXw1BZeszeLDy7C8GciskqCTZn5", false}, // valid peerid
|
||
{"k51qzi5uqu5di608geewp3nqkg0bpujoasmka7ftkyxgcm3fh1aroup0gsdrna", false}, // valid peerid
|
||
} {
|
||
out := isDomainNameAndNotPeerID(test.hostname)
|
||
if out != test.out {
|
||
t.Errorf("(%s) returned '%t', expected '%t'", test.hostname, out, test.out)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestPortStripping(t *testing.T) {
|
||
for _, test := range []struct {
|
||
in string
|
||
out string
|
||
}{
|
||
{"localhost:8080", "localhost"},
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.localhost:8080", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.localhost"},
|
||
{"example.com:443", "example.com"},
|
||
{"example.com", "example.com"},
|
||
{"foo-dweb.ipfs.pvt.k12.ma.us:8080", "foo-dweb.ipfs.pvt.k12.ma.us"},
|
||
{"localhost", "localhost"},
|
||
{"[::1]:8080", "::1"},
|
||
} {
|
||
out := stripPort(test.in)
|
||
if out != test.out {
|
||
t.Errorf("(%s): returned '%s', expected '%s'", test.in, out, test.out)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestToDNSLabel(t *testing.T) {
|
||
for _, test := range []struct {
|
||
in string
|
||
out string
|
||
err error
|
||
}{
|
||
// <= 63
|
||
{"QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", nil},
|
||
{"bafybeickencdqw37dpz3ha36ewrh4undfjt2do52chtcky4rxkj447qhdm", "bafybeickencdqw37dpz3ha36ewrh4undfjt2do52chtcky4rxkj447qhdm", nil},
|
||
// > 63
|
||
// PeerID: ed25519+identity multihash → CIDv1Base36
|
||
{"bafzaajaiaejca4syrpdu6gdx4wsdnokxkprgzxf4wrstuc34gxw5k5jrag2so5gk", "k51qzi5uqu5dj16qyiq0tajolkojyl9qdkr254920wxv7ghtuwcz593tp69z9m", nil},
|
||
// CIDv1 with long sha512 → error
|
||
{"bafkrgqe3ohjcjplc6n4f3fwunlj6upltggn7xqujbsvnvyw764srszz4u4rshq6ztos4chl4plgg4ffyyxnayrtdi5oc4xb2332g645433aeg", "", errors.New("CID incompatible with DNS label length limit of 63: kf1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5oj")},
|
||
} {
|
||
inCID, _ := cid.Decode(test.in)
|
||
out, err := toDNSLabel(test.in, inCID)
|
||
if out != test.out || !equalError(err, test.err) {
|
||
t.Errorf("(%s): returned (%s, %v) expected (%s, %v)", test.in, out, err, test.out, test.err)
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
func TestKnownSubdomainDetails(t *testing.T) {
|
||
gwLocalhost := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
|
||
gwDweb := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
|
||
gwLong := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
|
||
gwWildcard1 := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
|
||
gwWildcard2 := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
|
||
|
||
knownGateways := prepareKnownGateways(map[string]*config.GatewaySpec{
|
||
"localhost": gwLocalhost,
|
||
"dweb.link": gwDweb,
|
||
"dweb.ipfs.pvt.k12.ma.us": gwLong, // note the sneaky ".ipfs." ;-)
|
||
"*.wildcard1.tld": gwWildcard1,
|
||
"*.*.wildcard2.tld": gwWildcard2,
|
||
})
|
||
|
||
for _, test := range []struct {
|
||
// in:
|
||
hostHeader string
|
||
// out:
|
||
gw *config.GatewaySpec
|
||
hostname string
|
||
ns string
|
||
rootID string
|
||
ok bool
|
||
}{
|
||
// no subdomain
|
||
{"127.0.0.1:8080", nil, "", "", "", false},
|
||
{"[::1]:8080", nil, "", "", "", false},
|
||
{"hey.look.example.com", nil, "", "", "", false},
|
||
{"dweb.link", nil, "", "", "", false},
|
||
// malformed Host header
|
||
{".....dweb.link", nil, "", "", "", false},
|
||
{"link", nil, "", "", "", false},
|
||
{"8080:dweb.link", nil, "", "", "", false},
|
||
{" ", nil, "", "", "", false},
|
||
{"", nil, "", "", "", false},
|
||
// unknown gateway host
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.unknown.example.com", nil, "", "", "", false},
|
||
// cid in subdomain, known gateway
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.localhost:8080", gwLocalhost, "localhost:8080", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.dweb.link", gwDweb, "dweb.link", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
|
||
// capture everything before .ipfs.
|
||
{"foo.bar.boo-buzz.ipfs.dweb.link", gwDweb, "dweb.link", "ipfs", "foo.bar.boo-buzz", true},
|
||
// ipns
|
||
{"bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju.ipns.localhost:8080", gwLocalhost, "localhost:8080", "ipns", "bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju", true},
|
||
{"bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju.ipns.dweb.link", gwDweb, "dweb.link", "ipns", "bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju", true},
|
||
// edge case check: public gateway under long TLD (see: https://publicsuffix.org)
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.dweb.ipfs.pvt.k12.ma.us", gwLong, "dweb.ipfs.pvt.k12.ma.us", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
|
||
{"bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju.ipns.dweb.ipfs.pvt.k12.ma.us", gwLong, "dweb.ipfs.pvt.k12.ma.us", "ipns", "bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju", true},
|
||
// dnslink in subdomain
|
||
{"en.wikipedia-on-ipfs.org.ipns.localhost:8080", gwLocalhost, "localhost:8080", "ipns", "en.wikipedia-on-ipfs.org", true},
|
||
{"en.wikipedia-on-ipfs.org.ipns.localhost", gwLocalhost, "localhost", "ipns", "en.wikipedia-on-ipfs.org", true},
|
||
{"dist.ipfs.io.ipns.localhost:8080", gwLocalhost, "localhost:8080", "ipns", "dist.ipfs.io", true},
|
||
{"en.wikipedia-on-ipfs.org.ipns.dweb.link", gwDweb, "dweb.link", "ipns", "en.wikipedia-on-ipfs.org", true},
|
||
// edge case check: public gateway under long TLD (see: https://publicsuffix.org)
|
||
{"foo.dweb.ipfs.pvt.k12.ma.us", nil, "", "", "", false},
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.dweb.ipfs.pvt.k12.ma.us", gwLong, "dweb.ipfs.pvt.k12.ma.us", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
|
||
{"bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju.ipns.dweb.ipfs.pvt.k12.ma.us", gwLong, "dweb.ipfs.pvt.k12.ma.us", "ipns", "bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju", true},
|
||
// other namespaces
|
||
{"api.localhost", nil, "", "", "", false},
|
||
{"peerid.p2p.localhost", gwLocalhost, "localhost", "p2p", "peerid", true},
|
||
// wildcards
|
||
{"wildcard1.tld", nil, "", "", "", false},
|
||
{".wildcard1.tld", nil, "", "", "", false},
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.wildcard1.tld", nil, "", "", "", false},
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.sub.wildcard1.tld", gwWildcard1, "sub.wildcard1.tld", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.sub1.sub2.wildcard1.tld", nil, "", "", "", false},
|
||
{"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.sub1.sub2.wildcard2.tld", gwWildcard2, "sub1.sub2.wildcard2.tld", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
|
||
} {
|
||
gw, hostname, ns, rootID, ok := knownSubdomainDetails(test.hostHeader, knownGateways)
|
||
if ok != test.ok {
|
||
t.Errorf("knownSubdomainDetails(%s): ok is %t, expected %t", test.hostHeader, ok, test.ok)
|
||
}
|
||
if rootID != test.rootID {
|
||
t.Errorf("knownSubdomainDetails(%s): rootID is '%s', expected '%s'", test.hostHeader, rootID, test.rootID)
|
||
}
|
||
if ns != test.ns {
|
||
t.Errorf("knownSubdomainDetails(%s): ns is '%s', expected '%s'", test.hostHeader, ns, test.ns)
|
||
}
|
||
if hostname != test.hostname {
|
||
t.Errorf("knownSubdomainDetails(%s): hostname is '%s', expected '%s'", test.hostHeader, hostname, test.hostname)
|
||
}
|
||
if gw != test.gw {
|
||
t.Errorf("knownSubdomainDetails(%s): gw is %+v, expected %+v", test.hostHeader, gw, test.gw)
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
func equalError(a, b error) bool {
|
||
return (a == nil && b == nil) || (a != nil && b != nil && a.Error() == b.Error())
|
||
}
|