diff --git a/pkg/api/api.go b/pkg/api/api.go index 5709ab0ef76..163888c64aa 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -75,6 +75,7 @@ const ( SwarmEncryptHeader = "Swarm-Encrypt" SwarmIndexDocumentHeader = "Swarm-Index-Document" SwarmErrorDocumentHeader = "Swarm-Error-Document" + SwarmSocSignature = "Swarm-Soc-Signature" SwarmFeedIndexHeader = "Swarm-Feed-Index" SwarmFeedIndexNextHeader = "Swarm-Feed-Index-Next" SwarmOnlyRootChunk = "Swarm-Only-Root-Chunk" diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index 6c24ed5e5fe..985ddb97213 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -552,7 +552,7 @@ func (s *Service) downloadHandler(logger log.Logger, w http.ResponseWriter, r *h l int64 ) if rootCh != nil { - reader, l, err = joiner.NewWithRootCh(ctx, s.storer.Download(cache), s.storer.Cache(), reference, rootCh) + reader, l, err = joiner.NewJoiner(ctx, s.storer.Download(cache), s.storer.Cache(), reference, rootCh) } else { reader, l, err = joiner.New(ctx, s.storer.Download(cache), s.storer.Cache(), reference) } diff --git a/pkg/api/router.go b/pkg/api/router.go index 232920e9090..3b6101e68e9 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -68,10 +68,12 @@ func (s *Service) MountAPI() { "/bytes", "/chunks", "/feeds", + "/soc", rootPath + "/bzz", rootPath + "/bytes", rootPath + "/chunks", rootPath + "/feeds", + rootPath + "/soc", } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -242,6 +244,7 @@ func (s *Service) mountAPI() { }) handle("/soc/{owner}/{id}", jsonhttp.MethodHandler{ + "GET": http.HandlerFunc(s.socGetHandler), "POST": web.ChainHandlers( jsonhttp.NewMaxBodyBytesHandler(swarm.ChunkWithSpanSize), web.FinalHandlerFunc(s.socUploadHandler), diff --git a/pkg/api/soc.go b/pkg/api/soc.go index 0abf338deb9..16d83854966 100644 --- a/pkg/api/soc.go +++ b/pkg/api/soc.go @@ -5,9 +5,13 @@ package api import ( + "bytes" + "encoding/hex" "errors" "io" "net/http" + "strconv" + "strings" "github.com/ethersphere/bee/v2/pkg/cac" "github.com/ethersphere/bee/v2/pkg/jsonhttp" @@ -173,3 +177,59 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { jsonhttp.Created(w, chunkAddressResponse{Reference: sch.Address()}) } + +func (s *Service) socGetHandler(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("get_soc").Build() + + paths := struct { + Owner []byte `map:"owner" validate:"required"` + ID []byte `map:"id" validate:"required"` + }{} + if response := s.mapStructure(mux.Vars(r), &paths); response != nil { + response("invalid path params", logger, w) + return + } + + headers := struct { + OnlyRootChunk bool `map:"Swarm-Only-Root-Chunk"` + }{} + if response := s.mapStructure(r.Header, &headers); response != nil { + response("invalid header params", logger, w) + return + } + + address, err := soc.CreateAddress(paths.ID, paths.Owner) + if err != nil { + jsonhttp.BadRequest(w, "soc address cannot be created") + } + + sch, err := s.storer.ChunkStore().Get(r.Context(), address) + if err != nil { + jsonhttp.NotFound(w, "requested chunk cannot be retrievable") + } + socCh, err := soc.FromChunk(sch) + if err != nil { + jsonhttp.InternalServerError(w, "chunk is not a single owner chunk") + } + + sig := socCh.Signature() + wc := socCh.WrappedChunk() + + additionalHeaders := http.Header{ + ContentTypeHeader: {"application/octet-stream"}, + SwarmSocSignature: {hex.EncodeToString(sig)}, + "Access-Control-Expose-Headers": {SwarmSocSignature}, + } + + if headers.OnlyRootChunk { + w.Header().Set(ContentLengthHeader, strconv.Itoa(len(wc.Data()))) + // include additional headers + for name, values := range additionalHeaders { + w.Header().Set(name, strings.Join(values, ", ")) + } + _, _ = io.Copy(w, bytes.NewReader(wc.Data())) + return + } + + s.downloadHandler(logger, w, r, wc.Address(), additionalHeaders, true, false, wc) +} diff --git a/pkg/api/soc_test.go b/pkg/api/soc_test.go index 79e72ffb603..55e1dfe0a17 100644 --- a/pkg/api/soc_test.go +++ b/pkg/api/soc_test.go @@ -85,16 +85,31 @@ func TestSOC(t *testing.T) { ) // try to fetch the same chunk - rsrc := fmt.Sprintf("/chunks/" + s.Address().String()) - resp := request(t, client, http.MethodGet, rsrc, nil, http.StatusOK) - data, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } + t.Run("chunks fetch", func(t *testing.T) { + rsrc := fmt.Sprintf("/chunks/" + s.Address().String()) + resp := request(t, client, http.MethodGet, rsrc, nil, http.StatusOK) + data, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(s.Chunk().Data(), data) { + t.Fatal("data retrieved doesn't match uploaded content") + } + }) - if !bytes.Equal(s.Chunk().Data(), data) { - t.Fatal("data retrieved doesn't match uploaded content") - } + t.Run("soc fetch", func(t *testing.T) { + rsrc := fmt.Sprintf("/soc/%s/%s", hex.EncodeToString(s.Owner), hex.EncodeToString(s.ID)) + resp := request(t, client, http.MethodGet, rsrc, nil, http.StatusOK) + data, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(s.WrappedChunk.Data()[swarm.SpanSize:], data) { + t.Fatal("data retrieved doesn't match uploaded content") + } + }) }) t.Run("postage", func(t *testing.T) {