Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/20251017171656.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:sparkles: `[tus]` Add utilities for generating header values
43 changes: 43 additions & 0 deletions utils/http/headers/tus/tus.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tus

import (
"context"
"fmt"
"net/url"
"regexp"
"strings"
Expand All @@ -16,6 +17,19 @@ import (

const KeyTUSMetadata = "filename"

// GenerateTUSChecksumHeader generate the checksum header value
// See https://tus.io/protocols/resumable-upload#upload-checksum
func GenerateTUSChecksumHeader(hashAlgo, hash string) (header string, err error) {
hashAlgoC, err := hashing.DetermineHashingAlgorithmCanonicalReference(hashAlgo)
if err != nil {
err = commonerrors.WrapError(commonerrors.ErrUnsupported, err, "hashing algorithm is not supported")
return
}
base64Hash := base64.EncodeString(hash)
header = fmt.Sprintf("%v %v", strings.ToLower(hashAlgoC), base64Hash)
return
}

// ParseTUSHash parses the checksum header value and tries to determine the different elements it contains.
// See https://tus.io/protocols/resumable-upload#upload-checksum
func ParseTUSHash(checksum string) (hashAlgo, hash string, err error) {
Expand Down Expand Up @@ -47,6 +61,17 @@ func ParseTUSHash(checksum string) (hashAlgo, hash string, err error) {
return
}

// GenerateTUSConcatFinalHeader generates the `Concat` header value https://tus.io/protocols/resumable-upload#upload-concat
func GenerateTUSConcatFinalHeader(partials []*url.URL) (header string, err error) {
header = fmt.Sprintf("final;%v", strings.Join(collection.Map[*url.URL, string](partials, func(u *url.URL) string {
if u == nil {
return ""
}
return u.EscapedPath()
}), " "))
return
}

// ParseTUSConcatHeader parses the `Concat` header value https://tus.io/protocols/resumable-upload#upload-concat
func ParseTUSConcatHeader(concat string) (isPartial bool, partials []*url.URL, err error) {
header := strings.TrimSpace(concat)
Expand All @@ -73,6 +98,24 @@ func ParseTUSConcatHeader(concat string) (isPartial bool, partials []*url.URL, e
return
}

// GenerateTUSMetadataHeader generates the `metadata` header value https://tus.io/protocols/resumable-upload#upload-metadata
func GenerateTUSMetadataHeader(filename *string, elements map[string]any) (header string, err error) {
newMap := make(map[string]string, len(elements))
for key, value := range elements {
valueB, ok := value.(bool)
if ok && valueB {
newMap[key] = ""
} else {
newMap[key] = base64.EncodeString(fmt.Sprintf("%v", value))
}
}
if !reflection.IsEmpty(filename) {
newMap[KeyTUSMetadata] = base64.EncodeString(field.OptionalString(filename, ""))
}
header = strings.Join(collection.ConvertMapToPairSlice(newMap, " "), ",")
return
}

// ParseTUSMetadataHeader parses the `metadata` header value https://tus.io/protocols/resumable-upload#upload-metadata
func ParseTUSMetadataHeader(header string) (filename *string, elements map[string]any, err error) {
h := strings.TrimSpace(header)
Expand Down
53 changes: 53 additions & 0 deletions utils/http/headers/tus/tus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ func TestParseTUSHash(t *testing.T) {
expectedAlgo: hashing.HashSha256,
expectedChecksum: "this is a test value obviously",
},
{
header: func() string {
header, err := GenerateTUSChecksumHeader(hashing.HashSha256, "the cloudy crew: josh jennings, kem govender, bianca bunaciu, adrien cabarbaye, abdelrahman abdelraouf, phuong linh nguyen")
require.NoError(t, err)
return header
}(),
expectedAlgo: hashing.HashSha256,
expectedChecksum: "the cloudy crew: josh jennings, kem govender, bianca bunaciu, adrien cabarbaye, abdelrahman abdelraouf, phuong linh nguyen",
},
{
header: "sha1-md5 Lve95gjOVATpfV8EL5X4nxwjKHE=",
expectedAlgo: "sha1-md5",
Expand Down Expand Up @@ -146,6 +155,31 @@ func TestParseTUSConcatHeader(t *testing.T) {
"/y",
},
},
{
input: func() string {
header, err := GenerateTUSConcatFinalHeader(
[]*url.URL{
func() *url.URL {
u, err := url.Parse("/x")
require.NoError(t, err)
return u
}(),
func() *url.URL {
u, err := url.Parse("/ywh/h")
require.NoError(t, err)
return u
}(),
},
)
require.NoError(t, err)
return header
}(),
isPartial: false,
expectedPartialURL: []string{
"/x",
"/ywh/h",
},
},
{
input: fmt.Sprintf(" final; %v %v ", url1, url2),
isPartial: false,
Expand Down Expand Up @@ -276,6 +310,25 @@ func TestParseTUSMetadataHeader(t *testing.T) {
"empty": true,
},
},
{
input: func() string {
header, err := GenerateTUSMetadataHeader(field.ToOptionalString("test.txt"), map[string]any{
"meta": "y",
"test": false,
"empty": true,
},
)
require.NoError(t, err)
return header
}(),
expectedFilename: field.ToOptionalString("test.txt"),
expectedElements: map[string]any{
"filename": "test.txt",
"meta": "y",
"test": "false",
"empty": true,
},
},
{
input: "note " + toBase64Encoded("A/B+C=D=="),
expectedElements: map[string]any{
Expand Down
Loading