Skip to content

Commit b805817

Browse files
lidelStebalien
authored andcommitted
fix(gateway): curl without redirect on localhost
When request is sent to http://localhost:8080/ipfs/$cid response has HTTP 301 status code and "Location" header with redirect destination at $cid.ipfs.localhost:8080 Redirect is followed by browsersi, but not by commandline tools. Status 301 is ignored by curl in default mode: it will print response and won't follow redirect, user needs to add -L for that. To fix curl, we return correct payload in body of HTTP 301 response, but set Clear-Site-Data header to ensure Origin sandbox can't be abused. This requires a surgical workaround: If Location header is present in ResponseWriter's Header map, we ensure http.ServeContent() returns HTTP 301 Context: #6982 License: MIT Signed-off-by: Marcin Rataj <[email protected]>
1 parent e5a38ea commit b805817

File tree

3 files changed

+60
-3
lines changed

3 files changed

+60
-3
lines changed

core/corehttp/gateway_handler.go

+20
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,25 @@ type gatewayHandler struct {
3838
api coreiface.CoreAPI
3939
}
4040

41+
// StatusResponseWriter enables us to override HTTP Status Code passed to
42+
// WriteHeader function inside of http.ServeContent. Decision is based on
43+
// presence of HTTP Headers such as Location.
44+
type statusResponseWriter struct {
45+
http.ResponseWriter
46+
}
47+
48+
func (sw *statusResponseWriter) WriteHeader(code int) {
49+
// Check if we need to adjust Status Code to account for scheduled redirect
50+
// This enables us to return payload along with HTTP 301
51+
// for subdomain redirect in web browsers while also returning body for cli
52+
// tools which do not follow redirects by default (curl, wget).
53+
redirect := sw.ResponseWriter.Header().Get("Location")
54+
if redirect != "" && code == http.StatusOK {
55+
code = http.StatusMovedPermanently
56+
}
57+
sw.ResponseWriter.WriteHeader(code)
58+
}
59+
4160
func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) *gatewayHandler {
4261
i := &gatewayHandler{
4362
config: c,
@@ -366,6 +385,7 @@ func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, nam
366385
}
367386
w.Header().Set("Content-Type", ctype)
368387

388+
w = &statusResponseWriter{w}
369389
http.ServeContent(w, req, name, modtime, content)
370390
}
371391

core/corehttp/hostname.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,24 @@ func HostnameOption() ServeOption {
9595
// Yes, redirect if applicable
9696
// Example: dweb.link/ipfs/{cid} → {cid}.ipfs.dweb.link
9797
if newURL, ok := toSubdomainURL(r.Host, r.URL.Path, r); ok {
98-
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
99-
return
98+
// Just to be sure single Origin can't be abused in
99+
// web browsers that ignored the redirect for some
100+
// reason, Clear-Site-Data header clears browsing
101+
// data (cookies, storage etc) associated with
102+
// hostname's root Origin
103+
// Note: we can't use "*" due to bug in Chromium:
104+
// https://bugs.chromium.org/p/chromium/issues/detail?id=898503
105+
w.Header().Set("Clear-Site-Data", "\"cookies\", \"storage\"")
106+
107+
// Set "Location" header with redirect destination.
108+
// It is ignored by curl in default mode, but will
109+
// be respected by user agents that follow
110+
// redirects by default, namely web browsers
111+
w.Header().Set("Location", newURL)
112+
113+
// Note: we continue regular gateway processing:
114+
// HTTP Status Code http.StatusMovedPermanently
115+
// will be set later, in statusResponseWriter
100116
}
101117
}
102118

test/sharness/t0114-gateway-subdomains.sh

+22-1
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,31 @@ test_localhost_gateway_response_should_contain \
145145
# payload directly, but redirect to URL with proper origin isolation
146146

147147
test_localhost_gateway_response_should_contain \
148-
"request for localhost/ipfs/{CIDv1} redirects to subdomain" \
148+
"request for localhost/ipfs/{CIDv1} returns status code HTTP 301" \
149+
"http://localhost:$GWAY_PORT/ipfs/$CIDv1" \
150+
"301 Moved Permanently"
151+
152+
test_localhost_gateway_response_should_contain \
153+
"request for localhost/ipfs/{CIDv1} returns Location HTTP header for subdomain redirect in browsers" \
149154
"http://localhost:$GWAY_PORT/ipfs/$CIDv1" \
150155
"Location: http://$CIDv1.ipfs.localhost:$GWAY_PORT/"
151156

157+
# Responses to the root domain of subdomain gateway hostname should Clear-Site-Data
158+
# https://github.com/ipfs/go-ipfs/issues/6975#issuecomment-597472477
159+
test_localhost_gateway_response_should_contain \
160+
"request for localhost/ipfs/{CIDv1} returns Clear-Site-Data header to purge Origin cookies and storage" \
161+
"http://localhost:$GWAY_PORT/ipfs/$CIDv1" \
162+
'Clear-Site-Data: \"cookies\", \"storage\"'
163+
164+
# We return body with HTTP 301 so existing cli scripts that use path-based
165+
# gateway do not break (curl doesn't auto-redirect without passing -L; wget
166+
# does not span across hostnames by default)
167+
# Context: https://github.com/ipfs/go-ipfs/issues/6975
168+
test_localhost_gateway_response_should_contain \
169+
"request for localhost/ipfs/{CIDv1} includes valid payload in body for CLI tools like curl" \
170+
"http://localhost:$GWAY_PORT/ipfs/$CIDv1" \
171+
"$CID_VAL"
172+
152173
test_localhost_gateway_response_should_contain \
153174
"request for localhost/ipfs/{CIDv0} redirects to CIDv1 representation in subdomain" \
154175
"http://localhost:$GWAY_PORT/ipfs/$CIDv0" \

0 commit comments

Comments
 (0)