diff --git a/openapi/Swarm.yaml b/openapi/Swarm.yaml index ec8050228d1..b4e4a18c918 100644 --- a/openapi/Swarm.yaml +++ b/openapi/Swarm.yaml @@ -179,6 +179,26 @@ paths: $ref: "SwarmCommon.yaml#/components/responses/400" default: description: Default response + head: + summary: Requests the headers containing the content type and length for the reference + tags: + - Bytes + parameters: + - in: path + name: address + schema: + $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" + required: true + description: Swarm address of chunk + responses: + "200": + description: Chunk exists + "400": + $ref: "SwarmCommon.yaml#/components/responses/400" + "404": + $ref: "SwarmCommon.yaml#/components/responses/404" + default: + description: Default response "/chunks": post: @@ -332,6 +352,26 @@ paths: $ref: "SwarmCommon.yaml#/components/responses/500" default: description: Default response + head: + summary: Get the headers containing the content type and length for the reference + tags: + - BZZ + parameters: + - in: path + name: address + schema: + $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" + required: true + description: Swarm address of chunk + responses: + "200": + description: Chunk exists + "400": + $ref: "SwarmCommon.yaml#/components/responses/400" + "404": + $ref: "SwarmCommon.yaml#/components/responses/404" + default: + description: Default response "/bzz/{reference}/{path}": get: diff --git a/pkg/api/bytes.go b/pkg/api/bytes.go index 988d73018ac..b5c6e4c6ed2 100644 --- a/pkg/api/bytes.go +++ b/pkg/api/bytes.go @@ -153,7 +153,7 @@ func (s *Service) bytesGetHandler(w http.ResponseWriter, r *http.Request) { ContentTypeHeader: {"application/octet-stream"}, } - s.downloadHandler(logger, w, r, paths.Address, additionalHeaders, true) + s.downloadHandler(logger, w, r, paths.Address, additionalHeaders, true, false) } func (s *Service) bytesHeadHandler(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index 1102b313607..07956fe5dc6 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -299,10 +299,29 @@ func (s *Service) bzzDownloadHandler(w http.ResponseWriter, r *http.Request) { paths.Path = strings.TrimRight(paths.Path, "/") + "/" // NOTE: leave one slash if there was some. } - s.serveReference(logger, paths.Address, paths.Path, w, r) + s.serveReference(logger, paths.Address, paths.Path, w, r, false) } -func (s *Service) serveReference(logger log.Logger, address swarm.Address, pathVar string, w http.ResponseWriter, r *http.Request) { +func (s *Service) bzzHeadHandler(w http.ResponseWriter, r *http.Request) { + logger := tracing.NewLoggerWithTraceID(r.Context(), s.logger.WithName("head_bzz_by_path").Build()) + + paths := struct { + Address swarm.Address `map:"address,resolve" validate:"required"` + Path string `map:"path"` + }{} + if response := s.mapStructure(mux.Vars(r), &paths); response != nil { + response("invalid path params", logger, w) + return + } + + if strings.HasSuffix(paths.Path, "/") { + paths.Path = strings.TrimRight(paths.Path, "/") + "/" // NOTE: leave one slash if there was some. + } + + s.serveReference(logger, paths.Address, paths.Path, w, r, true) +} + +func (s *Service) serveReference(logger log.Logger, address swarm.Address, pathVar string, w http.ResponseWriter, r *http.Request, headerOnly bool) { loggerV1 := logger.V(1).Build() headers := struct { @@ -404,7 +423,7 @@ FETCH: // index document exists logger.Debug("bzz download: serving path", "path", pathWithIndex) - s.serveManifestEntry(logger, w, r, indexDocumentManifestEntry, !feedDereferenced) + s.serveManifestEntry(logger, w, r, indexDocumentManifestEntry, !feedDereferenced, headerOnly) return } } @@ -447,7 +466,7 @@ FETCH: // index document exists logger.Debug("bzz download: serving path", "path", pathWithIndex) - s.serveManifestEntry(logger, w, r, indexDocumentManifestEntry, !feedDereferenced) + s.serveManifestEntry(logger, w, r, indexDocumentManifestEntry, !feedDereferenced, headerOnly) return } } @@ -461,7 +480,7 @@ FETCH: // error document exists logger.Debug("bzz download: serving path", "path", errorDocumentPath) - s.serveManifestEntry(logger, w, r, errorDocumentManifestEntry, !feedDereferenced) + s.serveManifestEntry(logger, w, r, errorDocumentManifestEntry, !feedDereferenced, headerOnly) return } } @@ -475,7 +494,7 @@ FETCH: } // serve requested path - s.serveManifestEntry(logger, w, r, me, !feedDereferenced) + s.serveManifestEntry(logger, w, r, me, !feedDereferenced, headerOnly) } func (s *Service) serveManifestEntry( @@ -483,7 +502,7 @@ func (s *Service) serveManifestEntry( w http.ResponseWriter, r *http.Request, manifestEntry manifest.Entry, - etag bool, + etag, headersOnly bool, ) { additionalHeaders := http.Header{} mtdt := manifestEntry.Metadata() @@ -496,11 +515,11 @@ func (s *Service) serveManifestEntry( additionalHeaders[ContentTypeHeader] = []string{mimeType} } - s.downloadHandler(logger, w, r, manifestEntry.Reference(), additionalHeaders, etag) + s.downloadHandler(logger, w, r, manifestEntry.Reference(), additionalHeaders, etag, headersOnly) } // downloadHandler contains common logic for dowloading Swarm file from API -func (s *Service) downloadHandler(logger log.Logger, w http.ResponseWriter, r *http.Request, reference swarm.Address, additionalHeaders http.Header, etag bool) { +func (s *Service) downloadHandler(logger log.Logger, w http.ResponseWriter, r *http.Request, reference swarm.Address, additionalHeaders http.Header, etag, headersOnly bool) { headers := struct { Strategy *getter.Strategy `map:"Swarm-Redundancy-Strategy"` FallbackMode *bool `map:"Swarm-Redundancy-Fallback-Mode"` @@ -551,6 +570,12 @@ func (s *Service) downloadHandler(logger log.Logger, w http.ResponseWriter, r *h } w.Header().Set(ContentLengthHeader, strconv.FormatInt(l, 10)) w.Header().Set("Access-Control-Expose-Headers", ContentDispositionHeader) + + if headersOnly { + w.WriteHeader(http.StatusOK) + return + } + bufSize := lookaheadBufferSize(l) if headers.LookaheadBufferSize != nil { bufSize = *(headers.LookaheadBufferSize) diff --git a/pkg/api/bzz_test.go b/pkg/api/bzz_test.go index fdcb3f1209f..951cd77aef3 100644 --- a/pkg/api/bzz_test.go +++ b/pkg/api/bzz_test.go @@ -521,8 +521,18 @@ func TestBzzFiles(t *testing.T) { jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), ) - }) + t.Run("head", func(t *testing.T) { + rootHash := "65148cd89b58e91616773f5acea433f7b5a6274f2259e25f4893a332b74a7e28" + + jsonhttptest.Request(t, client, http.MethodHead, fileDownloadResource(rootHash), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(bytes.NewReader(simpleData)), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithExpectedContentLength(21), + ) + }) + }) } // TestRangeRequests validates that all endpoints are serving content with diff --git a/pkg/api/cors_test.go b/pkg/api/cors_test.go index c8b9fae3512..7163f8f9563 100644 --- a/pkg/api/cors_test.go +++ b/pkg/api/cors_test.go @@ -137,7 +137,7 @@ func TestCors(t *testing.T) { expectedMethods: "POST", }, { endpoint: "bzz/0101011", - expectedMethods: "GET", + expectedMethods: "GET, HEAD", }, { endpoint: "chunks", diff --git a/pkg/api/router.go b/pkg/api/router.go index 08aaee08fa1..d4934ca8f09 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -270,6 +270,9 @@ func (s *Service) mountAPI() { s.newTracingHandler("bzz-download"), web.FinalHandlerFunc(s.bzzDownloadHandler), ), + "HEAD": web.ChainHandlers( + web.FinalHandlerFunc(s.bzzHeadHandler), + ), }) handle("/pss/send/{topic}/{targets}", web.ChainHandlers( diff --git a/pkg/api/subdomain.go b/pkg/api/subdomain.go index 2e90c4ac1ce..305dbd1bd71 100644 --- a/pkg/api/subdomain.go +++ b/pkg/api/subdomain.go @@ -28,5 +28,5 @@ func (s *Service) subdomainHandler(w http.ResponseWriter, r *http.Request) { paths.Path = strings.TrimRight(paths.Path, "/") + "/" // NOTE: leave one slash if there was some. } - s.serveReference(logger, paths.Subdomain, paths.Path, w, r) + s.serveReference(logger, paths.Subdomain, paths.Path, w, r, false) } diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 5638c29c26c..8c9b01c8719 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -242,7 +242,7 @@ func (e encrypter) decrypt(data []byte) ([]byte, error) { func applyPolicies(e *casbin.Enforcer) error { _, err := e.AddPolicies([][]string{ - {"consumer", "/bytes/*", "GET"}, + {"consumer", "/bytes/*", "(GET)|(HEAD)"}, {"creator", "/bytes", "POST"}, {"consumer", "/chunks/*", "GET"}, {"creator", "/chunks", "POST"}, @@ -250,7 +250,7 @@ func applyPolicies(e *casbin.Enforcer) error { {"creator", "/bzz/*", "PATCH"}, {"creator", "/bzz", "POST"}, {"creator", "/bzz?*", "POST"}, - {"consumer", "/bzz/*/*", "GET"}, + {"consumer", "/bzz/*/*", "(GET)|(HEAD)"}, {"creator", "/tags", "GET"}, {"creator", "/tags?*", "GET"}, {"creator", "/tags", "POST"},