mirror of
https://github.com/ProtonMail/go-proton-api.git
synced 2025-12-23 23:57:50 -05:00
fix(GODT-2212): Preserve header order when building messages
Ensure order of parsed header field is recorded alongside the values.
This commit is contained in:
committed by
LBeernaertProton
parent
3de73982c7
commit
331ad8e6d5
1
go.mod
1
go.mod
@@ -18,6 +18,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.2
|
||||
github.com/stretchr/testify v1.8.3
|
||||
github.com/urfave/cli/v2 v2.24.4
|
||||
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a
|
||||
go.uber.org/goleak v1.2.1
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/net v0.10.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -120,6 +120,8 @@ github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6f
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a h1:DxppxFKRqJ8WD6oJ3+ZXKDY0iMONQDl5UTg2aTyHh8k=
|
||||
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a/go.mod h1:NREvu3a57BaK0R1+ztrEzHWiZAihohNLQ6trPxlIqZI=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
|
||||
@@ -3,33 +3,50 @@ package proton
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"gitlab.com/c0b/go-ordered-json"
|
||||
)
|
||||
|
||||
var ErrBadHeader = errors.New("bad header")
|
||||
|
||||
type Headers map[string][]string
|
||||
type Headers struct {
|
||||
Values map[string][]string
|
||||
Order []string
|
||||
}
|
||||
|
||||
func (h *Headers) UnmarshalJSON(b []byte) error {
|
||||
type rawHeaders map[string]any
|
||||
|
||||
raw := make(rawHeaders)
|
||||
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
// Need to use a different type to deserialize, because there still is no official way for json to decode an object
|
||||
// with the fields in order https://github.com/golang/go/issues/27179.
|
||||
orderedMap := ordered.NewOrderedMap()
|
||||
if err := orderedMap.UnmarshalJSON(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header := make(Headers)
|
||||
header := Headers{
|
||||
Values: make(map[string][]string, len(raw)),
|
||||
Order: make([]string, 0, len(raw)),
|
||||
}
|
||||
|
||||
for key, val := range raw {
|
||||
switch val := val.(type) {
|
||||
iter := orderedMap.EntriesIter()
|
||||
|
||||
for {
|
||||
entry, ok := iter()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
switch val := entry.Value.(type) {
|
||||
case string:
|
||||
header[key] = []string{val}
|
||||
header.Values[entry.Key] = []string{val}
|
||||
|
||||
case []any:
|
||||
for _, val := range val {
|
||||
switch val := val.(type) {
|
||||
case string:
|
||||
header[key] = append(header[key], val)
|
||||
header.Values[entry.Key] = append(header.Values[entry.Key], val)
|
||||
|
||||
default:
|
||||
return ErrBadHeader
|
||||
@@ -39,9 +56,54 @@ func (h *Headers) UnmarshalJSON(b []byte) error {
|
||||
default:
|
||||
return ErrBadHeader
|
||||
}
|
||||
|
||||
header.Order = append(header.Order, entry.Key)
|
||||
}
|
||||
|
||||
*h = header
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h Headers) MarshalJSON() ([]byte, error) {
|
||||
// Manually Serialize to preserve oder
|
||||
if len(h.Values) == 0 {
|
||||
return []byte{'{', '}'}, nil
|
||||
}
|
||||
|
||||
out := make([]byte, 0, 64)
|
||||
|
||||
out = append(out, '{')
|
||||
|
||||
for _, k := range h.Order {
|
||||
v := h.Values[k]
|
||||
|
||||
if len(v) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key, err := json.Marshal(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var val []byte
|
||||
if len(v) == 1 {
|
||||
val, err = json.Marshal(v[0])
|
||||
} else {
|
||||
val, err = json.Marshal(v)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out = append(out, key...)
|
||||
out = append(out, ':')
|
||||
out = append(out, val...)
|
||||
out = append(out, ',')
|
||||
}
|
||||
|
||||
out[len(out)-1] = '}'
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
24
header_types_test.go
Normal file
24
header_types_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package proton
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHeaders_MarshalInOrder(t *testing.T) {
|
||||
jsonBytes := []byte(`{"zz":"v1","foo":["a","b"],"bar":"30"}`)
|
||||
|
||||
var h Headers
|
||||
|
||||
err := json.Unmarshal(jsonBytes, &h)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedKeyOrder := []string{"zz", "foo", "bar"}
|
||||
|
||||
require.Equal(t, expectedKeyOrder, h.Order)
|
||||
|
||||
serializedJson, err := json.Marshal(h)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, jsonBytes, serializedJson)
|
||||
}
|
||||
@@ -295,8 +295,8 @@ func getTextPartHeader(body []byte, mimeType rfc822.MIMEType) message.Header {
|
||||
func getAttachmentPartHeader(att Attachment) message.Header {
|
||||
var header message.Header
|
||||
|
||||
for key, val := range att.Headers {
|
||||
for _, val := range val {
|
||||
for _, key := range att.Headers.Order {
|
||||
for _, val := range att.Headers.Values[key] {
|
||||
header.Add(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,10 +237,13 @@ func (msg *message) getParsedHeaders() proton.Headers {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
parsed := make(proton.Headers)
|
||||
parsed := proton.Headers{
|
||||
Values: make(map[string][]string),
|
||||
}
|
||||
|
||||
header.Entries(func(key, value string) {
|
||||
parsed[key] = append(parsed[key], value)
|
||||
parsed.Order = append(parsed.Order, key)
|
||||
parsed.Values[key] = append(parsed.Values[key], value)
|
||||
})
|
||||
|
||||
return parsed
|
||||
|
||||
Reference in New Issue
Block a user