Skip to content

Commit defb1fd

Browse files
committed
test: /api in subdomains
License: MIT Signed-off-by: Marcin Rataj <[email protected]>
1 parent 2ebe0a8 commit defb1fd

File tree

2 files changed

+76
-26
lines changed

2 files changed

+76
-26
lines changed

core/corehttp/hostname.go

+37-17
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ import (
1818
)
1919

2020
var pathGatewaySpec = config.GatewaySpec{
21-
Paths: []string{ipfsPathPrefix, ipnsPathPrefix, "/api", "/p2p", "/version"},
21+
Paths: []string{"/ipfs/", "/ipns/", "/api/", "/p2p/", "/version"},
2222
UseSubdomains: false,
2323
}
2424

2525
var subdomainGatewaySpec = config.GatewaySpec{
26-
Paths: []string{ipfsPathPrefix, ipnsPathPrefix},
26+
Paths: []string{"/ipfs/", "/ipns/", "/api/", "/p2p/"},
2727
UseSubdomains: true,
2828
}
2929

@@ -38,7 +38,7 @@ var defaultKnownGateways = map[string]config.GatewaySpec{
3838

3939
// Find content identifier, protocol, and remaining hostname (host+optional port)
4040
// of a subdomain gateway (eg. *.ipfs.foo.bar.co.uk)
41-
var subdomainGatewayRegex = regexp.MustCompile("^(.+).(ipfs|ipns|ipld|p2p).([^/?#&]+)$")
41+
var subdomainGatewayRegex = regexp.MustCompile("^(.+).(ipfs|ipns|ipld|p2p|api).([^/?#&]+)$")
4242

4343
// HostnameOption rewrites an incoming request based on the Host header.
4444
func HostnameOption() ServeOption {
@@ -112,7 +112,7 @@ func HostnameOption() ServeOption {
112112
if gw.UseSubdomains {
113113
// Yes, redirect if applicable (pretty much everything except `/api`).
114114
// Example: dweb.link/ipfs/{cid} → {cid}.ipfs.dweb.link
115-
if newURL, ok := toSubdomainURL(r.Host, r.URL.Path); ok {
115+
if newURL, ok := toSubdomainURL(r.Host, r.URL.Path, r.URL.RawQuery); ok {
116116
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
117117
return
118118
}
@@ -130,24 +130,24 @@ func HostnameOption() ServeOption {
130130

131131
// HTTP Host check: is this one of our subdomain-based "known gateways"?
132132
// Example: {cid}.ipfs.localhost, {cid}.ipfs.dweb.link
133-
if hostname, ns, rootId, ok := parseSubdomains(r.Host); ok {
133+
if hostname, ns, rootID, ok := parseSubdomains(r.Host); ok {
134134
// Looks like we're using subdomains.
135135

136136
// Again, is this a known gateway that supports subdomains?
137137
if gw, ok := isKnownGateway(hostname); ok {
138138

139139
// Assemble original path prefix.
140-
pathPrefix := "/" + ns + "/" + rootId
140+
pathPrefix := "/" + ns + "/" + rootID
141141

142142
// Does this gateway _handle_ this path?
143143
if gw.UseSubdomains && hasPrefix(pathPrefix, gw.Paths...) {
144144

145145
// Do we need to fix multicodec in CID?
146146
if ns == "ipns" {
147-
keyCid, err := cid.Decode(rootId)
147+
keyCid, err := cid.Decode(rootID)
148148
if err == nil && keyCid.Type() != cid.Libp2pKey {
149149

150-
if newURL, ok := toSubdomainURL(hostname, pathPrefix+r.URL.Path); ok {
150+
if newURL, ok := toSubdomainURL(hostname, pathPrefix+r.URL.Path, r.URL.RawQuery); ok {
151151
// Redirect to CID fixed inside of toSubdomainURL()
152152
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
153153
return
@@ -198,7 +198,7 @@ func HostnameOption() ServeOption {
198198

199199
func isSubdomainNamespace(ns string) bool {
200200
switch ns {
201-
case "ipfs", "ipns", "p2p", "ipld":
201+
case "ipfs", "ipns", "p2p", "ipld", "api":
202202
return true
203203
default:
204204
return false
@@ -207,7 +207,7 @@ func isSubdomainNamespace(ns string) bool {
207207

208208
// Parses Host header of a subdomain-based URL and returns it's components
209209
// Note: hostname is host + optional port
210-
func parseSubdomains(hostHeader string) (hostname, ns, rootId string, ok bool) {
210+
func parseSubdomains(hostHeader string) (hostname, ns, rootID string, ok bool) {
211211
parts := subdomainGatewayRegex.FindStringSubmatch(hostHeader)
212212
if len(parts) < 4 || !isSubdomainNamespace(parts[2]) {
213213
return "", "", "", false
@@ -216,17 +216,17 @@ func parseSubdomains(hostHeader string) (hostname, ns, rootId string, ok bool) {
216216
}
217217

218218
// Converts a hostname/path to a subdomain-based URL, if applicable.
219-
func toSubdomainURL(hostname, path string) (url string, ok bool) {
219+
func toSubdomainURL(hostname, path string, query string) (url string, ok bool) {
220220
parts := strings.SplitN(path, "/", 4)
221221

222-
var ns, rootId, rest string
222+
var ns, rootID, rest string
223223
switch len(parts) {
224224
case 4:
225225
rest = parts[3]
226226
fallthrough
227227
case 3:
228228
ns = parts[1]
229-
rootId = parts[2]
229+
rootID = parts[2]
230230
default:
231231
return "", false
232232
}
@@ -235,7 +235,26 @@ func toSubdomainURL(hostname, path string) (url string, ok bool) {
235235
return "", false
236236
}
237237

238-
if rootCid, err := cid.Decode(rootId); err == nil {
238+
// add prefix if query is present
239+
if query != "" {
240+
query = "?" + query
241+
}
242+
243+
if ns == "api" || ns == "p2p" {
244+
// API and P2P proxy use the same paths on subdomains:
245+
// api.hostname/api/.. and p2p.hostname/p2p/..
246+
return fmt.Sprintf(
247+
"http://%s.%s/%s/%s/%s%s",
248+
ns,
249+
hostname,
250+
ns,
251+
rootID,
252+
rest,
253+
query,
254+
), true
255+
}
256+
257+
if rootCid, err := cid.Decode(rootID); err == nil {
239258
multicodec := rootCid.Type()
240259

241260
// CIDs in IPNS are expected to have libp2p-key multicodec.
@@ -248,15 +267,16 @@ func toSubdomainURL(hostname, path string) (url string, ok bool) {
248267
// if object turns out to be a valid CID,
249268
// ensure text representation used in subdomain is CIDv1 in Base32
250269
// https://github.com/ipfs/in-web-browsers/issues/89
251-
rootId = cid.NewCidV1(multicodec, rootCid.Hash()).String()
270+
rootID = cid.NewCidV1(multicodec, rootCid.Hash()).String()
252271
}
253272

254273
return fmt.Sprintf(
255-
"http://%s.%s.%s/%s",
256-
rootId,
274+
"http://%s.%s.%s/%s%s",
275+
rootID,
257276
ns,
258277
hostname,
259278
rest,
279+
query,
260280
), true
261281
}
262282

test/sharness/t0114-gateway-subdomains.sh

+39-9
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44

55
test_description="Test subdomain support on the HTTP gateway"
66

7+
78
. lib/test-lib.sh
89

10+
## ============================================================================
11+
## Helpers specific to subdomain tests
12+
## ============================================================================
13+
914
# Helper that tests gateway response over direct HTTP
1015
# and in all supported HTTP proxy modes
1116
test_localhost_gateway_response_should_contain() {
@@ -41,6 +46,9 @@ test_hostname_gateway_response_should_contain() {
4146
test_should_contain \"$4\" response
4247
"
4348
}
49+
## ============================================================================
50+
## Start IPFS Node and prepare test CIDs
51+
## ============================================================================
4452

4553
test_init_ipfs
4654
test_launch_ipfs_daemon --offline
@@ -53,6 +61,13 @@ test_expect_success "Add test text file" '
5361
CIDv0to1=$(echo "$CIDv0" | ipfs cid base32)
5462
'
5563

64+
test_expect_success "Add the test directory" '
65+
mkdir -p testdirlisting/subdir1/subdir2 &&
66+
echo "hello" > testdirlisting/hello &&
67+
echo "subdir2-bar" > testdirlisting/subdir1/subdir2/bar &&
68+
DIR_CID=$(ipfs add -Qr --cid-version 1 testdirlisting)
69+
'
70+
5671
test_expect_success "Publish test text file to IPNS" '
5772
PEERID=$(ipfs id --format="<id>")
5873
IPNS_IDv0=$(echo "$PEERID" | ipfs cid format -v 0)
@@ -64,6 +79,7 @@ test_expect_success "Publish test text file to IPNS" '
6479
printf "/ipfs/%s\n" "$CIDv1" > expected2 &&
6580
test_cmp expected2 output
6681
'
82+
6783
#test_kill_ipfs_daemon
6884
#test_launch_ipfs_daemon
6985

@@ -108,6 +124,13 @@ test_localhost_gateway_response_should_contain \
108124
"http://localhost:$GWAY_PORT/ipns/en.wikipedia-on-ipfs.org/wiki" \
109125
"Location: http://en.wikipedia-on-ipfs.org.ipns.localhost:$GWAY_PORT/wiki"
110126

127+
# /api/ → api.localhost/api
128+
129+
test_localhost_gateway_response_should_contain \
130+
"Request for localhost/api redirect to api.localhost" \
131+
"http://localhost:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
132+
"Location: http://api.localhost:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true"
133+
111134
## ============================================================================
112135
## Test subdomain-based requests to a local gateway with default config
113136
## (origin per content root at http://*.localhost)
@@ -127,13 +150,6 @@ test_localhost_gateway_response_should_contain \
127150

128151
# {CID}.ipfs.localhost/sub/dir (Directory Listing)
129152

130-
test_expect_success "Add the test directory" '
131-
mkdir -p testdirlisting/subdir1/subdir2 &&
132-
echo "hello" > testdirlisting/hello &&
133-
echo "subdir2-bar" > testdirlisting/subdir1/subdir2/bar &&
134-
DIR_CID=$(ipfs add -Qr --cid-version 1 testdirlisting)
135-
'
136-
137153
test_expect_success "Valid file and subdirectory paths in directory listing at {cid}.ipfs.localhost" '
138154
curl -s "http://${DIR_CID}.ipfs.localhost:$GWAY_PORT" > list_response &&
139155
test_should_contain "<a href=\"/hello\">hello</a>" list_response &&
@@ -184,14 +200,21 @@ test_localhost_gateway_response_should_contain \
184200
# test_cmp docs_cid_expected dnslink_response
185201
#'
186202

203+
# api.localhost/api
204+
205+
# Note: use DIR_CID so refs -r returns some CIDs for child nodes
206+
test_localhost_gateway_response_should_contain \
207+
"Request for api.localhost returns API response" \
208+
"http://api.localhost:$GWAY_PORT/api/v0/refs?arg=$DIR_CID&r=true" \
209+
"Ref"
187210

188211
## ============================================================================
189212
## Test subdomain-based requests with a custom hostname config
190213
## (origin per content root at http://*.example.com)
191214
## ============================================================================
192215

193216
# set explicit subdomain gateway config for the hostname
194-
ipfs config --json Gateway.PublicGateways '{"example.com": { "UseSubdomains": true, "Paths": ["/ipfs", "/ipns"] }}'
217+
ipfs config --json Gateway.PublicGateways '{"example.com": { "UseSubdomains": true, "Paths": ["/ipfs", "/ipns", "/api"] }}'
195218
# restart daemon to apply config changes
196219
test_kill_ipfs_daemon
197220
test_launch_ipfs_daemon --offline
@@ -281,7 +304,14 @@ test_hostname_gateway_response_should_contain \
281304
"http://127.0.0.1:$GWAY_PORT" \
282305
"Location: http://${IPNS_IDv1}.ipns.example.com/"
283306

284-
# TODO: api.example.com/v0/id
307+
# api.example.com
308+
# ============================================================================
309+
310+
test_hostname_gateway_response_should_contain \
311+
"Request for api.example.com/api/v0/refs returns expected payload when /api is on Paths whitelist" \
312+
"api.example.com" \
313+
"http://127.0.0.1:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
314+
"Ref"
285315
#
286316
# DNSLink requests (could be moved to separate test file)
287317
#

0 commit comments

Comments
 (0)