Skip to content

Commit c70cbfb

Browse files
fix(api): resolve header overwriting and delimiter issue in Access-Control-Expose-Headers (#4960)
1 parent e058ad5 commit c70cbfb

File tree

13 files changed

+132
-69
lines changed

13 files changed

+132
-69
lines changed

pkg/api/api.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,14 @@ const (
9898
GasLimitHeader = "Gas-Limit"
9999
ETagHeader = "ETag"
100100

101-
AuthorizationHeader = "Authorization"
102-
AcceptEncodingHeader = "Accept-Encoding"
103-
ContentTypeHeader = "Content-Type"
104-
ContentDispositionHeader = "Content-Disposition"
105-
ContentLengthHeader = "Content-Length"
106-
RangeHeader = "Range"
107-
OriginHeader = "Origin"
101+
AuthorizationHeader = "Authorization"
102+
AcceptEncodingHeader = "Accept-Encoding"
103+
ContentTypeHeader = "Content-Type"
104+
ContentDispositionHeader = "Content-Disposition"
105+
ContentLengthHeader = "Content-Length"
106+
RangeHeader = "Range"
107+
OriginHeader = "Origin"
108+
AccessControlExposeHeaders = "Access-Control-Expose-Headers"
108109
)
109110

110111
const (

pkg/api/api_test.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"encoding/hex"
1313
"encoding/json"
1414
"errors"
15-
"io"
1615
"math/big"
1716
"net"
1817
"net/http"
@@ -294,23 +293,6 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.
294293
return httpClient, conn, ts.Listener.Addr().String(), chanStore
295294
}
296295

297-
func request(t *testing.T, client *http.Client, method, resource string, body io.Reader, responseCode int) *http.Response {
298-
t.Helper()
299-
300-
req, err := http.NewRequestWithContext(context.TODO(), method, resource, body)
301-
if err != nil {
302-
t.Fatal(err)
303-
}
304-
resp, err := client.Do(req)
305-
if err != nil {
306-
t.Fatal(err)
307-
}
308-
if resp.StatusCode != responseCode {
309-
t.Fatalf("got response status %s, want %v %s", resp.Status, responseCode, http.StatusText(responseCode))
310-
}
311-
return resp
312-
}
313-
314296
func pipelineFactory(s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface {
315297
return func() pipeline.Interface {
316298
return builder.NewPipelineBuilder(context.Background(), s, encrypt, rLevel)

pkg/api/bytes.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) {
155155

156156
span.LogFields(olog.Bool("success", true))
157157

158-
w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader)
158+
w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader)
159159
jsonhttp.Created(w, bytesPostResponse{
160160
Reference: encryptedReference,
161161
})
@@ -210,7 +210,7 @@ func (s *Service) bytesHeadHandler(w http.ResponseWriter, r *http.Request) {
210210
return
211211
}
212212

213-
w.Header().Add("Access-Control-Expose-Headers", "Accept-Ranges, Content-Encoding")
213+
w.Header().Add(AccessControlExposeHeaders, "Accept-Ranges, Content-Encoding")
214214
w.Header().Add(ContentTypeHeader, "application/octet-stream")
215215
var span int64
216216

pkg/api/bytes_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ func TestBytes(t *testing.T) {
113113
jsonhttptest.Request(t, client, http.MethodGet, resource+"/"+expHash, http.StatusOK,
114114
jsonhttptest.WithExpectedContentLength(len(content)),
115115
jsonhttptest.WithExpectedResponse(content),
116+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
117+
jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
116118
)
117119
})
118120

pkg/api/bzz.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ func (s *Service) fileUploadHandler(
304304
span.SetTag("tagID", tagID)
305305
}
306306
w.Header().Set(ETagHeader, fmt.Sprintf("%q", reference.String()))
307-
w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader)
307+
w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader)
308308

309309
jsonhttp.Created(w, bzzUploadResponse{
310310
Reference: reference,
@@ -412,7 +412,7 @@ FETCH:
412412
// go on normally.
413413
if !feedDereferenced {
414414
if l, err := s.manifestFeed(ctx, m); err == nil {
415-
//we have a feed manifest here
415+
// we have a feed manifest here
416416
ch, cur, _, err := l.At(ctx, time.Now().Unix(), 0)
417417
if err != nil {
418418
logger.Debug("bzz download: feed lookup failed", "error", err)
@@ -451,7 +451,7 @@ FETCH:
451451
// we should implement an append functionality for this specific header,
452452
// since different parts of handlers might be overriding others' values
453453
// resulting in inconsistent headers in the response.
454-
w.Header().Set("Access-Control-Expose-Headers", SwarmFeedIndexHeader)
454+
w.Header().Set(AccessControlExposeHeaders, SwarmFeedIndexHeader)
455455
goto FETCH
456456
}
457457
}
@@ -551,8 +551,7 @@ func (s *Service) serveManifestEntry(
551551
mtdt := manifestEntry.Metadata()
552552
if fname, ok := mtdt[manifest.EntryMetadataFilenameKey]; ok {
553553
fname = filepath.Base(fname) // only keep the file name
554-
additionalHeaders[ContentDispositionHeader] =
555-
[]string{fmt.Sprintf("inline; filename=\"%s\"", fname)}
554+
additionalHeaders[ContentDispositionHeader] = []string{fmt.Sprintf("inline; filename=\"%s\"", escapeQuotes(fname))}
556555
}
557556
if mimeType, ok := mtdt[manifest.EntryMetadataContentTypeKey]; ok {
558557
additionalHeaders[ContentTypeHeader] = []string{mimeType}
@@ -616,13 +615,15 @@ func (s *Service) downloadHandler(logger log.Logger, w http.ResponseWriter, r *h
616615

617616
// include additional headers
618617
for name, values := range additionalHeaders {
619-
w.Header().Set(name, strings.Join(values, "; "))
618+
for _, value := range values {
619+
w.Header().Add(name, value)
620+
}
620621
}
621622
if etag {
622623
w.Header().Set(ETagHeader, fmt.Sprintf("%q", reference))
623624
}
624625
w.Header().Set(ContentLengthHeader, strconv.FormatInt(l, 10))
625-
w.Header().Set("Access-Control-Expose-Headers", ContentDispositionHeader)
626+
w.Header().Add(AccessControlExposeHeaders, ContentDispositionHeader)
626627

627628
if headersOnly {
628629
w.WriteHeader(http.StatusOK)

pkg/api/bzz_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,10 @@ func TestFeedIndirection(t *testing.T) {
841841
jsonhttptest.Request(t, client, http.MethodGet, bzzDownloadResource(manifRef.String(), ""), http.StatusOK,
842842
jsonhttptest.WithExpectedResponse(updateData),
843843
jsonhttptest.WithExpectedContentLength(len(updateData)),
844+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
845+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
846+
jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, `inline; filename="index.html"`),
847+
jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"),
844848
)
845849
}
846850

@@ -1090,3 +1094,53 @@ func TestDirectUploadBzz(t *testing.T) {
10901094
}),
10911095
)
10921096
}
1097+
1098+
func TestBzzDownloadHeaders(t *testing.T) {
1099+
t.Parallel()
1100+
var (
1101+
data = []byte("<h1>Swarm Hello World!</h1>")
1102+
logger = log.Noop
1103+
storer = mockstorer.New()
1104+
testServer, _, _, _ = newTestServer(t, testServerOptions{
1105+
Storer: storer,
1106+
Logger: logger,
1107+
Post: mockpost.New(mockpost.WithAcceptAll()),
1108+
})
1109+
)
1110+
// tar all the test case files
1111+
tarReader := tarFiles(t, []f{
1112+
{
1113+
data: data,
1114+
name: "\"index.html\"",
1115+
dir: "",
1116+
filePath: "./index.html",
1117+
},
1118+
})
1119+
1120+
var resp api.BzzUploadResponse
1121+
1122+
options := []jsonhttptest.Option{
1123+
jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
1124+
jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
1125+
jsonhttptest.WithRequestBody(tarReader),
1126+
jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
1127+
jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"),
1128+
jsonhttptest.WithUnmarshalJSONResponse(&resp),
1129+
jsonhttptest.WithRequestHeader(api.SwarmIndexDocumentHeader, "index.html"),
1130+
}
1131+
1132+
// verify directory tar upload response
1133+
jsonhttptest.Request(t, testServer, http.MethodPost, "/bzz", http.StatusCreated, options...)
1134+
1135+
if resp.Reference.String() == "" {
1136+
t.Fatalf("expected file reference, did not got any")
1137+
}
1138+
1139+
jsonhttptest.Request(t, testServer, http.MethodGet, "/bzz/"+resp.Reference.String(), http.StatusOK,
1140+
jsonhttptest.WithExpectedResponse(data),
1141+
jsonhttptest.WithExpectedContentLength(len(data)),
1142+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
1143+
jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, `inline; filename="index.html"`),
1144+
jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"),
1145+
)
1146+
}

pkg/api/chunk.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) {
216216
w.Header().Set(SwarmTagHeader, fmt.Sprint(tag))
217217
}
218218

219-
w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader)
219+
w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader)
220220
jsonhttp.Created(w, chunkAddressResponse{Reference: reference})
221221
}
222222

pkg/api/dirs.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func (s *Service) dirUploadHandler(
134134
w.Header().Set(SwarmTagHeader, fmt.Sprint(tag))
135135
span.LogFields(olog.Bool("success", true))
136136
}
137-
w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader)
137+
w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader)
138138
jsonhttp.Created(w, bzzUploadResponse{
139139
Reference: encryptedReference,
140140
})
@@ -153,7 +153,6 @@ func storeDir(
153153
errorFilename string,
154154
rLevel redundancy.Level,
155155
) (swarm.Address, error) {
156-
157156
logger := tracing.NewLoggerWithTraceID(ctx, log)
158157
loggerV1 := logger.V(1).Build()
159158

pkg/api/feed.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"io"
1212
"net/http"
1313
"strconv"
14-
"strings"
1514
"time"
1615

1716
"github.com/ethereum/go-ethereum/common"
@@ -134,18 +133,20 @@ func (s *Service) feedGetHandler(w http.ResponseWriter, r *http.Request) {
134133
sig := socCh.Signature()
135134

136135
additionalHeaders := http.Header{
137-
ContentTypeHeader: {"application/octet-stream"},
138-
SwarmFeedIndexHeader: {hex.EncodeToString(curBytes)},
139-
SwarmFeedIndexNextHeader: {hex.EncodeToString(nextBytes)},
140-
SwarmSocSignatureHeader: {hex.EncodeToString(sig)},
141-
"Access-Control-Expose-Headers": {SwarmFeedIndexHeader, SwarmFeedIndexNextHeader, SwarmSocSignatureHeader},
136+
ContentTypeHeader: {"application/octet-stream"},
137+
SwarmFeedIndexHeader: {hex.EncodeToString(curBytes)},
138+
SwarmFeedIndexNextHeader: {hex.EncodeToString(nextBytes)},
139+
SwarmSocSignatureHeader: {hex.EncodeToString(sig)},
140+
AccessControlExposeHeaders: {SwarmFeedIndexHeader, SwarmFeedIndexNextHeader, SwarmSocSignatureHeader},
142141
}
143142

144143
if headers.OnlyRootChunk {
145144
w.Header().Set(ContentLengthHeader, strconv.Itoa(len(wc.Data())))
146145
// include additional headers
147146
for name, values := range additionalHeaders {
148-
w.Header().Set(name, strings.Join(values, ", "))
147+
for _, value := range values {
148+
w.Header().Add(name, value)
149+
}
149150
}
150151
_, _ = io.Copy(w, bytes.NewReader(wc.Data()))
151152
return

pkg/api/feed_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ func TestFeed_Get(t *testing.T) {
7777
jsonhttptest.Request(t, client, http.MethodGet, feedResource(ownerString, "aabbcc", "12"), http.StatusOK,
7878
jsonhttptest.WithExpectedResponse(mockWrappedCh.Data()[swarm.SpanSize:]),
7979
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
80+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
81+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
82+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
83+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
84+
jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
8085
)
8186
})
8287

@@ -100,6 +105,11 @@ func TestFeed_Get(t *testing.T) {
100105
jsonhttptest.WithExpectedResponse(mockWrappedCh.Data()[swarm.SpanSize:]),
101106
jsonhttptest.WithExpectedContentLength(len(mockWrappedCh.Data()[swarm.SpanSize:])),
102107
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
108+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
109+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
110+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
111+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
112+
jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
103113
)
104114
})
105115

@@ -124,6 +134,11 @@ func TestFeed_Get(t *testing.T) {
124134
jsonhttptest.WithExpectedResponse(testData),
125135
jsonhttptest.WithExpectedContentLength(len(testData)),
126136
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
137+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
138+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
139+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
140+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
141+
jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
127142
)
128143
})
129144

@@ -181,6 +196,11 @@ func TestFeed_Get(t *testing.T) {
181196
jsonhttptest.WithExpectedResponse(testData),
182197
jsonhttptest.WithExpectedContentLength(testDataLen),
183198
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
199+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
200+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
201+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
202+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.ContentDispositionHeader),
203+
jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
184204
)
185205
})
186206

@@ -190,6 +210,10 @@ func TestFeed_Get(t *testing.T) {
190210
jsonhttptest.WithExpectedResponse(testRootCh.Data()),
191211
jsonhttptest.WithExpectedContentLength(len(testRootCh.Data())),
192212
jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
213+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexHeader),
214+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmFeedIndexNextHeader),
215+
jsonhttptest.WithExpectedResponseHeader(api.AccessControlExposeHeaders, api.SwarmSocSignatureHeader),
216+
jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/octet-stream"),
193217
)
194218
})
195219
})
@@ -267,7 +291,6 @@ func TestFeed_Post(t *testing.T) {
267291
)
268292
})
269293
})
270-
271294
}
272295

273296
// TestDirectUploadFeed tests that the direct upload endpoint give correct error message in dev mode

0 commit comments

Comments
 (0)