From 25995dc5d60872b8cb064f7b11dbcbbd799150ae Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Thu, 2 Feb 2023 15:45:20 +0100 Subject: [PATCH] feat: migrate DNSLink and subdomain gateway code to go-libipfs --- core/corehttp/gateway.go | 152 +++++-- core/corehttp/hostname.go | 602 ------------------------- core/corehttp/hostname_test.go | 307 ------------- docs/examples/kubo-as-a-library/go.mod | 2 +- docs/examples/kubo-as-a-library/go.sum | 4 +- go.mod | 2 +- go.sum | 4 +- 7 files changed, 126 insertions(+), 947 deletions(-) delete mode 100644 core/corehttp/hostname.go delete mode 100644 core/corehttp/hostname_test.go diff --git a/core/corehttp/gateway.go b/core/corehttp/gateway.go index 3c7803036936..c20ab6e4a4f5 100644 --- a/core/corehttp/gateway.go +++ b/core/corehttp/gateway.go @@ -11,10 +11,13 @@ import ( "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-libipfs/files" "github.com/ipfs/go-libipfs/gateway" + "github.com/ipfs/go-namesys" iface "github.com/ipfs/interface-go-ipfs-core" options "github.com/ipfs/interface-go-ipfs-core/options" + nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys" "github.com/ipfs/interface-go-ipfs-core/path" version "github.com/ipfs/kubo" + config "github.com/ipfs/kubo/config" core "github.com/ipfs/kubo/core" coreapi "github.com/ipfs/kubo/core/coreapi" id "github.com/libp2p/go-libp2p/p2p/protocol/identify" @@ -40,55 +43,70 @@ func GatewayOption(writable bool, paths ...string) ServeOption { gateway.AddAccessControlHeaders(headers) - offlineAPI, err := api.WithOptions(options.Api.Offline(true)) - if err != nil { - return nil, err - } - - gatewayConfig := gateway.Config{ + gwConfig := gateway.Config{ Headers: headers, } - gatewayAPI := &gatewayAPI{ - api: api, - offlineAPI: offlineAPI, + gwAPI, err := newGatewayAPI(n) + if err != nil { + return nil, err } - gateway := gateway.NewHandler(gatewayConfig, gatewayAPI) - gateway = otelhttp.NewHandler(gateway, "Gateway.Request") + gw := gateway.NewHandler(gwConfig, gwAPI) + gw = otelhttp.NewHandler(gw, "Gateway.Request") - var writableGateway *writableGatewayHandler + // By default, our HTTP handler is the gateway handler. + handler := gw.ServeHTTP + + // If we have the writable gateway enabled, we have to replace our + // http handler by a handler that takes care of the different methods. if writable { - writableGateway = &writableGatewayHandler{ - config: &gatewayConfig, + writableGw := &writableGatewayHandler{ + config: &gwConfig, api: api, } - } - for _, p := range paths { - mux.HandleFunc(p+"/", func(w http.ResponseWriter, r *http.Request) { - if writable { - switch r.Method { - case http.MethodPost: - writableGateway.postHandler(w, r) - case http.MethodDelete: - writableGateway.deleteHandler(w, r) - case http.MethodPut: - writableGateway.putHandler(w, r) - default: - gateway.ServeHTTP(w, r) - } - - return + handler = func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + writableGw.postHandler(w, r) + case http.MethodDelete: + writableGw.deleteHandler(w, r) + case http.MethodPut: + writableGw.putHandler(w, r) + default: + gw.ServeHTTP(w, r) } + } + } - gateway.ServeHTTP(w, r) - }) + for _, p := range paths { + mux.HandleFunc(p+"/", handler) } + return mux, nil } } +func HostnameOption() ServeOption { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { + cfg, err := n.Repo.Config() + if err != nil { + return nil, err + } + + gwAPI, err := newGatewayAPI(n) + if err != nil { + return nil, err + } + + publicGateways := convertPublicGateways(cfg.Gateway.PublicGateways) + childMux := http.NewServeMux() + mux.HandleFunc("/", gateway.WithHostname(childMux, gwAPI, publicGateways, cfg.Gateway.NoDNSLink).ServeHTTP) + return childMux, nil + } +} + func VersionOption() ServeOption { return func(_ *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { @@ -101,10 +119,33 @@ func VersionOption() ServeOption { } type gatewayAPI struct { + ns namesys.NameSystem api iface.CoreAPI offlineAPI iface.CoreAPI } +func newGatewayAPI(n *core.IpfsNode) (*gatewayAPI, error) { + cfg, err := n.Repo.Config() + if err != nil { + return nil, err + } + + api, err := coreapi.NewCoreAPI(n, options.Api.FetchBlocks(!cfg.Gateway.NoFetch)) + if err != nil { + return nil, err + } + offlineAPI, err := api.WithOptions(options.Api.Offline(true)) + if err != nil { + return nil, err + } + + return &gatewayAPI{ + ns: n.Namesys, + api: api, + offlineAPI: offlineAPI, + }, nil +} + func (gw *gatewayAPI) GetUnixFsNode(ctx context.Context, pth path.Resolved) (files.Node, error) { return gw.api.Unixfs().Get(ctx, pth) } @@ -137,6 +178,14 @@ func (gw *gatewayAPI) GetIPNSRecord(ctx context.Context, c cid.Cid) ([]byte, err return gw.api.Routing().Get(ctx, "/ipns/"+c.String()) } +func (gw *gatewayAPI) GetDNSLinkRecord(ctx context.Context, hostname string) (path.Path, error) { + p, err := gw.ns.Resolve(ctx, "/ipns/"+hostname, nsopts.Depth(1)) + if err == namesys.ErrResolveRecursion { + err = nil + } + return path.New(p.String()), err +} + func (gw *gatewayAPI) IsCached(ctx context.Context, pth path.Path) bool { _, err := gw.offlineAPI.Block().Stat(ctx, pth) return err == nil @@ -145,3 +194,42 @@ func (gw *gatewayAPI) IsCached(ctx context.Context, pth path.Path) bool { func (gw *gatewayAPI) ResolvePath(ctx context.Context, pth path.Path) (path.Resolved, error) { return gw.api.ResolvePath(ctx, pth) } + +var defaultPaths = []string{"/ipfs/", "/ipns/", "/api/", "/p2p/"} + +var subdomainGatewaySpec = &gateway.Specification{ + Paths: defaultPaths, + UseSubdomains: true, +} + +var defaultKnownGateways = map[string]*gateway.Specification{ + "localhost": subdomainGatewaySpec, +} + +func convertPublicGateways(publicGateways map[string]*config.GatewaySpec) map[string]*gateway.Specification { + gws := map[string]*gateway.Specification{} + + // First, implicit defaults such as subdomain gateway on localhost + for hostname, gw := range defaultKnownGateways { + gws[hostname] = gw + } + + // Then apply values from Gateway.PublicGateways, if present in the config + for hostname, gw := range publicGateways { + if gw == nil { + // Remove any implicit defaults, if present. This is useful when one + // wants to disable subdomain gateway on localhost etc. + delete(gws, hostname) + continue + } + + gws[hostname] = &gateway.Specification{ + Paths: gw.Paths, + NoDNSLink: gw.NoDNSLink, + UseSubdomains: gw.UseSubdomains, + InlineDNSLink: gw.InlineDNSLink.WithDefault(config.DefaultInlineDNSLink), + } + } + + return gws +} diff --git a/core/corehttp/hostname.go b/core/corehttp/hostname.go deleted file mode 100644 index adc47ab4ddb5..000000000000 --- a/core/corehttp/hostname.go +++ /dev/null @@ -1,602 +0,0 @@ -package corehttp - -import ( - "context" - "fmt" - "net" - "net/http" - "net/url" - "regexp" - "strings" - - cid "github.com/ipfs/go-cid" - "github.com/ipfs/go-libipfs/gateway" - namesys "github.com/ipfs/go-namesys" - core "github.com/ipfs/kubo/core" - coreapi "github.com/ipfs/kubo/core/coreapi" - "github.com/libp2p/go-libp2p/core/peer" - dns "github.com/miekg/dns" - - mbase "github.com/multiformats/go-multibase" - - iface "github.com/ipfs/interface-go-ipfs-core" - options "github.com/ipfs/interface-go-ipfs-core/options" - nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys" - config "github.com/ipfs/kubo/config" -) - -var defaultPaths = []string{"/ipfs/", "/ipns/", "/api/", "/p2p/"} - -var subdomainGatewaySpec = &config.GatewaySpec{ - Paths: defaultPaths, - UseSubdomains: true, -} - -var defaultKnownGateways = map[string]*config.GatewaySpec{ - "localhost": subdomainGatewaySpec, -} - -// Label's max length in DNS (https://tools.ietf.org/html/rfc1034#page-7) -const dnsLabelMaxLength int = 63 - -// HostnameOption rewrites an incoming request based on the Host header. -func HostnameOption() ServeOption { - return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { - childMux := http.NewServeMux() - - coreAPI, err := coreapi.NewCoreAPI(n) - if err != nil { - return nil, err - } - - cfg, err := n.Repo.Config() - if err != nil { - return nil, err - } - - knownGateways := prepareKnownGateways(cfg.Gateway.PublicGateways) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - // Unfortunately, many (well, ipfs.io) gateways use - // DNSLink so if we blindly rewrite with DNSLink, we'll - // break /ipfs links. - // - // We fix this by maintaining a list of known gateways - // and the paths that they serve "gateway" content on. - // That way, we can use DNSLink for everything else. - - // Support X-Forwarded-Host if added by a reverse proxy - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host - host := r.Host - if xHost := r.Header.Get("X-Forwarded-Host"); xHost != "" { - host = xHost - } - - // HTTP Host & Path check: is this one of our "known gateways"? - if gw, ok := isKnownHostname(host, knownGateways); ok { - // This is a known gateway but request is not using - // the subdomain feature. - - // Does this gateway _handle_ this path? - if hasPrefix(r.URL.Path, gw.Paths...) { - // It does. - - // Should this gateway use subdomains instead of paths? - if gw.UseSubdomains { - // Yes, redirect if applicable - // Example: dweb.link/ipfs/{cid} → {cid}.ipfs.dweb.link - useInlinedDNSLink := gw.InlineDNSLink.WithDefault(config.DefaultInlineDNSLink) - newURL, err := toSubdomainURL(host, r.URL.Path, r, useInlinedDNSLink, coreAPI) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if newURL != "" { - // Set "Location" header with redirect destination. - // It is ignored by curl in default mode, but will - // be respected by user agents that follow - // redirects by default, namely web browsers - w.Header().Set("Location", newURL) - - // Note: we continue regular gateway processing: - // HTTP Status Code http.StatusMovedPermanently - // will be set later, in statusResponseWriter - } - } - - // Not a subdomain resource, continue with path processing - // Example: 127.0.0.1:8080/ipfs/{CID}, ipfs.io/ipfs/{CID} etc - childMux.ServeHTTP(w, r) - return - } - // Not a whitelisted path - - // Try DNSLink, if it was not explicitly disabled for the hostname - if !gw.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) { - // rewrite path and handle as DNSLink - r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path - childMux.ServeHTTP(w, withHostnameContext(r, host)) - return - } - - // If not, resource does not exist on the hostname, return 404 - http.NotFound(w, r) - return - } - - // HTTP Host check: is this one of our subdomain-based "known gateways"? - // IPFS details extracted from the host: {rootID}.{ns}.{gwHostname} - // /ipfs/ example: {cid}.ipfs.localhost:8080, {cid}.ipfs.dweb.link - // /ipns/ example: {libp2p-key}.ipns.localhost:8080, {inlined-dnslink-fqdn}.ipns.dweb.link - if gw, gwHostname, ns, rootID, ok := knownSubdomainDetails(host, knownGateways); ok { - // Looks like we're using a known gateway in subdomain mode. - - // Assemble original path prefix. - pathPrefix := "/" + ns + "/" + rootID - - // Retrieve whether or not we should inline DNSLink. - useInlinedDNSLink := gw.InlineDNSLink.WithDefault(config.DefaultInlineDNSLink) - - // Does this gateway _handle_ subdomains AND this path? - if !(gw.UseSubdomains && hasPrefix(pathPrefix, gw.Paths...)) { - // If not, resource does not exist, return 404 - http.NotFound(w, r) - return - } - - // Check if rootID is a valid CID - if rootCID, err := cid.Decode(rootID); err == nil { - // Do we need to redirect root CID to a canonical DNS representation? - dnsCID, err := toDNSLabel(rootID, rootCID) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if !strings.HasPrefix(r.Host, dnsCID) { - dnsPrefix := "/" + ns + "/" + dnsCID - newURL, err := toSubdomainURL(gwHostname, dnsPrefix+r.URL.Path, r, useInlinedDNSLink, coreAPI) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if newURL != "" { - // Redirect to deterministic CID to ensure CID - // always gets the same Origin on the web - http.Redirect(w, r, newURL, http.StatusMovedPermanently) - return - } - } - - // Do we need to fix multicodec in PeerID represented as CIDv1? - if isPeerIDNamespace(ns) { - if rootCID.Type() != cid.Libp2pKey { - newURL, err := toSubdomainURL(gwHostname, pathPrefix+r.URL.Path, r, useInlinedDNSLink, coreAPI) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if newURL != "" { - // Redirect to CID fixed inside of toSubdomainURL() - http.Redirect(w, r, newURL, http.StatusMovedPermanently) - return - } - } - } - } else { // rootID is not a CID.. - - // Check if rootID is a single DNS label with an inlined - // DNSLink FQDN a single DNS label. We support this so - // loading DNSLink names over TLS "just works" on public - // HTTP gateways. - // - // Rationale for doing this can be found under "Option C" - // at: https://github.com/ipfs/in-web-browsers/issues/169 - // - // TLDR is: - // https://dweb.link/ipns/my.v-long.example.com - // can be loaded from a subdomain gateway with a wildcard - // TLS cert if represented as a single DNS label: - // https://my-v--long-example-com.ipns.dweb.link - if ns == "ipns" && !strings.Contains(rootID, ".") { - // if there is no TXT recordfor rootID - if !isDNSLinkName(r.Context(), coreAPI, rootID) { - // my-v--long-example-com → my.v-long.example.com - dnslinkFQDN := toDNSLinkFQDN(rootID) - if isDNSLinkName(r.Context(), coreAPI, dnslinkFQDN) { - // update path prefix to use real FQDN with DNSLink - pathPrefix = "/ipns/" + dnslinkFQDN - } - } - } - } - - // Rewrite the path to not use subdomains - r.URL.Path = pathPrefix + r.URL.Path - - // Serve path request - childMux.ServeHTTP(w, withHostnameContext(r, gwHostname)) - return - } - // We don't have a known gateway. Fallback on DNSLink lookup - - // Wildcard HTTP Host check: - // 1. is wildcard DNSLink enabled (Gateway.NoDNSLink=false)? - // 2. does Host header include a fully qualified domain name (FQDN)? - // 3. does DNSLink record exist in DNS? - if !cfg.Gateway.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) { - // rewrite path and handle as DNSLink - r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path - ctx := context.WithValue(r.Context(), gateway.DNSLinkHostnameKey, host) - childMux.ServeHTTP(w, withHostnameContext(r.WithContext(ctx), host)) - return - } - - // else, treat it as an old school gateway, I guess. - childMux.ServeHTTP(w, r) - }) - return childMux, nil - } -} - -type gatewayHosts struct { - exact map[string]*config.GatewaySpec - wildcard []wildcardHost -} - -type wildcardHost struct { - re *regexp.Regexp - spec *config.GatewaySpec -} - -// Extends request context to include hostname of a canonical gateway root -// (subdomain root or dnslink fqdn) -func withHostnameContext(r *http.Request, hostname string) *http.Request { - // This is required for links on directory listing pages to work correctly - // on subdomain and dnslink gateways. While DNSlink could read value from - // Host header, subdomain gateways have more comples rules (knownSubdomainDetails) - // More: https://github.com/ipfs/dir-index-html/issues/42 - // nolint: staticcheck // non-backward compatible change - ctx := context.WithValue(r.Context(), gateway.GatewayHostnameKey, hostname) - return r.WithContext(ctx) -} - -func prepareKnownGateways(publicGateways map[string]*config.GatewaySpec) gatewayHosts { - var hosts gatewayHosts - - hosts.exact = make(map[string]*config.GatewaySpec, len(publicGateways)+len(defaultKnownGateways)) - - // First, implicit defaults such as subdomain gateway on localhost - for hostname, gw := range defaultKnownGateways { - hosts.exact[hostname] = gw - } - - // Then apply values from Gateway.PublicGateways, if present in the config - for hostname, gw := range publicGateways { - if gw == nil { - // Remove any implicit defaults, if present. This is useful when one - // wants to disable subdomain gateway on localhost etc. - delete(hosts.exact, hostname) - continue - } - if strings.Contains(hostname, "*") { - // from *.domain.tld, construct a regexp that match any direct subdomain - // of .domain.tld. - // - // Regexp will be in the form of ^[^.]+\.domain.tld(?::\d+)?$ - - escaped := strings.ReplaceAll(hostname, ".", `\.`) - regexed := strings.ReplaceAll(escaped, "*", "[^.]+") - - re, err := regexp.Compile(fmt.Sprintf(`^%s(?::\d+)?$`, regexed)) - if err != nil { - log.Warn("invalid wildcard gateway hostname \"%s\"", hostname) - } - - hosts.wildcard = append(hosts.wildcard, wildcardHost{re: re, spec: gw}) - } else { - hosts.exact[hostname] = gw - } - } - - return hosts -} - -// isKnownHostname checks Gateway.PublicGateways and returns matching -// GatewaySpec with graceful fallback to version without port -func isKnownHostname(hostname string, knownGateways gatewayHosts) (gw *config.GatewaySpec, ok bool) { - // Try hostname (host+optional port - value from Host header as-is) - if gw, ok := knownGateways.exact[hostname]; ok { - return gw, ok - } - // Also test without port - if gw, ok = knownGateways.exact[stripPort(hostname)]; ok { - return gw, ok - } - - // Wildcard support. Test both with and without port. - for _, host := range knownGateways.wildcard { - if host.re.MatchString(hostname) { - return host.spec, true - } - } - - return nil, false -} - -// Parses Host header and looks for a known gateway matching subdomain host. -// If found, returns GatewaySpec and subdomain components extracted from Host -// header: {rootID}.{ns}.{gwHostname} -// Note: hostname is host + optional port -func knownSubdomainDetails(hostname string, knownGateways gatewayHosts) (gw *config.GatewaySpec, gwHostname, ns, rootID string, ok bool) { - labels := strings.Split(hostname, ".") - // Look for FQDN of a known gateway hostname. - // Example: given "dist.ipfs.tech.ipns.dweb.link": - // 1. Lookup "link" TLD in knownGateways: negative - // 2. Lookup "dweb.link" in knownGateways: positive - // - // Stops when we have 2 or fewer labels left as we need at least a - // rootId and a namespace. - for i := len(labels) - 1; i >= 2; i-- { - fqdn := strings.Join(labels[i:], ".") - gw, ok := isKnownHostname(fqdn, knownGateways) - if !ok { - continue - } - - ns := labels[i-1] - if !isSubdomainNamespace(ns) { - continue - } - - // Merge remaining labels (could be a FQDN with DNSLink) - rootID := strings.Join(labels[:i-1], ".") - return gw, fqdn, ns, rootID, true - } - // no match - return nil, "", "", "", false -} - -// isDomainNameAndNotPeerID returns bool if string looks like a valid DNS name AND is not a PeerID -func isDomainNameAndNotPeerID(hostname string) bool { - if len(hostname) == 0 { - return false - } - if _, err := peer.Decode(hostname); err == nil { - return false - } - _, ok := dns.IsDomainName(hostname) - return ok -} - -// isDNSLinkName returns bool if a valid DNS TXT record exist for provided host -func isDNSLinkName(ctx context.Context, ipfs iface.CoreAPI, host string) bool { - dnslinkName := stripPort(host) - - if !isDomainNameAndNotPeerID(dnslinkName) { - return false - } - - name := "/ipns/" + dnslinkName - // check if DNSLink exists - depth := options.Name.ResolveOption(nsopts.Depth(1)) - _, err := ipfs.Name().Resolve(ctx, name, depth) - return err == nil || err == namesys.ErrResolveRecursion -} - -func isSubdomainNamespace(ns string) bool { - switch ns { - case "ipfs", "ipns", "p2p", "ipld": - return true - default: - return false - } -} - -func isPeerIDNamespace(ns string) bool { - switch ns { - case "ipns", "p2p": - return true - default: - return false - } -} - -// Converts a CID to DNS-safe representation that fits in 63 characters -func toDNSLabel(rootID string, rootCID cid.Cid) (dnsCID string, err error) { - // Return as-is if things fit - if len(rootID) <= dnsLabelMaxLength { - return rootID, nil - } - - // Convert to Base36 and see if that helped - rootID, err = cid.NewCidV1(rootCID.Type(), rootCID.Hash()).StringOfBase(mbase.Base36) - if err != nil { - return "", err - } - if len(rootID) <= dnsLabelMaxLength { - return rootID, nil - } - - // Can't win with DNS at this point, return error - return "", fmt.Errorf("CID incompatible with DNS label length limit of 63: %s", rootID) -} - -// Returns true if HTTP request involves TLS certificate. -// See https://github.com/ipfs/in-web-browsers/issues/169 to understand how it -// impacts DNSLink websites on public gateways. -func isHTTPSRequest(r *http.Request) bool { - // X-Forwarded-Proto if added by a reverse proxy - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto - xproto := r.Header.Get("X-Forwarded-Proto") - // Is request a native TLS (not used atm, but future-proofing) - // or a proxied HTTPS (eg. go-ipfs behind nginx at a public gw)? - return r.URL.Scheme == "https" || xproto == "https" -} - -// Converts a FQDN to DNS-safe representation that fits in 63 characters: -// my.v-long.example.com → my-v--long-example-com -func toDNSLinkDNSLabel(fqdn string) (dnsLabel string, err error) { - dnsLabel = strings.ReplaceAll(fqdn, "-", "--") - dnsLabel = strings.ReplaceAll(dnsLabel, ".", "-") - if len(dnsLabel) > dnsLabelMaxLength { - return "", fmt.Errorf("DNSLink representation incompatible with DNS label length limit of 63: %s", dnsLabel) - } - return dnsLabel, nil -} - -// Converts a DNS-safe representation of DNSLink FQDN to real FQDN: -// my-v--long-example-com → my.v-long.example.com -func toDNSLinkFQDN(dnsLabel string) (fqdn string) { - fqdn = strings.ReplaceAll(dnsLabel, "--", "@") // @ placeholder is unused in DNS labels - fqdn = strings.ReplaceAll(fqdn, "-", ".") - fqdn = strings.ReplaceAll(fqdn, "@", "-") - return fqdn -} - -// Converts a hostname/path to a subdomain-based URL, if applicable. -func toSubdomainURL(hostname, path string, r *http.Request, inlineDNSLink bool, ipfs iface.CoreAPI) (redirURL string, err error) { - var scheme, ns, rootID, rest string - - query := r.URL.RawQuery - parts := strings.SplitN(path, "/", 4) - isHTTPS := isHTTPSRequest(r) - safeRedirectURL := func(in string) (out string, err error) { - safeURI, err := url.ParseRequestURI(in) - if err != nil { - return "", err - } - return safeURI.String(), nil - } - - if isHTTPS { - scheme = "https:" - } else { - scheme = "http:" - } - - switch len(parts) { - case 4: - rest = parts[3] - fallthrough - case 3: - ns = parts[1] - rootID = parts[2] - default: - return "", nil - } - - if !isSubdomainNamespace(ns) { - return "", nil - } - - // add prefix if query is present - if query != "" { - query = "?" + query - } - - // Normalize problematic PeerIDs (eg. ed25519+identity) to CID representation - if isPeerIDNamespace(ns) && !isDomainNameAndNotPeerID(rootID) { - peerID, err := peer.Decode(rootID) - // Note: PeerID CIDv1 with protobuf multicodec will fail, but we fix it - // in the next block - if err == nil { - rootID = peer.ToCid(peerID).String() - } - } - - // If rootID is a CID, ensure it uses DNS-friendly text representation - if rootCID, err := cid.Decode(rootID); err == nil { - multicodec := rootCID.Type() - var base mbase.Encoding = mbase.Base32 - - // Normalizations specific to /ipns/{libp2p-key} - if isPeerIDNamespace(ns) { - // Using Base36 for /ipns/ for consistency - // Context: https://github.com/ipfs/kubo/pull/7441#discussion_r452372828 - base = mbase.Base36 - - // PeerIDs represented as CIDv1 are expected to have libp2p-key - // multicodec (https://github.com/libp2p/specs/pull/209). - // We ease the transition by fixing multicodec on the fly: - // https://github.com/ipfs/kubo/issues/5287#issuecomment-492163929 - if multicodec != cid.Libp2pKey { - multicodec = cid.Libp2pKey - } - } - - // Ensure CID text representation used in subdomain is compatible - // with the way DNS and URIs are implemented in user agents. - // - // 1. Switch to CIDv1 and enable case-insensitive Base encoding - // to avoid issues when user agent force-lowercases the hostname - // before making the request - // (https://github.com/ipfs/in-web-browsers/issues/89) - rootCID = cid.NewCidV1(multicodec, rootCID.Hash()) - rootID, err = rootCID.StringOfBase(base) - if err != nil { - return "", err - } - // 2. Make sure CID fits in a DNS label, adjust encoding if needed - // (https://github.com/ipfs/kubo/issues/7318) - rootID, err = toDNSLabel(rootID, rootCID) - if err != nil { - return "", err - } - } else { // rootID is not a CID - - // Check if rootID is a FQDN with DNSLink and convert it to TLS-safe - // representation that fits in a single DNS label. We support this so - // loading DNSLink names over TLS "just works" on public HTTP gateways - // that pass 'https' in X-Forwarded-Proto to go-ipfs. - // - // Rationale can be found under "Option C" - // at: https://github.com/ipfs/in-web-browsers/issues/169 - // - // TLDR is: - // /ipns/my.v-long.example.com - // can be loaded from a subdomain gateway with a wildcard TLS cert if - // represented as a single DNS label: - // https://my-v--long-example-com.ipns.dweb.link - if (inlineDNSLink || isHTTPS) && ns == "ipns" && strings.Contains(rootID, ".") { - if isDNSLinkName(r.Context(), ipfs, rootID) { - // my.v-long.example.com → my-v--long-example-com - dnsLabel, err := toDNSLinkDNSLabel(rootID) - if err != nil { - return "", err - } - // update path prefix to use real FQDN with DNSLink - rootID = dnsLabel - } - } - } - - return safeRedirectURL(fmt.Sprintf( - "%s//%s.%s.%s/%s%s", - scheme, - rootID, - ns, - hostname, - rest, - query, - )) -} - -func hasPrefix(path string, prefixes ...string) bool { - for _, prefix := range prefixes { - // Assume people are creative with trailing slashes in Gateway config - p := strings.TrimSuffix(prefix, "/") - // Support for both /version and /ipfs/$cid - if p == path || strings.HasPrefix(path, p+"/") { - return true - } - } - return false -} - -func stripPort(hostname string) string { - host, _, err := net.SplitHostPort(hostname) - if err == nil { - return host - } - return hostname -} diff --git a/core/corehttp/hostname_test.go b/core/corehttp/hostname_test.go deleted file mode 100644 index b4a8b8d1643b..000000000000 --- a/core/corehttp/hostname_test.go +++ /dev/null @@ -1,307 +0,0 @@ -package corehttp - -import ( - "errors" - "net/http" - "net/http/httptest" - "testing" - - cid "github.com/ipfs/go-cid" - "github.com/ipfs/go-libipfs/files" - path "github.com/ipfs/go-path" - config "github.com/ipfs/kubo/config" - coreapi "github.com/ipfs/kubo/core/coreapi" -) - -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 - inlineDNSLink bool - path string - // out: - url string - err error - }{ - // DNSLink - {httpRequest, "localhost", false, "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost/", nil}, - // Hostname with port - {httpRequest, "localhost:8080", false, "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost:8080/", nil}, - // CIDv0 → CIDv1base32 - {httpRequest, "localhost", false, "/ipfs/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "http://bafybeif7a7gdklt6hodwdrmwmxnhksctcuav6lfxlcyfz4khzl3qfmvcgu.ipfs.localhost/", nil}, - // CIDv1 with long sha512 - {httpRequest, "localhost", false, "/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", false, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", "http://k2k4r8n0flx3ra0y5dr8fmyvwbzy3eiztmtq6th694k5a3rznayp3e4o.ipns.localhost/", nil}, - {httpRequest, "localhost", false, "/ipns/bafybeickencdqw37dpz3ha36ewrh4undfjt2do52chtcky4rxkj447qhdm", "http://k2k4r8l9ja7hkzynavdqup76ou46tnvuaqegbd04a4o1mpbsey0meucb.ipns.localhost/", nil}, - // PeerID: ed25519+identity multihash → CIDv1Base36 - {httpRequest, "localhost", false, "/ipns/12D3KooWFB51PRY9BxcXSH6khFXw1BZeszeLDy7C8GciskqCTZn5", "http://k51qzi5uqu5di608geewp3nqkg0bpujoasmka7ftkyxgcm3fh1aroup0gsdrna.ipns.localhost/", nil}, - {httpRequest, "sub.localhost", false, "/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", false, "/ipns/dnslink.long-name.example.com", "http://dnslink.long-name.example.com.ipns.dweb.link/", nil}, - {httpsRequest, "dweb.link", false, "/ipns/dnslink.long-name.example.com", "https://dnslink-long--name-example-com.ipns.dweb.link/", nil}, - {httpsProxiedRequest, "dweb.link", false, "/ipns/dnslink.long-name.example.com", "https://dnslink-long--name-example-com.ipns.dweb.link/", nil}, - // HTTP requests can also be converted to fit into a single DNS label - https://github.com/ipfs/kubo/issues/9243 - {httpRequest, "localhost", true, "/ipns/dnslink.long-name.example.com", "http://dnslink-long--name-example-com.ipns.localhost/", nil}, - {httpRequest, "dweb.link", true, "/ipns/dnslink.long-name.example.com", "http://dnslink-long--name-example-com.ipns.dweb.link/", nil}, - } { - url, err := toSubdomainURL(test.gwHostname, test.path, test.request, test.inlineDNSLink, coreAPI) - if url != test.url || !equalError(err, test.err) { - t.Errorf("(%s, %v, %s) returned (%s, %v), expected (%s, %v)", test.gwHostname, test.inlineDNSLink, 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-tech", "docs.ipfs.tech"}, - {"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, - "devgateway.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}, - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.devgateway.dweb.link", gwDweb, "devgateway.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.tech.ipns.localhost:8080", gwLocalhost, "localhost:8080", "ipns", "dist.ipfs.tech", 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()) -} diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod index 2b739db84ae4..f100309996ac 100644 --- a/docs/examples/kubo-as-a-library/go.mod +++ b/docs/examples/kubo-as-a-library/go.mod @@ -7,7 +7,7 @@ go 1.18 replace github.com/ipfs/kubo => ./../../.. require ( - github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c + github.com/ipfs/go-libipfs v0.4.1-0.20230203104224-13b6f58f2846 github.com/ipfs/interface-go-ipfs-core v0.10.0 github.com/ipfs/kubo v0.0.0-00010101000000-000000000000 github.com/libp2p/go-libp2p v0.24.2 diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index f286c50add6c..852415b5b267 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -548,8 +548,8 @@ github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2 github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg= github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A= github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= -github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c h1:Z8GrWoG3VZWj0RvHnzKlyIyXh8sgCIw62O9t3jnhqyk= -github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c/go.mod h1:S5wg08D/FkeYxeMf8adgt6Mi6ttbA7kSFcQYlmeGHMU= +github.com/ipfs/go-libipfs v0.4.1-0.20230203104224-13b6f58f2846 h1:cF5ZQJMarBKalwkyxQcR5x4ApK9HPVhqkw8DCnmsD8c= +github.com/ipfs/go-libipfs v0.4.1-0.20230203104224-13b6f58f2846/go.mod h1:XKRXmSlJ32qlpxGN+mdGJJXUl/Z055etN1xpMEaANQ8= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= diff --git a/go.mod b/go.mod index cf7a72f75343..dca89981b92a 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/ipfs/go-ipld-git v0.1.1 github.com/ipfs/go-ipld-legacy v0.1.1 github.com/ipfs/go-ipns v0.3.0 - github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c + github.com/ipfs/go-libipfs v0.4.1-0.20230206093736-276ccbab8543 github.com/ipfs/go-log v1.0.5 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.9.0 diff --git a/go.sum b/go.sum index 1243d7896b5f..c22c92ab8263 100644 --- a/go.sum +++ b/go.sum @@ -570,8 +570,8 @@ github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2 github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg= github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A= github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= -github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c h1:Z8GrWoG3VZWj0RvHnzKlyIyXh8sgCIw62O9t3jnhqyk= -github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c/go.mod h1:S5wg08D/FkeYxeMf8adgt6Mi6ttbA7kSFcQYlmeGHMU= +github.com/ipfs/go-libipfs v0.4.1-0.20230206093736-276ccbab8543 h1:v+6lfj/WfdEIr/GEc76pQ0j5ZTR+jceQzyNhrMaKQB8= +github.com/ipfs/go-libipfs v0.4.1-0.20230206093736-276ccbab8543/go.mod h1:XKRXmSlJ32qlpxGN+mdGJJXUl/Z055etN1xpMEaANQ8= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A=