diff --git a/base64.go b/base64.go index 5bb111c..d2ddf58 100644 --- a/base64.go +++ b/base64.go @@ -54,3 +54,25 @@ func MustDecode(b64 string) B64 { } return b } + +// sB64 is useful for map keys since currently in Go []byte is not allowed to be +// a map key but string is. The type really should be []byte and not string, +// but Go does not yet support this. See https://github.com/golang/go/issues/283 +// and https://github.com/google/go-cmp/issues/67. SB64 will be deprecated +// if/when Go supports []byte keys. +// +// From https://go.dev/blog/strings >[A] string holds arbitrary bytes. It is not +// required to hold Unicode text, UTF-8 text, or any other predefined format. As +// far as the content of a string is concerned, it is exactly equivalent to a +// slice of bytes. +type SB64 string + +// String implements fmt.Stringer +func (b SB64) String() string { + return B64(b).String() +} + +// GoString implements fmt.GoString +func (b SB64) GoString() string { + return b.String() +} diff --git a/base64_test.go b/base64_test.go index 769598b..0c48a1e 100644 --- a/base64_test.go +++ b/base64_test.go @@ -1,8 +1,11 @@ package coze import ( + "bytes" + "crypto/rand" "encoding/json" "fmt" + "testing" ) // B64 of nil is "" while B64 of 0 is "AA". @@ -127,3 +130,50 @@ func ExampleB64_non_strict_decode() { // invalid character '\n' in string literal // invalid character '\r' in string literal } + +// ExampleSB64 demonstrates using SB64 as a map key and that fmt prints "RFC +// 4648 base 64 URI canonical with padding truncated" properly. +func ExampleSB64() { + b := MustDecode("zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0") + b2 := MustDecode("vZIAk8rjcSIKZKokGylCtVoI3DXvFYJn4XNWzf_C_FA") + + lp := make(map[SB64]B64) + lp[SB64(b)] = B64(b2) + + fmt.Printf("%s\n", SB64(b)) + fmt.Printf("%s\n", lp) + fmt.Printf("%+v\n", lp) + fmt.Printf("%#v\n", lp) + + // Output: + // zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0 + // map[zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0:vZIAk8rjcSIKZKokGylCtVoI3DXvFYJn4XNWzf_C_FA] + // map[zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0:vZIAk8rjcSIKZKokGylCtVoI3DXvFYJn4XNWzf_C_FA] + // map[coze.SB64]coze.B64{zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0:vZIAk8rjcSIKZKokGylCtVoI3DXvFYJn4XNWzf_C_FA} +} + +// FuzzCastB64ToString ensures that casting to and from B64 and string does not +// cause unexpected issues (issues like replacing bytes with the unicode +// replacement character). +// https://go.dev/security/fuzz/ +// https://go.dev/doc/tutorial/fuzz +func FuzzCastB64ToString(f *testing.F) { + f.Add(100) + f.Fuzz(func(t *testing.T, a int) { + var b B64 + var err error + for i := 0; i < a; i++ { + b = make([]byte, 32) + _, err = rand.Read(b) + if err != nil { + t.Fatal(err) + } + + s := string(b) + bb := B64(s) + if !bytes.Equal(b, bb) { + t.Fatalf("Casting to string: %s failed when converting back B64.", s) + } + } + }) +}