Skip to content
Open
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
23 changes: 23 additions & 0 deletions decode_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net"
"net/mail"
"net/netip"
"net/url"
"reflect"
Expand Down Expand Up @@ -712,3 +713,25 @@ func StringToComplex128HookFunc() DecodeHookFunc {
return c128, wrapStrconvNumError(err)
}
}

// StringToMailAddressHookFunc returns a DecodeHookFunc that converts
// strings to mail.Address.
func StringToMailAddressHookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t != reflect.TypeOf(mail.Address{}) {
return data, nil
}

// Convert it by parsing
addr, err := mail.ParseAddress(data.(string))
if err != nil {
return mail.Address{}, fmt.Errorf("failed parsing mail address %v: %w", data, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of 2.4.0 decode hooks must not return errors that have the decoded value in the error message.

Please take a look at other decode hooks (particularly net and time hooks) for examples.

Also, I briefly looked into the mail package and it looks like in some cases their error messages also contain the decoded value. That needs to be dealt with as well.

Make sure to add a test here as well verifying that the decode hook doesn't leak values: https://github.com/go-viper/mapstructure/blob/main/decode_hooks_test.go#L2055

}

if addr == nil {
return mail.Address{}, fmt.Errorf("failed parsing mail address %v", data)
}

return *addr, nil
}
}
20 changes: 20 additions & 0 deletions decode_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math"
"math/big"
"net"
"net/mail"
"net/netip"
"net/url"
"reflect"
Expand Down Expand Up @@ -2123,3 +2124,22 @@ func TestErrorLeakageDecodeHook(t *testing.T) {
}
}
}

func TestStringToMailAddressHookFunc(t *testing.T) {
suite := decodeHookTestSuite[string, mail.Address]{
fn: StringToMailAddressHookFunc(),
ok: []decodeHookTestCase[string, mail.Address]{
{"Barry Gibbs <a-very-loooooooooooooongggggg-addresssssss@example.com>", mail.Address{Name: "Barry Gibbs", Address: "a-very-loooooooooooooongggggg-addresssssss@example.com"}},
{"Barry Gibbs <bg@example.com>", mail.Address{Name: "Barry Gibbs", Address: "bg@example.com"}},
{"bg@example.com", mail.Address{Address: "bg@example.com"}},
},
fail: []decodeHookFailureTestCase[string, mail.Address]{
{"Barry Gibbs"}, // no email
{"Barry Gibbs <>"}, // no email
{"Barry Gibbs <bg>"}, // invalid email
{"Barry Gibbs bg@example.com"}, // missing angle
},
}

suite.Run(t)
}
Loading