Skip to content

Commit

Permalink
fix(api): resolve header overwriting and delimiter issue in Access-Co…
Browse files Browse the repository at this point in the history
…ntrol-Expose-Headers (#4960)
  • Loading branch information
gacevicljubisa authored Feb 3, 2025
1 parent e058ad5 commit c70cbfb
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 69 deletions.
15 changes: 8 additions & 7 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
18 changes: 0 additions & 18 deletions pkg/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"encoding/hex"
"encoding/json"
"errors"
"io"
"math/big"
"net"
"net/http"
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions pkg/api/bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)
})

Expand Down
15 changes: 8 additions & 7 deletions pkg/api/bzz.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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)
Expand Down
54 changes: 54 additions & 0 deletions pkg/api/bzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)
}

Expand Down Expand Up @@ -1090,3 +1094,53 @@ func TestDirectUploadBzz(t *testing.T) {
}),
)
}

func TestBzzDownloadHeaders(t *testing.T) {
t.Parallel()
var (
data = []byte("<h1>Swarm Hello World!</h1>")
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"),
)
}
2 changes: 1 addition & 1 deletion pkg/api/chunk.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
}

Expand Down
3 changes: 1 addition & 2 deletions pkg/api/dirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand All @@ -153,7 +153,6 @@ func storeDir(
errorFilename string,
rLevel redundancy.Level,
) (swarm.Address, error) {

logger := tracing.NewLoggerWithTraceID(ctx, log)
loggerV1 := logger.V(1).Build()

Expand Down
15 changes: 8 additions & 7 deletions pkg/api/feed.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"io"
"net/http"
"strconv"
"strings"
"time"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -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
Expand Down
25 changes: 24 additions & 1 deletion pkg/api/feed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)
})

Expand All @@ -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"),
)
})

Expand All @@ -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"),
)
})

Expand Down Expand Up @@ -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"),
)
})

Expand All @@ -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"),
)
})
})
Expand Down Expand Up @@ -267,7 +291,6 @@ func TestFeed_Post(t *testing.T) {
)
})
})

}

// TestDirectUploadFeed tests that the direct upload endpoint give correct error message in dev mode
Expand Down
11 changes: 6 additions & 5 deletions pkg/api/soc.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"io"
"net/http"
"strconv"
"strings"

"github.com/ethersphere/bee/v2/pkg/accesscontrol"
"github.com/ethersphere/bee/v2/pkg/cac"
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit c70cbfb

Please sign in to comment.