1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-08-06 11:31:54 +08:00
Files
kubo/core/corehttp/hostname_test.go
Marcin Rataj 28d4d9b327 refactor: add isDomainNameAndNotPeerID
This ensures we exclude valid PeerIDs from code paths that require
DNSLink names.

Ref.
https://github.com/ipfs/go-ipfs/pull/8071#pullrequestreview-639409245
2021-05-13 10:54:40 -04:00

302 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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())
}