diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 1222b17bcd7..b310b63f3c8 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -1104,3 +1104,26 @@ func (i *gatewayHandler) setCommonHeaders(w http.ResponseWriter, r *http.Request return nil } + +// abortConn forcefully closes an HTTP connection, leading to a network error on the +// client side. This is a way of showing the client that there was an error while streaming +// the contents since there are no good ways of showing an error during a stream. +func abortConn(w http.ResponseWriter) { + hj, ok := w.(http.Hijacker) + if !ok { + // If we could not Hijack the connection (such as in HTTP/2.x) connections, we + // panic using http.ErrAbortHandler, which aborts the response. + // https://pkg.go.dev/net/http#ErrAbortHandler + panic(http.ErrAbortHandler) + } + + conn, _, err := hj.Hijack() + if err != nil { + panic(http.ErrAbortHandler) + } + + err = conn.Close() + if err != nil { + panic(http.ErrAbortHandler) + } +} diff --git a/core/corehttp/gateway_handler_block.go b/core/corehttp/gateway_handler_block.go index 3bf7c76be2d..2ac714d69ad 100644 --- a/core/corehttp/gateway_handler_block.go +++ b/core/corehttp/gateway_handler_block.go @@ -46,7 +46,12 @@ func (i *gatewayHandler) serveRawBlock(ctx context.Context, w http.ResponseWrite // ServeContent will take care of // If-None-Match+Etag, Content-Length and range requests - _, dataSent, _ := ServeContent(w, r, name, modtime, content) + _, dataSent, err := ServeContent(w, r, name, modtime, content) + + if err != nil { + abortConn(w) + return + } if dataSent { // Update metrics diff --git a/core/corehttp/gateway_handler_car.go b/core/corehttp/gateway_handler_car.go index 3363c5199e2..2c03b104624 100644 --- a/core/corehttp/gateway_handler_car.go +++ b/core/corehttp/gateway_handler_car.go @@ -76,11 +76,7 @@ func (i *gatewayHandler) serveCAR(ctx context.Context, w http.ResponseWriter, r car := gocar.NewSelectiveCar(ctx, store, []gocar.Dag{dag}, gocar.TraverseLinksOnlyOnce()) if err := car.Write(w); err != nil { - // We return error as a trailer, however it is not something browsers can access - // (https://github.com/mdn/browser-compat-data/issues/14703) - // Due to this, we suggest client always verify that - // the received CAR stream response is matching requested DAG selector - w.Header().Set("X-Stream-Error", err.Error()) + abortConn(w) return } diff --git a/core/corehttp/gateway_handler_tar.go b/core/corehttp/gateway_handler_tar.go index 532d8875760..884b7e8b452 100644 --- a/core/corehttp/gateway_handler_tar.go +++ b/core/corehttp/gateway_handler_tar.go @@ -79,14 +79,6 @@ func (i *gatewayHandler) serveTAR(ctx context.Context, w http.ResponseWriter, r // The TAR has a top-level directory (or file) named by the CID. if err := tarw.WriteFile(file, rootCid.String()); err != nil { - w.Header().Set("X-Stream-Error", err.Error()) - // Trailer headers do not work in web browsers - // (see https://github.com/mdn/browser-compat-data/issues/14703) - // and we have limited options around error handling in browser contexts. - // To improve UX/DX, we finish response stream with error message, allowing client to - // (1) detect error by having corrupted TAR - // (2) be able to reason what went wrong by instecting the tail of TAR stream - _, _ = w.Write([]byte(err.Error())) - return + abortConn(w) } } diff --git a/core/corehttp/gateway_handler_unixfs_dir.go b/core/corehttp/gateway_handler_unixfs_dir.go index 1c803b13b34..52c0327c326 100644 --- a/core/corehttp/gateway_handler_unixfs_dir.go +++ b/core/corehttp/gateway_handler_unixfs_dir.go @@ -209,7 +209,7 @@ func (i *gatewayHandler) serveDirectory(ctx context.Context, w http.ResponseWrit logger.Debugw("request processed", "tplDataDNSLink", dnslink, "tplDataSize", size, "tplDataBackLink", backLink, "tplDataHash", hash) if err := listingTemplate.Execute(w, tplData); err != nil { - internalWebError(w, err) + abortConn(w) return } diff --git a/core/corehttp/gateway_handler_unixfs_file.go b/core/corehttp/gateway_handler_unixfs_file.go index 9463be1acf8..6bb507359d5 100644 --- a/core/corehttp/gateway_handler_unixfs_file.go +++ b/core/corehttp/gateway_handler_unixfs_file.go @@ -94,7 +94,12 @@ func (i *gatewayHandler) serveFile(ctx context.Context, w http.ResponseWriter, r // ServeContent will take care of // If-None-Match+Etag, Content-Length and range requests - _, dataSent, _ := ServeContent(w, r, name, modtime, content) + _, dataSent, err := ServeContent(w, r, name, modtime, content) + + if err != nil { + abortConn(w) + return + } // Was response successful? if dataSent { diff --git a/test/sharness/t0122-gateway-tar.sh b/test/sharness/t0122-gateway-tar.sh index 34dc1ba12c8..b1f55d2d564 100755 --- a/test/sharness/t0122-gateway-tar.sh +++ b/test/sharness/t0122-gateway-tar.sh @@ -76,8 +76,7 @@ test_expect_success "Add CARs with relative paths to test with" ' ' test_expect_success "GET TAR with relative paths outside root fails" ' - curl -o - "http://127.0.0.1:$GWAY_PORT/ipfs/$OUTSIDE_ROOT_CID?format=tar" > curl_output_filename && - test_should_contain "relative UnixFS paths outside the root are now allowed" curl_output_filename + ! curl "http://127.0.0.1:$GWAY_PORT/ipfs/$OUTSIDE_ROOT_CID?format=tar" ' test_expect_success "GET TAR with relative paths inside root works" '