diff --git a/pkg/api/api.go b/pkg/api/api.go
index b226a5cff24..abb8312a6cc 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -98,13 +98,14 @@ const (
GasLimitHeader = "Gas-Limit"
ETagHeader = "ETag"
- AuthorizationHeader = "Authorization"
- AcceptEncodingHeader = "Accept-Encoding"
- ContentTypeHeader = "Content-Type"
- ContentDispositionHeader = "Content-Disposition"
- ContentLengthHeader = "Content-Length"
- RangeHeader = "Range"
- OriginHeader = "Origin"
+ AuthorizationHeader = "Authorization"
+ AcceptEncodingHeader = "Accept-Encoding"
+ ContentTypeHeader = "Content-Type"
+ ContentDispositionHeader = "Content-Disposition"
+ ContentLengthHeader = "Content-Length"
+ RangeHeader = "Range"
+ OriginHeader = "Origin"
+ AccessControlExposeHeaders = "Access-Control-Expose-Headers"
)
const (
diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go
index 63f366615f3..986bc663d3e 100644
--- a/pkg/api/api_test.go
+++ b/pkg/api/api_test.go
@@ -12,7 +12,6 @@ import (
"encoding/hex"
"encoding/json"
"errors"
- "io"
"math/big"
"net"
"net/http"
@@ -294,23 +293,6 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.
return httpClient, conn, ts.Listener.Addr().String(), chanStore
}
-func request(t *testing.T, client *http.Client, method, resource string, body io.Reader, responseCode int) *http.Response {
- t.Helper()
-
- req, err := http.NewRequestWithContext(context.TODO(), method, resource, body)
- if err != nil {
- t.Fatal(err)
- }
- resp, err := client.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- if resp.StatusCode != responseCode {
- t.Fatalf("got response status %s, want %v %s", resp.Status, responseCode, http.StatusText(responseCode))
- }
- return resp
-}
-
func pipelineFactory(s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface {
return func() pipeline.Interface {
return builder.NewPipelineBuilder(context.Background(), s, encrypt, rLevel)
diff --git a/pkg/api/bytes.go b/pkg/api/bytes.go
index d0d2886683b..45692f5c4d3 100644
--- a/pkg/api/bytes.go
+++ b/pkg/api/bytes.go
@@ -155,7 +155,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) {
span.LogFields(olog.Bool("success", true))
- w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader)
+ w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader)
jsonhttp.Created(w, bytesPostResponse{
Reference: encryptedReference,
})
@@ -210,7 +210,7 @@ func (s *Service) bytesHeadHandler(w http.ResponseWriter, r *http.Request) {
return
}
- w.Header().Add("Access-Control-Expose-Headers", "Accept-Ranges, Content-Encoding")
+ w.Header().Add(AccessControlExposeHeaders, "Accept-Ranges, Content-Encoding")
w.Header().Add(ContentTypeHeader, "application/octet-stream")
var span int64
diff --git a/pkg/api/bytes_test.go b/pkg/api/bytes_test.go
index f03fa8b973e..40280e44992 100644
--- a/pkg/api/bytes_test.go
+++ b/pkg/api/bytes_test.go
@@ -113,6 +113,8 @@ func TestBytes(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodGet, resource+"/"+expHash, http.StatusOK,
jsonhttptest.WithExpectedContentLength(len(content)),
jsonhttptest.WithExpectedResponse(content),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
)
})
diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go
index 14a236c183f..9acf227c6d4 100644
--- a/pkg/api/bzz.go
+++ b/pkg/api/bzz.go
@@ -304,7 +304,7 @@ func (s *Service) fileUploadHandler(
span.SetTag("tagID", tagID)
}
w.Header().Set(ETagHeader, fmt.Sprintf("%q", reference.String()))
- w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader)
+ w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader)
jsonhttp.Created(w, bzzUploadResponse{
Reference: reference,
@@ -412,7 +412,7 @@ FETCH:
// go on normally.
if !feedDereferenced {
if l, err := s.manifestFeed(ctx, m); err == nil {
- //we have a feed manifest here
+ // we have a feed manifest here
ch, cur, _, err := l.At(ctx, time.Now().Unix(), 0)
if err != nil {
logger.Debug("bzz download: feed lookup failed", "error", err)
@@ -451,7 +451,7 @@ FETCH:
// we should implement an append functionality for this specific header,
// since different parts of handlers might be overriding others' values
// resulting in inconsistent headers in the response.
- w.Header().Set("Access-Control-Expose-Headers", SwarmFeedIndexHeader)
+ w.Header().Set(AccessControlExposeHeaders, SwarmFeedIndexHeader)
goto FETCH
}
}
@@ -551,8 +551,7 @@ func (s *Service) serveManifestEntry(
mtdt := manifestEntry.Metadata()
if fname, ok := mtdt[manifest.EntryMetadataFilenameKey]; ok {
fname = filepath.Base(fname) // only keep the file name
- additionalHeaders[ContentDispositionHeader] =
- []string{fmt.Sprintf("inline; filename=\"%s\"", fname)}
+ additionalHeaders[ContentDispositionHeader] = []string{fmt.Sprintf("inline; filename=\"%s\"", escapeQuotes(fname))}
}
if mimeType, ok := mtdt[manifest.EntryMetadataContentTypeKey]; ok {
additionalHeaders[ContentTypeHeader] = []string{mimeType}
@@ -616,13 +615,15 @@ func (s *Service) downloadHandler(logger log.Logger, w http.ResponseWriter, r *h
// include additional headers
for name, values := range additionalHeaders {
- w.Header().Set(name, strings.Join(values, "; "))
+ for _, value := range values {
+ w.Header().Add(name, value)
+ }
}
if etag {
w.Header().Set(ETagHeader, fmt.Sprintf("%q", reference))
}
w.Header().Set(ContentLengthHeader, strconv.FormatInt(l, 10))
- w.Header().Set("Access-Control-Expose-Headers", ContentDispositionHeader)
+ w.Header().Add(AccessControlExposeHeaders, ContentDispositionHeader)
if headersOnly {
w.WriteHeader(http.StatusOK)
diff --git a/pkg/api/bzz_test.go b/pkg/api/bzz_test.go
index 246ed106778..55936d52568 100644
--- a/pkg/api/bzz_test.go
+++ b/pkg/api/bzz_test.go
@@ -841,6 +841,10 @@ func TestFeedIndirection(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodGet, bzzDownloadResource(manifRef.String(), ""), http.StatusOK,
jsonhttptest.WithExpectedResponse(updateData),
jsonhttptest.WithExpectedContentLength(len(updateData)),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, `inline; filename="index.html"`),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"),
)
}
@@ -1090,3 +1094,53 @@ func TestDirectUploadBzz(t *testing.T) {
}),
)
}
+
+func TestBzzDownloadHeaders(t *testing.T) {
+ t.Parallel()
+ var (
+ data = []byte("
Swarm Hello World!
")
+ logger = log.Noop
+ storer = mockstorer.New()
+ testServer, _, _, _ = newTestServer(t, testServerOptions{
+ Storer: storer,
+ Logger: logger,
+ Post: mockpost.New(mockpost.WithAcceptAll()),
+ })
+ )
+ // tar all the test case files
+ tarReader := tarFiles(t, []f{
+ {
+ data: data,
+ name: "\"index.html\"",
+ dir: "",
+ filePath: "./index.html",
+ },
+ })
+
+ var resp api.BzzUploadResponse
+
+ options := []jsonhttptest.Option{
+ jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
+ jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
+ jsonhttptest.WithRequestBody(tarReader),
+ jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
+ jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"),
+ jsonhttptest.WithUnmarshalJSONResponse(&resp),
+ jsonhttptest.WithRequestHeader(api.SwarmIndexDocumentHeader, "index.html"),
+ }
+
+ // verify directory tar upload response
+ jsonhttptest.Request(t, testServer, http.MethodPost, "/bzz", http.StatusCreated, options...)
+
+ if resp.Reference.String() == "" {
+ t.Fatalf("expected file reference, did not got any")
+ }
+
+ jsonhttptest.Request(t, testServer, http.MethodGet, "/bzz/"+resp.Reference.String(), http.StatusOK,
+ jsonhttptest.WithExpectedResponse(data),
+ jsonhttptest.WithExpectedContentLength(len(data)),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, `inline; filename="index.html"`),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"),
+ )
+}
diff --git a/pkg/api/chunk.go b/pkg/api/chunk.go
index efce3349501..1cbed96fef1 100644
--- a/pkg/api/chunk.go
+++ b/pkg/api/chunk.go
@@ -216,7 +216,7 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set(SwarmTagHeader, fmt.Sprint(tag))
}
- w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader)
+ w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader)
jsonhttp.Created(w, chunkAddressResponse{Reference: reference})
}
diff --git a/pkg/api/dirs.go b/pkg/api/dirs.go
index 23be6929acc..72c5a37ee87 100644
--- a/pkg/api/dirs.go
+++ b/pkg/api/dirs.go
@@ -134,7 +134,7 @@ func (s *Service) dirUploadHandler(
w.Header().Set(SwarmTagHeader, fmt.Sprint(tag))
span.LogFields(olog.Bool("success", true))
}
- w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader)
+ w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader)
jsonhttp.Created(w, bzzUploadResponse{
Reference: encryptedReference,
})
@@ -153,7 +153,6 @@ func storeDir(
errorFilename string,
rLevel redundancy.Level,
) (swarm.Address, error) {
-
logger := tracing.NewLoggerWithTraceID(ctx, log)
loggerV1 := logger.V(1).Build()
diff --git a/pkg/api/feed.go b/pkg/api/feed.go
index e2ab2f0affa..9f1ab16bfcc 100644
--- a/pkg/api/feed.go
+++ b/pkg/api/feed.go
@@ -11,7 +11,6 @@ import (
"io"
"net/http"
"strconv"
- "strings"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -134,18 +133,20 @@ func (s *Service) feedGetHandler(w http.ResponseWriter, r *http.Request) {
sig := socCh.Signature()
additionalHeaders := http.Header{
- ContentTypeHeader: {"application/octet-stream"},
- SwarmFeedIndexHeader: {hex.EncodeToString(curBytes)},
- SwarmFeedIndexNextHeader: {hex.EncodeToString(nextBytes)},
- SwarmSocSignatureHeader: {hex.EncodeToString(sig)},
- "Access-Control-Expose-Headers": {SwarmFeedIndexHeader, SwarmFeedIndexNextHeader, SwarmSocSignatureHeader},
+ ContentTypeHeader: {"application/octet-stream"},
+ SwarmFeedIndexHeader: {hex.EncodeToString(curBytes)},
+ SwarmFeedIndexNextHeader: {hex.EncodeToString(nextBytes)},
+ SwarmSocSignatureHeader: {hex.EncodeToString(sig)},
+ AccessControlExposeHeaders: {SwarmFeedIndexHeader, SwarmFeedIndexNextHeader, SwarmSocSignatureHeader},
}
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, ", "))
+ for _, value := range values {
+ w.Header().Add(name, value)
+ }
}
_, _ = io.Copy(w, bytes.NewReader(wc.Data()))
return
diff --git a/pkg/api/feed_test.go b/pkg/api/feed_test.go
index 843756d7237..0e2fa807ad4 100644
--- a/pkg/api/feed_test.go
+++ b/pkg/api/feed_test.go
@@ -77,6 +77,11 @@ func TestFeed_Get(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodGet, feedResource(ownerString, "aabbcc", "12"), http.StatusOK,
jsonhttptest.WithExpectedResponse(mockWrappedCh.Data()[swarm.SpanSize:]),
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
)
})
@@ -100,6 +105,11 @@ func TestFeed_Get(t *testing.T) {
jsonhttptest.WithExpectedResponse(mockWrappedCh.Data()[swarm.SpanSize:]),
jsonhttptest.WithExpectedContentLength(len(mockWrappedCh.Data()[swarm.SpanSize:])),
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
)
})
@@ -124,6 +134,11 @@ func TestFeed_Get(t *testing.T) {
jsonhttptest.WithExpectedResponse(testData),
jsonhttptest.WithExpectedContentLength(len(testData)),
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
)
})
@@ -181,6 +196,11 @@ func TestFeed_Get(t *testing.T) {
jsonhttptest.WithExpectedResponse(testData),
jsonhttptest.WithExpectedContentLength(testDataLen),
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
)
})
@@ -190,6 +210,10 @@ func TestFeed_Get(t *testing.T) {
jsonhttptest.WithExpectedResponse(testRootCh.Data()),
jsonhttptest.WithExpectedContentLength(len(testRootCh.Data())),
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
)
})
})
@@ -267,7 +291,6 @@ func TestFeed_Post(t *testing.T) {
)
})
})
-
}
// TestDirectUploadFeed tests that the direct upload endpoint give correct error message in dev mode
diff --git a/pkg/api/soc.go b/pkg/api/soc.go
index 9f0f838bfc9..54e9e4cc4d8 100644
--- a/pkg/api/soc.go
+++ b/pkg/api/soc.go
@@ -11,7 +11,6 @@ import (
"io"
"net/http"
"strconv"
- "strings"
"github.com/ethersphere/bee/v2/pkg/accesscontrol"
"github.com/ethersphere/bee/v2/pkg/cac"
@@ -257,16 +256,18 @@ func (s *Service) socGetHandler(w http.ResponseWriter, r *http.Request) {
wc := socCh.WrappedChunk()
additionalHeaders := http.Header{
- ContentTypeHeader: {"application/octet-stream"},
- SwarmSocSignatureHeader: {hex.EncodeToString(sig)},
- "Access-Control-Expose-Headers": {SwarmSocSignatureHeader},
+ ContentTypeHeader: {"application/octet-stream"},
+ SwarmSocSignatureHeader: {hex.EncodeToString(sig)},
+ AccessControlExposeHeaders: {SwarmSocSignatureHeader},
}
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, ", "))
+ for _, value := range values {
+ w.Header().Add(name, value)
+ }
}
_, _ = io.Copy(w, bytes.NewReader(wc.Data()))
return
diff --git a/pkg/api/soc_test.go b/pkg/api/soc_test.go
index 6c0d6fa0449..fb34eb82297 100644
--- a/pkg/api/soc_test.go
+++ b/pkg/api/soc_test.go
@@ -9,7 +9,6 @@ import (
"context"
"encoding/hex"
"fmt"
- "io"
"net/http"
"testing"
"time"
@@ -99,28 +98,22 @@ func TestSOC(t *testing.T) {
// try to fetch the same chunk
t.Run("chunks fetch", func(t *testing.T) {
rsrc := fmt.Sprintf("/chunks/%s", 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")
- }
+ jsonhttptest.Request(t, client, http.MethodGet, rsrc, http.StatusOK,
+ jsonhttptest.WithExpectedResponse(s.Chunk().Data()),
+ jsonhttptest.WithExpectedContentLength(len(s.Chunk().Data())),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "binary/octet-stream"),
+ )
})
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")
- }
+ jsonhttptest.Request(t, client, http.MethodGet, rsrc, http.StatusOK,
+ jsonhttptest.WithExpectedResponse(s.WrappedChunk.Data()[swarm.SpanSize:]),
+ jsonhttptest.WithExpectedContentLength(len(s.WrappedChunk.Data()[swarm.SpanSize:])),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
+ jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
+ )
})
})
diff --git a/pkg/api/util.go b/pkg/api/util.go
index a1ad148f6d5..6fdb575873a 100644
--- a/pkg/api/util.go
+++ b/pkg/api/util.go
@@ -347,3 +347,9 @@ func flattenValue(val reflect.Value) reflect.Value {
}
return val
}
+
+var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
+
+func escapeQuotes(s string) string {
+ return quoteEscaper.Replace(s)
+}