Skip to content

Commit 78818f2

Browse files
authored
[tus] Add utilities for generating header values (#731)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description - `[tus]` Add utilities for generating header values ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update).
1 parent f29e265 commit 78818f2

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

changes/20251017171656.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[tus]` Add utilities for generating header values

utils/http/headers/tus/tus.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tus
22

33
import (
44
"context"
5+
"fmt"
56
"net/url"
67
"regexp"
78
"strings"
@@ -16,6 +17,19 @@ import (
1617

1718
const KeyTUSMetadata = "filename"
1819

20+
// GenerateTUSChecksumHeader generate the checksum header value
21+
// See https://tus.io/protocols/resumable-upload#upload-checksum
22+
func GenerateTUSChecksumHeader(hashAlgo, hash string) (header string, err error) {
23+
hashAlgoC, err := hashing.DetermineHashingAlgorithmCanonicalReference(hashAlgo)
24+
if err != nil {
25+
err = commonerrors.WrapError(commonerrors.ErrUnsupported, err, "hashing algorithm is not supported")
26+
return
27+
}
28+
base64Hash := base64.EncodeString(hash)
29+
header = fmt.Sprintf("%v %v", strings.ToLower(hashAlgoC), base64Hash)
30+
return
31+
}
32+
1933
// ParseTUSHash parses the checksum header value and tries to determine the different elements it contains.
2034
// See https://tus.io/protocols/resumable-upload#upload-checksum
2135
func ParseTUSHash(checksum string) (hashAlgo, hash string, err error) {
@@ -47,6 +61,17 @@ func ParseTUSHash(checksum string) (hashAlgo, hash string, err error) {
4761
return
4862
}
4963

64+
// GenerateTUSConcatFinalHeader generates the `Concat` header value https://tus.io/protocols/resumable-upload#upload-concat
65+
func GenerateTUSConcatFinalHeader(partials []*url.URL) (header string, err error) {
66+
header = fmt.Sprintf("final;%v", strings.Join(collection.Map[*url.URL, string](partials, func(u *url.URL) string {
67+
if u == nil {
68+
return ""
69+
}
70+
return u.EscapedPath()
71+
}), " "))
72+
return
73+
}
74+
5075
// ParseTUSConcatHeader parses the `Concat` header value https://tus.io/protocols/resumable-upload#upload-concat
5176
func ParseTUSConcatHeader(concat string) (isPartial bool, partials []*url.URL, err error) {
5277
header := strings.TrimSpace(concat)
@@ -73,6 +98,24 @@ func ParseTUSConcatHeader(concat string) (isPartial bool, partials []*url.URL, e
7398
return
7499
}
75100

101+
// GenerateTUSMetadataHeader generates the `metadata` header value https://tus.io/protocols/resumable-upload#upload-metadata
102+
func GenerateTUSMetadataHeader(filename *string, elements map[string]any) (header string, err error) {
103+
newMap := make(map[string]string, len(elements))
104+
for key, value := range elements {
105+
valueB, ok := value.(bool)
106+
if ok && valueB {
107+
newMap[key] = ""
108+
} else {
109+
newMap[key] = base64.EncodeString(fmt.Sprintf("%v", value))
110+
}
111+
}
112+
if !reflection.IsEmpty(filename) {
113+
newMap[KeyTUSMetadata] = base64.EncodeString(field.OptionalString(filename, ""))
114+
}
115+
header = strings.Join(collection.ConvertMapToPairSlice(newMap, " "), ",")
116+
return
117+
}
118+
76119
// ParseTUSMetadataHeader parses the `metadata` header value https://tus.io/protocols/resumable-upload#upload-metadata
77120
func ParseTUSMetadataHeader(header string) (filename *string, elements map[string]any, err error) {
78121
h := strings.TrimSpace(header)

utils/http/headers/tus/tus_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ func TestParseTUSHash(t *testing.T) {
7777
expectedAlgo: hashing.HashSha256,
7878
expectedChecksum: "this is a test value obviously",
7979
},
80+
{
81+
header: func() string {
82+
header, err := GenerateTUSChecksumHeader(hashing.HashSha256, "the cloudy crew: josh jennings, kem govender, bianca bunaciu, adrien cabarbaye, abdelrahman abdelraouf, phuong linh nguyen")
83+
require.NoError(t, err)
84+
return header
85+
}(),
86+
expectedAlgo: hashing.HashSha256,
87+
expectedChecksum: "the cloudy crew: josh jennings, kem govender, bianca bunaciu, adrien cabarbaye, abdelrahman abdelraouf, phuong linh nguyen",
88+
},
8089
{
8190
header: "sha1-md5 Lve95gjOVATpfV8EL5X4nxwjKHE=",
8291
expectedAlgo: "sha1-md5",
@@ -146,6 +155,31 @@ func TestParseTUSConcatHeader(t *testing.T) {
146155
"/y",
147156
},
148157
},
158+
{
159+
input: func() string {
160+
header, err := GenerateTUSConcatFinalHeader(
161+
[]*url.URL{
162+
func() *url.URL {
163+
u, err := url.Parse("/x")
164+
require.NoError(t, err)
165+
return u
166+
}(),
167+
func() *url.URL {
168+
u, err := url.Parse("/ywh/h")
169+
require.NoError(t, err)
170+
return u
171+
}(),
172+
},
173+
)
174+
require.NoError(t, err)
175+
return header
176+
}(),
177+
isPartial: false,
178+
expectedPartialURL: []string{
179+
"/x",
180+
"/ywh/h",
181+
},
182+
},
149183
{
150184
input: fmt.Sprintf(" final; %v %v ", url1, url2),
151185
isPartial: false,
@@ -276,6 +310,25 @@ func TestParseTUSMetadataHeader(t *testing.T) {
276310
"empty": true,
277311
},
278312
},
313+
{
314+
input: func() string {
315+
header, err := GenerateTUSMetadataHeader(field.ToOptionalString("test.txt"), map[string]any{
316+
"meta": "y",
317+
"test": false,
318+
"empty": true,
319+
},
320+
)
321+
require.NoError(t, err)
322+
return header
323+
}(),
324+
expectedFilename: field.ToOptionalString("test.txt"),
325+
expectedElements: map[string]any{
326+
"filename": "test.txt",
327+
"meta": "y",
328+
"test": "false",
329+
"empty": true,
330+
},
331+
},
279332
{
280333
input: "note " + toBase64Encoded("A/B+C=D=="),
281334
expectedElements: map[string]any{

0 commit comments

Comments
 (0)