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=