1package kittygraphics
2
3import (
4 "bytes"
5 "encoding/base64"
6 "fmt"
7 "math/rand"
8 "testing"
9)
10
11// legacyDecodeBase64 is the original two-pass implementation, kept as the
12// accept/reject and error-text oracle for DecodeBase64.
13func legacyDecodeBase64(encoded []byte) ([]byte, error) {
14 decoded, err := base64.StdEncoding.DecodeString(string(encoded))
15 if err == nil {
16 return decoded, nil
17 }
18 decoded, rawErr := base64.RawStdEncoding.DecodeString(string(encoded))
19 if rawErr == nil {
20 return decoded, nil
21 }
22 return nil, fmt.Errorf("%w: %v", ErrInvalidBase64, err)
23}
24
25func checkBase64Equivalent(t testing.TB, input []byte) {
26 t.Helper()
27 want, wantErr := legacyDecodeBase64(input)
28 got, gotErr := DecodeBase64(input)
29 if (wantErr == nil) != (gotErr == nil) {
30 t.Fatalf("DecodeBase64(%q) error = %v, legacy error = %v", input, gotErr, wantErr)
31 }
32 if wantErr != nil {
33 if gotErr.Error() != wantErr.Error() {
34 t.Fatalf("DecodeBase64(%q) error = %q, legacy %q", input, gotErr, wantErr)
35 }
36 if !errorsIs(gotErr, ErrInvalidBase64) {
37 t.Fatalf("DecodeBase64(%q) error %v does not wrap ErrInvalidBase64", input, gotErr)
38 }
39 return
40 }
41 if !bytes.Equal(got, want) || (got == nil) != (want == nil) {
42 t.Fatalf("DecodeBase64(%q) = %x, legacy %x", input, got, want)
43 }
44}
45
46func TestDecodeBase64MatchesLegacyRandom(t *testing.T) {
47 rng := rand.New(rand.NewSource(1))
48 alphabets := []string{
49 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
50 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\r\n",
51 "AAAA==\r\n=-_ \t\x00\xff",
52 }
53 for i := 0; i < 200000; i++ {
54 alphabet := alphabets[rng.Intn(len(alphabets))]
55 input := make([]byte, rng.Intn(24))
56 for j := range input {
57 input[j] = alphabet[rng.Intn(len(alphabet))]
58 }
59 checkBase64Equivalent(t, input)
60 }
61 // Valid encodings, padded and raw, with and without line breaks.
62 for n := 0; n < 40; n++ {
63 raw := make([]byte, n)
64 rng.Read(raw)
65 padded := base64.StdEncoding.EncodeToString(raw)
66 unpadded := base64.RawStdEncoding.EncodeToString(raw)
67 checkBase64Equivalent(t, []byte(padded))
68 checkBase64Equivalent(t, []byte(unpadded))
69 checkBase64Equivalent(t, []byte(unpadded+"\r\n"))
70 if len(padded) > 2 {
71 checkBase64Equivalent(t, []byte(padded[:2]+"\n"+padded[2:]))
72 checkBase64Equivalent(t, []byte(padded[:len(padded)-1]))
73 }
74 }
75 checkBase64Equivalent(t, nil)
76 checkBase64Equivalent(t, []byte{})
77}
78
79func FuzzDecodeBase64MatchesLegacy(f *testing.F) {
80 for _, seed := range []string{"", "AAAA", "AAA=", "AAA", "AA==", "AA", "A", "AA=", "=", "AAAA\n", "AA\r\nAA", "-_-_"} {
81 f.Add([]byte(seed))
82 }
83 f.Fuzz(func(t *testing.T, input []byte) { checkBase64Equivalent(t, input) })
84}
85
86func TestParseAPCBorrowedAliasesAndParseAPCCopies(t *testing.T) {
87 apc := []byte("\x1b_Ga=t,i=1;QUJD\x1b\\")
88 copied, err := ParseAPC(apc)
89 if err != nil {
90 t.Fatal(err)
91 }
92 borrowed, err := ParseAPCBorrowed(apc)
93 if err != nil {
94 t.Fatal(err)
95 }
96 apc[len("\x1b_Ga=t,i=1;")] = 'Z'
97 if string(copied.Payload) != "QUJD" {
98 t.Fatalf("ParseAPC payload aliased input: %q", copied.Payload)
99 }
100 if string(borrowed.Payload) != "ZUJD" {
101 t.Fatalf("ParseAPCBorrowed payload = %q, want aliased", borrowed.Payload)
102 }
103}