@cryptotaxi247 / kubo / commits / b58356939

refactor(gw): move Host (DNSLink and subdomain) handling to go-libipfs (#9624)

Co-authored-by: Marcin Rataj <lidel@lidel.org>

Henrique Dias committed Feb 7, 2023 at 03:44 UTC b58356939e92729ab34e6c5e2615c67c7c4c1292
7 files changed +126 -947
core/corehttp/gateway.go
+120 -32
@@ -11,10 +11,13 @@ import (
11 "github.com/ipfs/go-libipfs/blocks"
12 "github.com/ipfs/go-libipfs/files"
13 "github.com/ipfs/go-libipfs/gateway"
14 + "github.com/ipfs/go-namesys"
15 iface "github.com/ipfs/interface-go-ipfs-core"
16 options "github.com/ipfs/interface-go-ipfs-core/options"
17 + nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
18 "github.com/ipfs/interface-go-ipfs-core/path"
19 version "github.com/ipfs/kubo"
20 + config "github.com/ipfs/kubo/config"
21 core "github.com/ipfs/kubo/core"
22 coreapi "github.com/ipfs/kubo/core/coreapi"
23 id "github.com/libp2p/go-libp2p/p2p/protocol/identify"
@@ -40,55 +43,70 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
43
44 gateway.AddAccessControlHeaders(headers)
45
43 - offlineAPI, err := api.WithOptions(options.Api.Offline(true))
44 - if err != nil {
45 - return nil, err
46 - }
47 -
48 - gatewayConfig := gateway.Config{
46 + gwConfig := gateway.Config{
47 Headers: headers,
48 }
49
52 - gatewayAPI := &gatewayAPI{
53 - api: api,
54 - offlineAPI: offlineAPI,
50 + gwAPI, err := newGatewayAPI(n)
51 + if err != nil {
52 + return nil, err
53 }
54
57 - gateway := gateway.NewHandler(gatewayConfig, gatewayAPI)
58 - gateway = otelhttp.NewHandler(gateway, "Gateway.Request")
55 + gw := gateway.NewHandler(gwConfig, gwAPI)
56 + gw = otelhttp.NewHandler(gw, "Gateway.Request")
57
60 - var writableGateway *writableGatewayHandler
58 + // By default, our HTTP handler is the gateway handler.
59 + handler := gw.ServeHTTP
60 +
61 + // If we have the writable gateway enabled, we have to replace our
62 + // http handler by a handler that takes care of the different methods.
63 if writable {
62 - writableGateway = &writableGatewayHandler{
63 - config: &gatewayConfig,
64 + writableGw := &writableGatewayHandler{
65 + config: &gwConfig,
66 api: api,
67 }
66 - }
68
68 - for _, p := range paths {
69 - mux.HandleFunc(p+"/", func(w http.ResponseWriter, r *http.Request) {
70 - if writable {
71 - switch r.Method {
72 - case http.MethodPost:
73 - writableGateway.postHandler(w, r)
74 - case http.MethodDelete:
75 - writableGateway.deleteHandler(w, r)
76 - case http.MethodPut:
77 - writableGateway.putHandler(w, r)
78 - default:
79 - gateway.ServeHTTP(w, r)
80 - }
81 -
82 - return
69 + handler = func(w http.ResponseWriter, r *http.Request) {
70 + switch r.Method {
71 + case http.MethodPost:
72 + writableGw.postHandler(w, r)
73 + case http.MethodDelete:
74 + writableGw.deleteHandler(w, r)
75 + case http.MethodPut:
76 + writableGw.putHandler(w, r)
77 + default:
78 + gw.ServeHTTP(w, r)
79 }
80 + }
81 + }
82
85 - gateway.ServeHTTP(w, r)
86 - })
83 + for _, p := range paths {
84 + mux.HandleFunc(p+"/", handler)
85 }
86 +
87 return mux, nil
88 }
89 }
90
91 +func HostnameOption() ServeOption {
92 + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
93 + cfg, err := n.Repo.Config()
94 + if err != nil {
95 + return nil, err
96 + }
97 +
98 + gwAPI, err := newGatewayAPI(n)
99 + if err != nil {
100 + return nil, err
101 + }
102 +
103 + publicGateways := convertPublicGateways(cfg.Gateway.PublicGateways)
104 + childMux := http.NewServeMux()
105 + mux.HandleFunc("/", gateway.WithHostname(childMux, gwAPI, publicGateways, cfg.Gateway.NoDNSLink).ServeHTTP)
106 + return childMux, nil
107 + }
108 +}
109 +
110 func VersionOption() ServeOption {
111 return func(_ *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
112 mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
@@ -101,10 +119,33 @@ func VersionOption() ServeOption {
119 }
120
121 type gatewayAPI struct {
122 + ns namesys.NameSystem
123 api iface.CoreAPI
124 offlineAPI iface.CoreAPI
125 }
126
127 +func newGatewayAPI(n *core.IpfsNode) (*gatewayAPI, error) {
128 + cfg, err := n.Repo.Config()
129 + if err != nil {
130 + return nil, err
131 + }
132 +
133 + api, err := coreapi.NewCoreAPI(n, options.Api.FetchBlocks(!cfg.Gateway.NoFetch))
134 + if err != nil {
135 + return nil, err
136 + }
137 + offlineAPI, err := api.WithOptions(options.Api.Offline(true))
138 + if err != nil {
139 + return nil, err
140 + }
141 +
142 + return &gatewayAPI{
143 + ns: n.Namesys,
144 + api: api,
145 + offlineAPI: offlineAPI,
146 + }, nil
147 +}
148 +
149 func (gw *gatewayAPI) GetUnixFsNode(ctx context.Context, pth path.Resolved) (files.Node, error) {
150 return gw.api.Unixfs().Get(ctx, pth)
151 }
@@ -137,6 +178,14 @@ func (gw *gatewayAPI) GetIPNSRecord(ctx context.Context, c cid.Cid) ([]byte, err
178 return gw.api.Routing().Get(ctx, "/ipns/"+c.String())
179 }
180
181 +func (gw *gatewayAPI) GetDNSLinkRecord(ctx context.Context, hostname string) (path.Path, error) {
182 + p, err := gw.ns.Resolve(ctx, "/ipns/"+hostname, nsopts.Depth(1))
183 + if err == namesys.ErrResolveRecursion {
184 + err = nil
185 + }
186 + return path.New(p.String()), err
187 +}
188 +
189 func (gw *gatewayAPI) IsCached(ctx context.Context, pth path.Path) bool {
190 _, err := gw.offlineAPI.Block().Stat(ctx, pth)
191 return err == nil
@@ -145,3 +194,42 @@ func (gw *gatewayAPI) IsCached(ctx context.Context, pth path.Path) bool {
194 func (gw *gatewayAPI) ResolvePath(ctx context.Context, pth path.Path) (path.Resolved, error) {
195 return gw.api.ResolvePath(ctx, pth)
196 }
197 +
198 +var defaultPaths = []string{"/ipfs/", "/ipns/", "/api/", "/p2p/"}
199 +
200 +var subdomainGatewaySpec = &gateway.Specification{
201 + Paths: defaultPaths,
202 + UseSubdomains: true,
203 +}
204 +
205 +var defaultKnownGateways = map[string]*gateway.Specification{
206 + "localhost": subdomainGatewaySpec,
207 +}
208 +
209 +func convertPublicGateways(publicGateways map[string]*config.GatewaySpec) map[string]*gateway.Specification {
210 + gws := map[string]*gateway.Specification{}
211 +
212 + // First, implicit defaults such as subdomain gateway on localhost
213 + for hostname, gw := range defaultKnownGateways {
214 + gws[hostname] = gw
215 + }
216 +
217 + // Then apply values from Gateway.PublicGateways, if present in the config
218 + for hostname, gw := range publicGateways {
219 + if gw == nil {
220 + // Remove any implicit defaults, if present. This is useful when one
221 + // wants to disable subdomain gateway on localhost etc.
222 + delete(gws, hostname)
223 + continue
224 + }
225 +
226 + gws[hostname] = &gateway.Specification{
227 + Paths: gw.Paths,
228 + NoDNSLink: gw.NoDNSLink,
229 + UseSubdomains: gw.UseSubdomains,
230 + InlineDNSLink: gw.InlineDNSLink.WithDefault(config.DefaultInlineDNSLink),
231 + }
232 + }
233 +
234 + return gws
235 +}
core/corehttp/hostname.go deleted
-602
@@ -1,602 +0,0 @@
1 -package corehttp
2 -
3 -import (
4 - "context"
5 - "fmt"
6 - "net"
7 - "net/http"
8 - "net/url"
9 - "regexp"
10 - "strings"
11 -
12 - cid "github.com/ipfs/go-cid"
13 - "github.com/ipfs/go-libipfs/gateway"
14 - namesys "github.com/ipfs/go-namesys"
15 - core "github.com/ipfs/kubo/core"
16 - coreapi "github.com/ipfs/kubo/core/coreapi"
17 - "github.com/libp2p/go-libp2p/core/peer"
18 - dns "github.com/miekg/dns"
19 -
20 - mbase "github.com/multiformats/go-multibase"
21 -
22 - iface "github.com/ipfs/interface-go-ipfs-core"
23 - options "github.com/ipfs/interface-go-ipfs-core/options"
24 - nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
25 - config "github.com/ipfs/kubo/config"
26 -)
27 -
28 -var defaultPaths = []string{"/ipfs/", "/ipns/", "/api/", "/p2p/"}
29 -
30 -var subdomainGatewaySpec = &config.GatewaySpec{
31 - Paths: defaultPaths,
32 - UseSubdomains: true,
33 -}
34 -
35 -var defaultKnownGateways = map[string]*config.GatewaySpec{
36 - "localhost": subdomainGatewaySpec,
37 -}
38 -
39 -// Label's max length in DNS (https://tools.ietf.org/html/rfc1034#page-7)
40 -const dnsLabelMaxLength int = 63
41 -
42 -// HostnameOption rewrites an incoming request based on the Host header.
43 -func HostnameOption() ServeOption {
44 - return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
45 - childMux := http.NewServeMux()
46 -
47 - coreAPI, err := coreapi.NewCoreAPI(n)
48 - if err != nil {
49 - return nil, err
50 - }
51 -
52 - cfg, err := n.Repo.Config()
53 - if err != nil {
54 - return nil, err
55 - }
56 -
57 - knownGateways := prepareKnownGateways(cfg.Gateway.PublicGateways)
58 -
59 - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
60 - // Unfortunately, many (well, ipfs.io) gateways use
61 - // DNSLink so if we blindly rewrite with DNSLink, we'll
62 - // break /ipfs links.
63 - //
64 - // We fix this by maintaining a list of known gateways
65 - // and the paths that they serve "gateway" content on.
66 - // That way, we can use DNSLink for everything else.
67 -
68 - // Support X-Forwarded-Host if added by a reverse proxy
69 - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
70 - host := r.Host
71 - if xHost := r.Header.Get("X-Forwarded-Host"); xHost != "" {
72 - host = xHost
73 - }
74 -
75 - // HTTP Host & Path check: is this one of our "known gateways"?
76 - if gw, ok := isKnownHostname(host, knownGateways); ok {
77 - // This is a known gateway but request is not using
78 - // the subdomain feature.
79 -
80 - // Does this gateway _handle_ this path?
81 - if hasPrefix(r.URL.Path, gw.Paths...) {
82 - // It does.
83 -
84 - // Should this gateway use subdomains instead of paths?
85 - if gw.UseSubdomains {
86 - // Yes, redirect if applicable
87 - // Example: dweb.link/ipfs/{cid} → {cid}.ipfs.dweb.link
88 - useInlinedDNSLink := gw.InlineDNSLink.WithDefault(config.DefaultInlineDNSLink)
89 - newURL, err := toSubdomainURL(host, r.URL.Path, r, useInlinedDNSLink, coreAPI)
90 - if err != nil {
91 - http.Error(w, err.Error(), http.StatusBadRequest)
92 - return
93 - }
94 - if newURL != "" {
95 - // Set "Location" header with redirect destination.
96 - // It is ignored by curl in default mode, but will
97 - // be respected by user agents that follow
98 - // redirects by default, namely web browsers
99 - w.Header().Set("Location", newURL)
100 -
101 - // Note: we continue regular gateway processing:
102 - // HTTP Status Code http.StatusMovedPermanently
103 - // will be set later, in statusResponseWriter
104 - }
105 - }
106 -
107 - // Not a subdomain resource, continue with path processing
108 - // Example: 127.0.0.1:8080/ipfs/{CID}, ipfs.io/ipfs/{CID} etc
109 - childMux.ServeHTTP(w, r)
110 - return
111 - }
112 - // Not a whitelisted path
113 -
114 - // Try DNSLink, if it was not explicitly disabled for the hostname
115 - if !gw.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) {
116 - // rewrite path and handle as DNSLink
117 - r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
118 - childMux.ServeHTTP(w, withHostnameContext(r, host))
119 - return
120 - }
121 -
122 - // If not, resource does not exist on the hostname, return 404
123 - http.NotFound(w, r)
124 - return
125 - }
126 -
127 - // HTTP Host check: is this one of our subdomain-based "known gateways"?
128 - // IPFS details extracted from the host: {rootID}.{ns}.{gwHostname}
129 - // /ipfs/ example: {cid}.ipfs.localhost:8080, {cid}.ipfs.dweb.link
130 - // /ipns/ example: {libp2p-key}.ipns.localhost:8080, {inlined-dnslink-fqdn}.ipns.dweb.link
131 - if gw, gwHostname, ns, rootID, ok := knownSubdomainDetails(host, knownGateways); ok {
132 - // Looks like we're using a known gateway in subdomain mode.
133 -
134 - // Assemble original path prefix.
135 - pathPrefix := "/" + ns + "/" + rootID
136 -
137 - // Retrieve whether or not we should inline DNSLink.
138 - useInlinedDNSLink := gw.InlineDNSLink.WithDefault(config.DefaultInlineDNSLink)
139 -
140 - // Does this gateway _handle_ subdomains AND this path?
141 - if !(gw.UseSubdomains && hasPrefix(pathPrefix, gw.Paths...)) {
142 - // If not, resource does not exist, return 404
143 - http.NotFound(w, r)
144 - return
145 - }
146 -
147 - // Check if rootID is a valid CID
148 - if rootCID, err := cid.Decode(rootID); err == nil {
149 - // Do we need to redirect root CID to a canonical DNS representation?
150 - dnsCID, err := toDNSLabel(rootID, rootCID)
151 - if err != nil {
152 - http.Error(w, err.Error(), http.StatusBadRequest)
153 - return
154 - }
155 - if !strings.HasPrefix(r.Host, dnsCID) {
156 - dnsPrefix := "/" + ns + "/" + dnsCID
157 - newURL, err := toSubdomainURL(gwHostname, dnsPrefix+r.URL.Path, r, useInlinedDNSLink, coreAPI)
158 - if err != nil {
159 - http.Error(w, err.Error(), http.StatusBadRequest)
160 - return
161 - }
162 - if newURL != "" {
163 - // Redirect to deterministic CID to ensure CID
164 - // always gets the same Origin on the web
165 - http.Redirect(w, r, newURL, http.StatusMovedPermanently)
166 - return
167 - }
168 - }
169 -
170 - // Do we need to fix multicodec in PeerID represented as CIDv1?
171 - if isPeerIDNamespace(ns) {
172 - if rootCID.Type() != cid.Libp2pKey {
173 - newURL, err := toSubdomainURL(gwHostname, pathPrefix+r.URL.Path, r, useInlinedDNSLink, coreAPI)
174 - if err != nil {
175 - http.Error(w, err.Error(), http.StatusBadRequest)
176 - return
177 - }
178 - if newURL != "" {
179 - // Redirect to CID fixed inside of toSubdomainURL()
180 - http.Redirect(w, r, newURL, http.StatusMovedPermanently)
181 - return
182 - }
183 - }
184 - }
185 - } else { // rootID is not a CID..
186 -
187 - // Check if rootID is a single DNS label with an inlined
188 - // DNSLink FQDN a single DNS label. We support this so
189 - // loading DNSLink names over TLS "just works" on public
190 - // HTTP gateways.
191 - //
192 - // Rationale for doing this can be found under "Option C"
193 - // at: https://github.com/ipfs/in-web-browsers/issues/169
194 - //
195 - // TLDR is:
196 - // https://dweb.link/ipns/my.v-long.example.com
197 - // can be loaded from a subdomain gateway with a wildcard
198 - // TLS cert if represented as a single DNS label:
199 - // https://my-v--long-example-com.ipns.dweb.link
200 - if ns == "ipns" && !strings.Contains(rootID, ".") {
201 - // if there is no TXT recordfor rootID
202 - if !isDNSLinkName(r.Context(), coreAPI, rootID) {
203 - // my-v--long-example-com → my.v-long.example.com
204 - dnslinkFQDN := toDNSLinkFQDN(rootID)
205 - if isDNSLinkName(r.Context(), coreAPI, dnslinkFQDN) {
206 - // update path prefix to use real FQDN with DNSLink
207 - pathPrefix = "/ipns/" + dnslinkFQDN
208 - }
209 - }
210 - }
211 - }
212 -
213 - // Rewrite the path to not use subdomains
214 - r.URL.Path = pathPrefix + r.URL.Path
215 -
216 - // Serve path request
217 - childMux.ServeHTTP(w, withHostnameContext(r, gwHostname))
218 - return
219 - }
220 - // We don't have a known gateway. Fallback on DNSLink lookup
221 -
222 - // Wildcard HTTP Host check:
223 - // 1. is wildcard DNSLink enabled (Gateway.NoDNSLink=false)?
224 - // 2. does Host header include a fully qualified domain name (FQDN)?
225 - // 3. does DNSLink record exist in DNS?
226 - if !cfg.Gateway.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) {
227 - // rewrite path and handle as DNSLink
228 - r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
229 - ctx := context.WithValue(r.Context(), gateway.DNSLinkHostnameKey, host)
230 - childMux.ServeHTTP(w, withHostnameContext(r.WithContext(ctx), host))
231 - return
232 - }
233 -
234 - // else, treat it as an old school gateway, I guess.
235 - childMux.ServeHTTP(w, r)
236 - })
237 - return childMux, nil
238 - }
239 -}
240 -
241 -type gatewayHosts struct {
242 - exact map[string]*config.GatewaySpec
243 - wildcard []wildcardHost
244 -}
245 -
246 -type wildcardHost struct {
247 - re *regexp.Regexp
248 - spec *config.GatewaySpec
249 -}
250 -
251 -// Extends request context to include hostname of a canonical gateway root
252 -// (subdomain root or dnslink fqdn)
253 -func withHostnameContext(r *http.Request, hostname string) *http.Request {
254 - // This is required for links on directory listing pages to work correctly
255 - // on subdomain and dnslink gateways. While DNSlink could read value from
256 - // Host header, subdomain gateways have more comples rules (knownSubdomainDetails)
257 - // More: https://github.com/ipfs/dir-index-html/issues/42
258 - // nolint: staticcheck // non-backward compatible change
259 - ctx := context.WithValue(r.Context(), gateway.GatewayHostnameKey, hostname)
260 - return r.WithContext(ctx)
261 -}
262 -
263 -func prepareKnownGateways(publicGateways map[string]*config.GatewaySpec) gatewayHosts {
264 - var hosts gatewayHosts
265 -
266 - hosts.exact = make(map[string]*config.GatewaySpec, len(publicGateways)+len(defaultKnownGateways))
267 -
268 - // First, implicit defaults such as subdomain gateway on localhost
269 - for hostname, gw := range defaultKnownGateways {
270 - hosts.exact[hostname] = gw
271 - }
272 -
273 - // Then apply values from Gateway.PublicGateways, if present in the config
274 - for hostname, gw := range publicGateways {
275 - if gw == nil {
276 - // Remove any implicit defaults, if present. This is useful when one
277 - // wants to disable subdomain gateway on localhost etc.
278 - delete(hosts.exact, hostname)
279 - continue
280 - }
281 - if strings.Contains(hostname, "*") {
282 - // from *.domain.tld, construct a regexp that match any direct subdomain
283 - // of .domain.tld.
284 - //
285 - // Regexp will be in the form of ^[^.]+\.domain.tld(?::\d+)?$
286 -
287 - escaped := strings.ReplaceAll(hostname, ".", `\.`)
288 - regexed := strings.ReplaceAll(escaped, "*", "[^.]+")
289 -
290 - re, err := regexp.Compile(fmt.Sprintf(`^%s(?::\d+)?$`, regexed))
291 - if err != nil {
292 - log.Warn("invalid wildcard gateway hostname \"%s\"", hostname)
293 - }
294 -
295 - hosts.wildcard = append(hosts.wildcard, wildcardHost{re: re, spec: gw})
296 - } else {
297 - hosts.exact[hostname] = gw
298 - }
299 - }
300 -
301 - return hosts
302 -}
303 -
304 -// isKnownHostname checks Gateway.PublicGateways and returns matching
305 -// GatewaySpec with graceful fallback to version without port
306 -func isKnownHostname(hostname string, knownGateways gatewayHosts) (gw *config.GatewaySpec, ok bool) {
307 - // Try hostname (host+optional port - value from Host header as-is)
308 - if gw, ok := knownGateways.exact[hostname]; ok {
309 - return gw, ok
310 - }
311 - // Also test without port
312 - if gw, ok = knownGateways.exact[stripPort(hostname)]; ok {
313 - return gw, ok
314 - }
315 -
316 - // Wildcard support. Test both with and without port.
317 - for _, host := range knownGateways.wildcard {
318 - if host.re.MatchString(hostname) {
319 - return host.spec, true
320 - }
321 - }
322 -
323 - return nil, false
324 -}
325 -
326 -// Parses Host header and looks for a known gateway matching subdomain host.
327 -// If found, returns GatewaySpec and subdomain components extracted from Host
328 -// header: {rootID}.{ns}.{gwHostname}
329 -// Note: hostname is host + optional port
330 -func knownSubdomainDetails(hostname string, knownGateways gatewayHosts) (gw *config.GatewaySpec, gwHostname, ns, rootID string, ok bool) {
331 - labels := strings.Split(hostname, ".")
332 - // Look for FQDN of a known gateway hostname.
333 - // Example: given "dist.ipfs.tech.ipns.dweb.link":
334 - // 1. Lookup "link" TLD in knownGateways: negative
335 - // 2. Lookup "dweb.link" in knownGateways: positive
336 - //
337 - // Stops when we have 2 or fewer labels left as we need at least a
338 - // rootId and a namespace.
339 - for i := len(labels) - 1; i >= 2; i-- {
340 - fqdn := strings.Join(labels[i:], ".")
341 - gw, ok := isKnownHostname(fqdn, knownGateways)
342 - if !ok {
343 - continue
344 - }
345 -
346 - ns := labels[i-1]
347 - if !isSubdomainNamespace(ns) {
348 - continue
349 - }
350 -
351 - // Merge remaining labels (could be a FQDN with DNSLink)
352 - rootID := strings.Join(labels[:i-1], ".")
353 - return gw, fqdn, ns, rootID, true
354 - }
355 - // no match
356 - return nil, "", "", "", false
357 -}
358 -
359 -// isDomainNameAndNotPeerID returns bool if string looks like a valid DNS name AND is not a PeerID
360 -func isDomainNameAndNotPeerID(hostname string) bool {
361 - if len(hostname) == 0 {
362 - return false
363 - }
364 - if _, err := peer.Decode(hostname); err == nil {
365 - return false
366 - }
367 - _, ok := dns.IsDomainName(hostname)
368 - return ok
369 -}
370 -
371 -// isDNSLinkName returns bool if a valid DNS TXT record exist for provided host
372 -func isDNSLinkName(ctx context.Context, ipfs iface.CoreAPI, host string) bool {
373 - dnslinkName := stripPort(host)
374 -
375 - if !isDomainNameAndNotPeerID(dnslinkName) {
376 - return false
377 - }
378 -
379 - name := "/ipns/" + dnslinkName
380 - // check if DNSLink exists
381 - depth := options.Name.ResolveOption(nsopts.Depth(1))
382 - _, err := ipfs.Name().Resolve(ctx, name, depth)
383 - return err == nil || err == namesys.ErrResolveRecursion
384 -}
385 -
386 -func isSubdomainNamespace(ns string) bool {
387 - switch ns {
388 - case "ipfs", "ipns", "p2p", "ipld":
389 - return true
390 - default:
391 - return false
392 - }
393 -}
394 -
395 -func isPeerIDNamespace(ns string) bool {
396 - switch ns {
397 - case "ipns", "p2p":
398 - return true
399 - default:
400 - return false
401 - }
402 -}
403 -
404 -// Converts a CID to DNS-safe representation that fits in 63 characters
405 -func toDNSLabel(rootID string, rootCID cid.Cid) (dnsCID string, err error) {
406 - // Return as-is if things fit
407 - if len(rootID) <= dnsLabelMaxLength {
408 - return rootID, nil
409 - }
410 -
411 - // Convert to Base36 and see if that helped
412 - rootID, err = cid.NewCidV1(rootCID.Type(), rootCID.Hash()).StringOfBase(mbase.Base36)
413 - if err != nil {
414 - return "", err
415 - }
416 - if len(rootID) <= dnsLabelMaxLength {
417 - return rootID, nil
418 - }
419 -
420 - // Can't win with DNS at this point, return error
421 - return "", fmt.Errorf("CID incompatible with DNS label length limit of 63: %s", rootID)
422 -}
423 -
424 -// Returns true if HTTP request involves TLS certificate.
425 -// See https://github.com/ipfs/in-web-browsers/issues/169 to understand how it
426 -// impacts DNSLink websites on public gateways.
427 -func isHTTPSRequest(r *http.Request) bool {
428 - // X-Forwarded-Proto if added by a reverse proxy
429 - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
430 - xproto := r.Header.Get("X-Forwarded-Proto")
431 - // Is request a native TLS (not used atm, but future-proofing)
432 - // or a proxied HTTPS (eg. go-ipfs behind nginx at a public gw)?
433 - return r.URL.Scheme == "https" || xproto == "https"
434 -}
435 -
436 -// Converts a FQDN to DNS-safe representation that fits in 63 characters:
437 -// my.v-long.example.com → my-v--long-example-com
438 -func toDNSLinkDNSLabel(fqdn string) (dnsLabel string, err error) {
439 - dnsLabel = strings.ReplaceAll(fqdn, "-", "--")
440 - dnsLabel = strings.ReplaceAll(dnsLabel, ".", "-")
441 - if len(dnsLabel) > dnsLabelMaxLength {
442 - return "", fmt.Errorf("DNSLink representation incompatible with DNS label length limit of 63: %s", dnsLabel)
443 - }
444 - return dnsLabel, nil
445 -}
446 -
447 -// Converts a DNS-safe representation of DNSLink FQDN to real FQDN:
448 -// my-v--long-example-com → my.v-long.example.com
449 -func toDNSLinkFQDN(dnsLabel string) (fqdn string) {
450 - fqdn = strings.ReplaceAll(dnsLabel, "--", "@") // @ placeholder is unused in DNS labels
451 - fqdn = strings.ReplaceAll(fqdn, "-", ".")
452 - fqdn = strings.ReplaceAll(fqdn, "@", "-")
453 - return fqdn
454 -}
455 -
456 -// Converts a hostname/path to a subdomain-based URL, if applicable.
457 -func toSubdomainURL(hostname, path string, r *http.Request, inlineDNSLink bool, ipfs iface.CoreAPI) (redirURL string, err error) {
458 - var scheme, ns, rootID, rest string
459 -
460 - query := r.URL.RawQuery
461 - parts := strings.SplitN(path, "/", 4)
462 - isHTTPS := isHTTPSRequest(r)
463 - safeRedirectURL := func(in string) (out string, err error) {
464 - safeURI, err := url.ParseRequestURI(in)
465 - if err != nil {
466 - return "", err
467 - }
468 - return safeURI.String(), nil
469 - }
470 -
471 - if isHTTPS {
472 - scheme = "https:"
473 - } else {
474 - scheme = "http:"
475 - }
476 -
477 - switch len(parts) {
478 - case 4:
479 - rest = parts[3]
480 - fallthrough
481 - case 3:
482 - ns = parts[1]
483 - rootID = parts[2]
484 - default:
485 - return "", nil
486 - }
487 -
488 - if !isSubdomainNamespace(ns) {
489 - return "", nil
490 - }
491 -
492 - // add prefix if query is present
493 - if query != "" {
494 - query = "?" + query
495 - }
496 -
497 - // Normalize problematic PeerIDs (eg. ed25519+identity) to CID representation
498 - if isPeerIDNamespace(ns) && !isDomainNameAndNotPeerID(rootID) {
499 - peerID, err := peer.Decode(rootID)
500 - // Note: PeerID CIDv1 with protobuf multicodec will fail, but we fix it
501 - // in the next block
502 - if err == nil {
503 - rootID = peer.ToCid(peerID).String()
504 - }
505 - }
506 -
507 - // If rootID is a CID, ensure it uses DNS-friendly text representation
508 - if rootCID, err := cid.Decode(rootID); err == nil {
509 - multicodec := rootCID.Type()
510 - var base mbase.Encoding = mbase.Base32
511 -
512 - // Normalizations specific to /ipns/{libp2p-key}
513 - if isPeerIDNamespace(ns) {
514 - // Using Base36 for /ipns/ for consistency
515 - // Context: https://github.com/ipfs/kubo/pull/7441#discussion_r452372828
516 - base = mbase.Base36
517 -
518 - // PeerIDs represented as CIDv1 are expected to have libp2p-key
519 - // multicodec (https://github.com/libp2p/specs/pull/209).
520 - // We ease the transition by fixing multicodec on the fly:
521 - // https://github.com/ipfs/kubo/issues/5287#issuecomment-492163929
522 - if multicodec != cid.Libp2pKey {
523 - multicodec = cid.Libp2pKey
524 - }
525 - }
526 -
527 - // Ensure CID text representation used in subdomain is compatible
528 - // with the way DNS and URIs are implemented in user agents.
529 - //
530 - // 1. Switch to CIDv1 and enable case-insensitive Base encoding
531 - // to avoid issues when user agent force-lowercases the hostname
532 - // before making the request
533 - // (https://github.com/ipfs/in-web-browsers/issues/89)
534 - rootCID = cid.NewCidV1(multicodec, rootCID.Hash())
535 - rootID, err = rootCID.StringOfBase(base)
536 - if err != nil {
537 - return "", err
538 - }
539 - // 2. Make sure CID fits in a DNS label, adjust encoding if needed
540 - // (https://github.com/ipfs/kubo/issues/7318)
541 - rootID, err = toDNSLabel(rootID, rootCID)
542 - if err != nil {
543 - return "", err
544 - }
545 - } else { // rootID is not a CID
546 -
547 - // Check if rootID is a FQDN with DNSLink and convert it to TLS-safe
548 - // representation that fits in a single DNS label. We support this so
549 - // loading DNSLink names over TLS "just works" on public HTTP gateways
550 - // that pass 'https' in X-Forwarded-Proto to go-ipfs.
551 - //
552 - // Rationale can be found under "Option C"
553 - // at: https://github.com/ipfs/in-web-browsers/issues/169
554 - //
555 - // TLDR is:
556 - // /ipns/my.v-long.example.com
557 - // can be loaded from a subdomain gateway with a wildcard TLS cert if
558 - // represented as a single DNS label:
559 - // https://my-v--long-example-com.ipns.dweb.link
560 - if (inlineDNSLink || isHTTPS) && ns == "ipns" && strings.Contains(rootID, ".") {
561 - if isDNSLinkName(r.Context(), ipfs, rootID) {
562 - // my.v-long.example.com → my-v--long-example-com
563 - dnsLabel, err := toDNSLinkDNSLabel(rootID)
564 - if err != nil {
565 - return "", err
566 - }
567 - // update path prefix to use real FQDN with DNSLink
568 - rootID = dnsLabel
569 - }
570 - }
571 - }
572 -
573 - return safeRedirectURL(fmt.Sprintf(
574 - "%s//%s.%s.%s/%s%s",
575 - scheme,
576 - rootID,
577 - ns,
578 - hostname,
579 - rest,
580 - query,
581 - ))
582 -}
583 -
584 -func hasPrefix(path string, prefixes ...string) bool {
585 - for _, prefix := range prefixes {
586 - // Assume people are creative with trailing slashes in Gateway config
587 - p := strings.TrimSuffix(prefix, "/")
588 - // Support for both /version and /ipfs/$cid
589 - if p == path || strings.HasPrefix(path, p+"/") {
590 - return true
591 - }
592 - }
593 - return false
594 -}
595 -
596 -func stripPort(hostname string) string {
597 - host, _, err := net.SplitHostPort(hostname)
598 - if err == nil {
599 - return host
600 - }
601 - return hostname
602 -}
core/corehttp/hostname_test.go deleted
-307
@@ -1,307 +0,0 @@
1 -package corehttp
2 -
3 -import (
4 - "errors"
5 - "net/http"
6 - "net/http/httptest"
7 - "testing"
8 -
9 - cid "github.com/ipfs/go-cid"
10 - "github.com/ipfs/go-libipfs/files"
11 - path "github.com/ipfs/go-path"
12 - config "github.com/ipfs/kubo/config"
13 - coreapi "github.com/ipfs/kubo/core/coreapi"
14 -)
15 -
16 -func TestToSubdomainURL(t *testing.T) {
17 - ns := mockNamesys{}
18 - n, err := newNodeWithMockNamesys(ns)
19 - if err != nil {
20 - t.Fatal(err)
21 - }
22 - coreAPI, err := coreapi.NewCoreAPI(n)
23 - if err != nil {
24 - t.Fatal(err)
25 - }
26 - testCID, err := coreAPI.Unixfs().Add(n.Context(), files.NewBytesFile([]byte("fnord")))
27 - if err != nil {
28 - t.Fatal(err)
29 - }
30 - ns["/ipns/dnslink.long-name.example.com"] = path.FromString(testCID.String())
31 - ns["/ipns/dnslink.too-long.f1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5o.example.com"] = path.FromString(testCID.String())
32 - httpRequest := httptest.NewRequest("GET", "http://127.0.0.1:8080", nil)
33 - httpsRequest := httptest.NewRequest("GET", "https://https-request-stub.example.com", nil)
34 - httpsProxiedRequest := httptest.NewRequest("GET", "http://proxied-https-request-stub.example.com", nil)
35 - httpsProxiedRequest.Header.Set("X-Forwarded-Proto", "https")
36 -
37 - for _, test := range []struct {
38 - // in:
39 - request *http.Request
40 - gwHostname string
41 - inlineDNSLink bool
42 - path string
43 - // out:
44 - url string
45 - err error
46 - }{
47 - // DNSLink
48 - {httpRequest, "localhost", false, "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost/", nil},
49 - // Hostname with port
50 - {httpRequest, "localhost:8080", false, "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost:8080/", nil},
51 - // CIDv0 → CIDv1base32
52 - {httpRequest, "localhost", false, "/ipfs/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "http://bafybeif7a7gdklt6hodwdrmwmxnhksctcuav6lfxlcyfz4khzl3qfmvcgu.ipfs.localhost/", nil},
53 - // CIDv1 with long sha512
54 - {httpRequest, "localhost", false, "/ipfs/bafkrgqe3ohjcjplc6n4f3fwunlj6upltggn7xqujbsvnvyw764srszz4u4rshq6ztos4chl4plgg4ffyyxnayrtdi5oc4xb2332g645433aeg", "", errors.New("CID incompatible with DNS label length limit of 63: kf1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5oj")},
55 - // PeerID as CIDv1 needs to have libp2p-key multicodec
56 - {httpRequest, "localhost", false, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", "http://k2k4r8n0flx3ra0y5dr8fmyvwbzy3eiztmtq6th694k5a3rznayp3e4o.ipns.localhost/", nil},
57 - {httpRequest, "localhost", false, "/ipns/bafybeickencdqw37dpz3ha36ewrh4undfjt2do52chtcky4rxkj447qhdm", "http://k2k4r8l9ja7hkzynavdqup76ou46tnvuaqegbd04a4o1mpbsey0meucb.ipns.localhost/", nil},
58 - // PeerID: ed25519+identity multihash → CIDv1Base36
59 - {httpRequest, "localhost", false, "/ipns/12D3KooWFB51PRY9BxcXSH6khFXw1BZeszeLDy7C8GciskqCTZn5", "http://k51qzi5uqu5di608geewp3nqkg0bpujoasmka7ftkyxgcm3fh1aroup0gsdrna.ipns.localhost/", nil},
60 - {httpRequest, "sub.localhost", false, "/ipfs/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "http://bafybeif7a7gdklt6hodwdrmwmxnhksctcuav6lfxlcyfz4khzl3qfmvcgu.ipfs.sub.localhost/", nil},
61 - // HTTPS requires DNSLink name to fit in a single DNS label – see "Option C" from https://github.com/ipfs/in-web-browsers/issues/169
62 - {httpRequest, "dweb.link", false, "/ipns/dnslink.long-name.example.com", "http://dnslink.long-name.example.com.ipns.dweb.link/", nil},
63 - {httpsRequest, "dweb.link", false, "/ipns/dnslink.long-name.example.com", "https://dnslink-long--name-example-com.ipns.dweb.link/", nil},
64 - {httpsProxiedRequest, "dweb.link", false, "/ipns/dnslink.long-name.example.com", "https://dnslink-long--name-example-com.ipns.dweb.link/", nil},
65 - // HTTP requests can also be converted to fit into a single DNS label - https://github.com/ipfs/kubo/issues/9243
66 - {httpRequest, "localhost", true, "/ipns/dnslink.long-name.example.com", "http://dnslink-long--name-example-com.ipns.localhost/", nil},
67 - {httpRequest, "dweb.link", true, "/ipns/dnslink.long-name.example.com", "http://dnslink-long--name-example-com.ipns.dweb.link/", nil},
68 - } {
69 - url, err := toSubdomainURL(test.gwHostname, test.path, test.request, test.inlineDNSLink, coreAPI)
70 - if url != test.url || !equalError(err, test.err) {
71 - t.Errorf("(%s, %v, %s) returned (%s, %v), expected (%s, %v)", test.gwHostname, test.inlineDNSLink, test.path, url, err, test.url, test.err)
72 - }
73 - }
74 -}
75 -
76 -func TestToDNSLinkDNSLabel(t *testing.T) {
77 - for _, test := range []struct {
78 - in string
79 - out string
80 - err error
81 - }{
82 - {"dnslink.long-name.example.com", "dnslink-long--name-example-com", nil},
83 - {"dnslink.too-long.f1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5o.example.com", "", errors.New("DNSLink representation incompatible with DNS label length limit of 63: dnslink-too--long-f1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5o-example-com")},
84 - } {
85 - out, err := toDNSLinkDNSLabel(test.in)
86 - if out != test.out || !equalError(err, test.err) {
87 - t.Errorf("(%s) returned (%s, %v), expected (%s, %v)", test.in, out, err, test.out, test.err)
88 - }
89 - }
90 -}
91 -
92 -func TestToDNSLinkFQDN(t *testing.T) {
93 - for _, test := range []struct {
94 - in string
95 - out string
96 - }{
97 - {"singlelabel", "singlelabel"},
98 - {"docs-ipfs-tech", "docs.ipfs.tech"},
99 - {"dnslink-long--name-example-com", "dnslink.long-name.example.com"},
100 - } {
101 - out := toDNSLinkFQDN(test.in)
102 - if out != test.out {
103 - t.Errorf("(%s) returned (%s), expected (%s)", test.in, out, test.out)
104 - }
105 - }
106 -}
107 -
108 -func TestIsHTTPSRequest(t *testing.T) {
109 - httpRequest := httptest.NewRequest("GET", "http://127.0.0.1:8080", nil)
110 - httpsRequest := httptest.NewRequest("GET", "https://https-request-stub.example.com", nil)
111 - httpsProxiedRequest := httptest.NewRequest("GET", "http://proxied-https-request-stub.example.com", nil)
112 - httpsProxiedRequest.Header.Set("X-Forwarded-Proto", "https")
113 - httpProxiedRequest := httptest.NewRequest("GET", "http://proxied-http-request-stub.example.com", nil)
114 - httpProxiedRequest.Header.Set("X-Forwarded-Proto", "http")
115 - oddballRequest := httptest.NewRequest("GET", "foo://127.0.0.1:8080", nil)
116 - for _, test := range []struct {
117 - in *http.Request
118 - out bool
119 - }{
120 - {httpRequest, false},
121 - {httpsRequest, true},
122 - {httpsProxiedRequest, true},
123 - {httpProxiedRequest, false},
124 - {oddballRequest, false},
125 - } {
126 - out := isHTTPSRequest(test.in)
127 - if out != test.out {
128 - t.Errorf("(%+v): returned %t, expected %t", test.in, out, test.out)
129 - }
130 - }
131 -}
132 -
133 -func TestHasPrefix(t *testing.T) {
134 - for _, test := range []struct {
135 - prefixes []string
136 - path string
137 - out bool
138 - }{
139 - {[]string{"/ipfs"}, "/ipfs/cid", true},
140 - {[]string{"/ipfs/"}, "/ipfs/cid", true},
141 - {[]string{"/version/"}, "/version", true},
142 - {[]string{"/version"}, "/version", true},
143 - } {
144 - out := hasPrefix(test.path, test.prefixes...)
145 - if out != test.out {
146 - t.Errorf("(%+v, %s) returned '%t', expected '%t'", test.prefixes, test.path, out, test.out)
147 - }
148 - }
149 -}
150 -
151 -func TestIsDomainNameAndNotPeerID(t *testing.T) {
152 - for _, test := range []struct {
153 - hostname string
154 - out bool
155 - }{
156 - {"", false},
157 - {"example.com", true},
158 - {"non-icann.something", true},
159 - {"..", false},
160 - {"12D3KooWFB51PRY9BxcXSH6khFXw1BZeszeLDy7C8GciskqCTZn5", false}, // valid peerid
161 - {"k51qzi5uqu5di608geewp3nqkg0bpujoasmka7ftkyxgcm3fh1aroup0gsdrna", false}, // valid peerid
162 - } {
163 - out := isDomainNameAndNotPeerID(test.hostname)
164 - if out != test.out {
165 - t.Errorf("(%s) returned '%t', expected '%t'", test.hostname, out, test.out)
166 - }
167 - }
168 -}
169 -
170 -func TestPortStripping(t *testing.T) {
171 - for _, test := range []struct {
172 - in string
173 - out string
174 - }{
175 - {"localhost:8080", "localhost"},
176 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.localhost:8080", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.localhost"},
177 - {"example.com:443", "example.com"},
178 - {"example.com", "example.com"},
179 - {"foo-dweb.ipfs.pvt.k12.ma.us:8080", "foo-dweb.ipfs.pvt.k12.ma.us"},
180 - {"localhost", "localhost"},
181 - {"[::1]:8080", "::1"},
182 - } {
183 - out := stripPort(test.in)
184 - if out != test.out {
185 - t.Errorf("(%s): returned '%s', expected '%s'", test.in, out, test.out)
186 - }
187 - }
188 -}
189 -
190 -func TestToDNSLabel(t *testing.T) {
191 - for _, test := range []struct {
192 - in string
193 - out string
194 - err error
195 - }{
196 - // <= 63
197 - {"QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", nil},
198 - {"bafybeickencdqw37dpz3ha36ewrh4undfjt2do52chtcky4rxkj447qhdm", "bafybeickencdqw37dpz3ha36ewrh4undfjt2do52chtcky4rxkj447qhdm", nil},
199 - // > 63
200 - // PeerID: ed25519+identity multihash → CIDv1Base36
201 - {"bafzaajaiaejca4syrpdu6gdx4wsdnokxkprgzxf4wrstuc34gxw5k5jrag2so5gk", "k51qzi5uqu5dj16qyiq0tajolkojyl9qdkr254920wxv7ghtuwcz593tp69z9m", nil},
202 - // CIDv1 with long sha512 → error
203 - {"bafkrgqe3ohjcjplc6n4f3fwunlj6upltggn7xqujbsvnvyw764srszz4u4rshq6ztos4chl4plgg4ffyyxnayrtdi5oc4xb2332g645433aeg", "", errors.New("CID incompatible with DNS label length limit of 63: kf1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5oj")},
204 - } {
205 - inCID, _ := cid.Decode(test.in)
206 - out, err := toDNSLabel(test.in, inCID)
207 - if out != test.out || !equalError(err, test.err) {
208 - t.Errorf("(%s): returned (%s, %v) expected (%s, %v)", test.in, out, err, test.out, test.err)
209 - }
210 - }
211 -
212 -}
213 -
214 -func TestKnownSubdomainDetails(t *testing.T) {
215 - gwLocalhost := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
216 - gwDweb := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
217 - gwLong := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
218 - gwWildcard1 := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
219 - gwWildcard2 := &config.GatewaySpec{Paths: []string{"/ipfs", "/ipns", "/api"}, UseSubdomains: true}
220 -
221 - knownGateways := prepareKnownGateways(map[string]*config.GatewaySpec{
222 - "localhost": gwLocalhost,
223 - "dweb.link": gwDweb,
224 - "devgateway.dweb.link": gwDweb,
225 - "dweb.ipfs.pvt.k12.ma.us": gwLong, // note the sneaky ".ipfs." ;-)
226 - "*.wildcard1.tld": gwWildcard1,
227 - "*.*.wildcard2.tld": gwWildcard2,
228 - })
229 -
230 - for _, test := range []struct {
231 - // in:
232 - hostHeader string
233 - // out:
234 - gw *config.GatewaySpec
235 - hostname string
236 - ns string
237 - rootID string
238 - ok bool
239 - }{
240 - // no subdomain
241 - {"127.0.0.1:8080", nil, "", "", "", false},
242 - {"[::1]:8080", nil, "", "", "", false},
243 - {"hey.look.example.com", nil, "", "", "", false},
244 - {"dweb.link", nil, "", "", "", false},
245 - // malformed Host header
246 - {".....dweb.link", nil, "", "", "", false},
247 - {"link", nil, "", "", "", false},
248 - {"8080:dweb.link", nil, "", "", "", false},
249 - {" ", nil, "", "", "", false},
250 - {"", nil, "", "", "", false},
251 - // unknown gateway host
252 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.unknown.example.com", nil, "", "", "", false},
253 - // cid in subdomain, known gateway
254 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.localhost:8080", gwLocalhost, "localhost:8080", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
255 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.dweb.link", gwDweb, "dweb.link", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
256 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.devgateway.dweb.link", gwDweb, "devgateway.dweb.link", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
257 - // capture everything before .ipfs.
258 - {"foo.bar.boo-buzz.ipfs.dweb.link", gwDweb, "dweb.link", "ipfs", "foo.bar.boo-buzz", true},
259 - // ipns
260 - {"bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju.ipns.localhost:8080", gwLocalhost, "localhost:8080", "ipns", "bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju", true},
261 - {"bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju.ipns.dweb.link", gwDweb, "dweb.link", "ipns", "bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju", true},
262 - // edge case check: public gateway under long TLD (see: https://publicsuffix.org)
263 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.dweb.ipfs.pvt.k12.ma.us", gwLong, "dweb.ipfs.pvt.k12.ma.us", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
264 - {"bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju.ipns.dweb.ipfs.pvt.k12.ma.us", gwLong, "dweb.ipfs.pvt.k12.ma.us", "ipns", "bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju", true},
265 - // dnslink in subdomain
266 - {"en.wikipedia-on-ipfs.org.ipns.localhost:8080", gwLocalhost, "localhost:8080", "ipns", "en.wikipedia-on-ipfs.org", true},
267 - {"en.wikipedia-on-ipfs.org.ipns.localhost", gwLocalhost, "localhost", "ipns", "en.wikipedia-on-ipfs.org", true},
268 - {"dist.ipfs.tech.ipns.localhost:8080", gwLocalhost, "localhost:8080", "ipns", "dist.ipfs.tech", true},
269 - {"en.wikipedia-on-ipfs.org.ipns.dweb.link", gwDweb, "dweb.link", "ipns", "en.wikipedia-on-ipfs.org", true},
270 - // edge case check: public gateway under long TLD (see: https://publicsuffix.org)
271 - {"foo.dweb.ipfs.pvt.k12.ma.us", nil, "", "", "", false},
272 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.dweb.ipfs.pvt.k12.ma.us", gwLong, "dweb.ipfs.pvt.k12.ma.us", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
273 - {"bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju.ipns.dweb.ipfs.pvt.k12.ma.us", gwLong, "dweb.ipfs.pvt.k12.ma.us", "ipns", "bafzbeihe35nmjqar22thmxsnlsgxppd66pseq6tscs4mo25y55juhh6bju", true},
274 - // other namespaces
275 - {"api.localhost", nil, "", "", "", false},
276 - {"peerid.p2p.localhost", gwLocalhost, "localhost", "p2p", "peerid", true},
277 - // wildcards
278 - {"wildcard1.tld", nil, "", "", "", false},
279 - {".wildcard1.tld", nil, "", "", "", false},
280 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.wildcard1.tld", nil, "", "", "", false},
281 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.sub.wildcard1.tld", gwWildcard1, "sub.wildcard1.tld", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
282 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.sub1.sub2.wildcard1.tld", nil, "", "", "", false},
283 - {"bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am.ipfs.sub1.sub2.wildcard2.tld", gwWildcard2, "sub1.sub2.wildcard2.tld", "ipfs", "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am", true},
284 - } {
285 - gw, hostname, ns, rootID, ok := knownSubdomainDetails(test.hostHeader, knownGateways)
286 - if ok != test.ok {
287 - t.Errorf("knownSubdomainDetails(%s): ok is %t, expected %t", test.hostHeader, ok, test.ok)
288 - }
289 - if rootID != test.rootID {
290 - t.Errorf("knownSubdomainDetails(%s): rootID is '%s', expected '%s'", test.hostHeader, rootID, test.rootID)
291 - }
292 - if ns != test.ns {
293 - t.Errorf("knownSubdomainDetails(%s): ns is '%s', expected '%s'", test.hostHeader, ns, test.ns)
294 - }
295 - if hostname != test.hostname {
296 - t.Errorf("knownSubdomainDetails(%s): hostname is '%s', expected '%s'", test.hostHeader, hostname, test.hostname)
297 - }
298 - if gw != test.gw {
299 - t.Errorf("knownSubdomainDetails(%s): gw is %+v, expected %+v", test.hostHeader, gw, test.gw)
300 - }
301 - }
302 -
303 -}
304 -
305 -func equalError(a, b error) bool {
306 - return (a == nil && b == nil) || (a != nil && b != nil && a.Error() == b.Error())
307 -}
docs/examples/kubo-as-a-library/go.mod
+1 -1
@@ -7,7 +7,7 @@ go 1.18
7 replace github.com/ipfs/kubo => ./../../..
8
9 require (
10 - github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c
10 + github.com/ipfs/go-libipfs v0.4.1-0.20230207021459-1a932f7bb3c1
11 github.com/ipfs/interface-go-ipfs-core v0.10.0
12 github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
13 github.com/libp2p/go-libp2p v0.24.2
docs/examples/kubo-as-a-library/go.sum
+2 -2
@@ -548,8 +548,8 @@ github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2
548 github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg=
549 github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A=
550 github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24=
551 -github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c h1:Z8GrWoG3VZWj0RvHnzKlyIyXh8sgCIw62O9t3jnhqyk=
552 -github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c/go.mod h1:S5wg08D/FkeYxeMf8adgt6Mi6ttbA7kSFcQYlmeGHMU=
551 +github.com/ipfs/go-libipfs v0.4.1-0.20230207021459-1a932f7bb3c1 h1:drLEvJmJM0UrXvQfMF84EqSeGSXl5Elk/PAcc0XnNb4=
552 +github.com/ipfs/go-libipfs v0.4.1-0.20230207021459-1a932f7bb3c1/go.mod h1:XKRXmSlJ32qlpxGN+mdGJJXUl/Z055etN1xpMEaANQ8=
553 github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
554 github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk=
555 github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A=
go.mod
+1 -1
@@ -45,7 +45,7 @@ require (
45 github.com/ipfs/go-ipld-git v0.1.1
46 github.com/ipfs/go-ipld-legacy v0.1.1
47 github.com/ipfs/go-ipns v0.3.0
48 - github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c
48 + github.com/ipfs/go-libipfs v0.4.1-0.20230207021459-1a932f7bb3c1
49 github.com/ipfs/go-log v1.0.5
50 github.com/ipfs/go-log/v2 v2.5.1
51 github.com/ipfs/go-merkledag v0.9.0
go.sum
+2 -2
@@ -570,8 +570,8 @@ github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2
570 github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg=
571 github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A=
572 github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24=
573 -github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c h1:Z8GrWoG3VZWj0RvHnzKlyIyXh8sgCIw62O9t3jnhqyk=
574 -github.com/ipfs/go-libipfs v0.4.1-0.20230202010411-6399b73f974c/go.mod h1:S5wg08D/FkeYxeMf8adgt6Mi6ttbA7kSFcQYlmeGHMU=
573 +github.com/ipfs/go-libipfs v0.4.1-0.20230207021459-1a932f7bb3c1 h1:drLEvJmJM0UrXvQfMF84EqSeGSXl5Elk/PAcc0XnNb4=
574 +github.com/ipfs/go-libipfs v0.4.1-0.20230207021459-1a932f7bb3c1/go.mod h1:XKRXmSlJ32qlpxGN+mdGJJXUl/Z055etN1xpMEaANQ8=
575 github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
576 github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk=
577 github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A=