// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package base64 import ( "bytes" "errors" "io" "io/ioutil" "reflect" "strings" "testing" "time" ) type testpair struct { decoded, encoded string } var pairs = []testpair{ // RFC 3548 examples {"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+"}, {"\x14\xfb\x9c\x03\xd9", "FPucA9k="}, {"\x14\xfb\x9c\x03", "FPucAw=="}, // RFC 4648 examples {"", ""}, {"f", "Zg=="}, {"fo", "Zm8="}, {"foo", "Zm9v"}, {"foob", "Zm9vYg=="}, {"fooba", "Zm9vYmE="}, {"foobar", "Zm9vYmFy"}, // Wikipedia examples {"sure.", "c3VyZS4="}, {"sure", "c3VyZQ=="}, {"sur", "c3Vy"}, {"su", "c3U="}, {"leasure.", "bGVhc3VyZS4="}, {"easure.", "ZWFzdXJlLg=="}, {"asure.", "YXN1cmUu"}, {"sure.", "c3VyZS4="}, } // Do nothing to a reference base64 string (leave in standard format) func stdRef(ref string) string { return ref } // Convert a reference string to URL-encoding func urlRef(ref string) string { ref = strings.Replace(ref, "+", "-", -1) ref = strings.Replace(ref, "/", "_", -1) return ref } // Convert a reference string to raw, unpadded format func rawRef(ref string) string { return strings.TrimRight(ref, "=") } // Both URL and unpadding conversions func rawUrlRef(ref string) string { return rawRef(urlRef(ref)) } // A nonstandard encoding with a funny padding character, for testing var funnyEncoding = NewEncoding(encodeStd).WithPadding(rune('@')) func funnyRef(ref string) string { return strings.Replace(ref, "=", "@", -1) } type encodingTest struct { enc *Encoding // Encoding to test conv func(string) string // Reference string converter } var encodingTests = []encodingTest{ encodingTest{StdEncoding, stdRef}, encodingTest{URLEncoding, urlRef}, encodingTest{RawStdEncoding, rawRef}, encodingTest{RawURLEncoding, rawUrlRef}, encodingTest{funnyEncoding, funnyRef}, } var bigtest = testpair{ "Twas brillig, and the slithy toves", "VHdhcyBicmlsbGlnLCBhbmQgdGhlIHNsaXRoeSB0b3Zlcw==", } func testEqual(t *testing.T, msg string, args ...interface{}) bool { if args[len(args)-2] != args[len(args)-1] { t.Errorf(msg, args...) return false } return true } func TestEncode(t *testing.T) { for _, p := range pairs { for _, tt := range encodingTests { got := tt.enc.EncodeToString([]byte(p.decoded)) testEqual(t, "Encode(%q) = %q, want %q", p.decoded, got, tt.conv(p.encoded)) } } } func TestEncoder(t *testing.T) { for _, p := range pairs { bb := &bytes.Buffer{} encoder := NewEncoder(StdEncoding, bb) encoder.Write([]byte(p.decoded)) encoder.Close() testEqual(t, "Encode(%q) = %q, want %q", p.decoded, bb.String(), p.encoded) } } func TestEncoderBuffering(t *testing.T) { input := []byte(bigtest.decoded) for bs := 1; bs <= 12; bs++ { bb := &bytes.Buffer{} encoder := NewEncoder(StdEncoding, bb) for pos := 0; pos < len(input); pos += bs { end := pos + bs if end > len(input) { end = len(input) } n, err := encoder.Write(input[pos:end]) testEqual(t, "Write(%q) gave error %v, want %v", input[pos:end], err, error(nil)) testEqual(t, "Write(%q) gave length %v, want %v", input[pos:end], n, end-pos) } err := encoder.Close() testEqual(t, "Close gave error %v, want %v", err, error(nil)) testEqual(t, "Encoding/%d of %q = %q, want %q", bs, bigtest.decoded, bb.String(), bigtest.encoded) } } func TestDecode(t *testing.T) { for _, p := range pairs { for _, tt := range encodingTests { encoded := tt.conv(p.encoded) dbuf := make([]byte, tt.enc.DecodedLen(len(encoded))) count, end, err := tt.enc.decode(dbuf, []byte(encoded)) testEqual(t, "Decode(%q) = error %v, want %v", encoded, err, error(nil)) testEqual(t, "Decode(%q) = length %v, want %v", encoded, count, len(p.decoded)) if len(encoded) > 0 { testEqual(t, "Decode(%q) = end %v, want %v", encoded, end, len(p.decoded)%3 != 0) } testEqual(t, "Decode(%q) = %q, want %q", encoded, string(dbuf[0:count]), p.decoded) dbuf, err = tt.enc.DecodeString(encoded) testEqual(t, "DecodeString(%q) = error %v, want %v", encoded, err, error(nil)) testEqual(t, "DecodeString(%q) = %q, want %q", string(dbuf), p.decoded) } } } func TestDecoder(t *testing.T) { for _, p := range pairs { decoder := NewDecoder(StdEncoding, strings.NewReader(p.encoded)) dbuf := make([]byte, StdEncoding.DecodedLen(len(p.encoded))) count, err := decoder.Read(dbuf) if err != nil && err != io.EOF { t.Fatal("Read failed", err) } testEqual(t, "Read from %q = length %v, want %v", p.encoded, count, len(p.decoded)) testEqual(t, "Decoding of %q = %q, want %q", p.encoded, string(dbuf[0:count]), p.decoded) if err != io.EOF { count, err = decoder.Read(dbuf) } testEqual(t, "Read from %q = %v, want %v", p.encoded, err, io.EOF) } } func TestDecoderBuffering(t *testing.T) { for bs := 1; bs <= 12; bs++ { decoder := NewDecoder(StdEncoding, strings.NewReader(bigtest.encoded)) buf := make([]byte, len(bigtest.decoded)+12) var total int for total = 0; total < len(bigtest.decoded); { n, err := decoder.Read(buf[total : total+bs]) testEqual(t, "Read from %q at pos %d = %d, %v, want _, %v", bigtest.encoded, total, n, err, error(nil)) total += n } testEqual(t, "Decoding/%d of %q = %q, want %q", bs, bigtest.encoded, string(buf[0:total]), bigtest.decoded) } } func TestDecodeCorrupt(t *testing.T) { testCases := []struct { input string offset int // -1 means no corruption. }{ {"", -1}, {"!!!!", 0}, {"====", 0}, {"x===", 1}, {"=AAA", 0}, {"A=AA", 1}, {"AA=A", 2}, {"AA==A", 4}, {"AAA=AAAA", 4}, {"AAAAA", 4}, {"AAAAAA", 4}, {"A=", 1}, {"A==", 1}, {"AA=", 3}, {"AA==", -1}, {"AAA=", -1}, {"AAAA", -1}, {"AAAAAA=", 7}, {"YWJjZA=====", 8}, } for _, tc := range testCases { dbuf := make([]byte, StdEncoding.DecodedLen(len(tc.input))) _, err := StdEncoding.Decode(dbuf, []byte(tc.input)) if tc.offset == -1 { if err != nil { t.Error("Decoder wrongly detected coruption in", tc.input) } continue } switch err := err.(type) { case CorruptInputError: testEqual(t, "Corruption in %q at offset %v, want %v", tc.input, int(err), tc.offset) default: t.Error("Decoder failed to detect corruption in", tc) } } } func TestBig(t *testing.T) { n := 3*1000 + 1 raw := make([]byte, n) const alpha = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" for i := 0; i < n; i++ { raw[i] = alpha[i%len(alpha)] } encoded := new(bytes.Buffer) w := NewEncoder(StdEncoding, encoded) nn, err := w.Write(raw) if nn != n || err != nil { t.Fatalf("Encoder.Write(raw) = %d, %v want %d, nil", nn, err, n) } err = w.Close() if err != nil { t.Fatalf("Encoder.Close() = %v want nil", err) } decoded, err := ioutil.ReadAll(NewDecoder(StdEncoding, encoded)) if err != nil { t.Fatalf("ioutil.ReadAll(NewDecoder(...)): %v", err) } if !bytes.Equal(raw, decoded) { var i int for i = 0; i < len(decoded) && i < len(raw); i++ { if decoded[i] != raw[i] { break } } t.Errorf("Decode(Encode(%d-byte string)) failed at offset %d", n, i) } } func TestNewLineCharacters(t *testing.T) { // Each of these should decode to the string "sure", without errors. const expected = "sure" examples := []string{ "c3VyZQ==", "c3VyZQ==\r", "c3VyZQ==\n", "c3VyZQ==\r\n", "c3VyZ\r\nQ==", "c3V\ryZ\nQ==", "c3V\nyZ\rQ==", "c3VyZ\nQ==", "c3VyZQ\n==", "c3VyZQ=\n=", "c3VyZQ=\r\n\r\n=", } for _, e := range examples { buf, err := StdEncoding.DecodeString(e) if err != nil { t.Errorf("Decode(%q) failed: %v", e, err) continue } if s := string(buf); s != expected { t.Errorf("Decode(%q) = %q, want %q", e, s, expected) } } } type nextRead struct { n int // bytes to return err error // error to return } // faultInjectReader returns data from source, rate-limited // and with the errors as written to nextc. type faultInjectReader struct { source string nextc <-chan nextRead } func (r *faultInjectReader) Read(p []byte) (int, error) { nr := <-r.nextc if len(p) > nr.n { p = p[:nr.n] } n := copy(p, r.source) r.source = r.source[n:] return n, nr.err } // tests that we don't ignore errors from our underlying reader func TestDecoderIssue3577(t *testing.T) { next := make(chan nextRead, 10) wantErr := errors.New("my error") next <- nextRead{5, nil} next <- nextRead{10, wantErr} next <- nextRead{0, wantErr} d := NewDecoder(StdEncoding, &faultInjectReader{ source: "VHdhcyBicmlsbGlnLCBhbmQgdGhlIHNsaXRoeSB0b3Zlcw==", // twas brillig... nextc: next, }) errc := make(chan error) go func() { _, err := ioutil.ReadAll(d) errc <- err }() select { case err := <-errc: if err != wantErr { t.Errorf("got error %v; want %v", err, wantErr) } case <-time.After(5 * time.Second): t.Errorf("timeout; Decoder blocked without returning an error") } } func TestDecoderIssue4779(t *testing.T) { encoded := `CP/EAT8AAAEF AQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAAB BAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHx Y3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm 9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS 0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0 pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8A9VSSSSUpJJJJSkkkJ+Tj 1kiy1jCJJDnAcCTykpKkuQ6p/jN6FgmxlNduXawwAzaGH+V6jn/R/wCt71zdn+N/qL3kVYFNYB4N ji6PDVjWpKp9TSXnvTf8bFNjg3qOEa2n6VlLpj/rT/pf567DpX1i6L1hs9Py67X8mqdtg/rUWbbf +gkp0kkkklKSSSSUpJJJJT//0PVUkkklKVLq3WMDpGI7KzrNjADtYNXvI/Mqr/Pd/q9W3vaxjnvM NaCXE9gNSvGPrf8AWS3qmba5jjsJhoB0DAf0NDf6sevf+/lf8Hj0JJATfWT6/dV6oXU1uOLQeKKn EQP+Hubtfe/+R7Mf/g7f5xcocp++Z11JMCJPgFBxOg7/AOuqDx8I/ikpkXkmSdU8mJIJA/O8EMAy j+mSARB/17pKVXYWHXjsj7yIex0PadzXMO1zT5KHoNA3HT8ietoGhgjsfA+CSnvvqh/jJtqsrwOv 2b6NGNzXfTYexzJ+nU7/ALkf4P8Awv6P9KvTQQ4AgyDqCF85Pho3CTB7eHwXoH+LT65uZbX9X+o2 bqbPb06551Y4 ` encodedShort := strings.Replace(encoded, "\n", "", -1) dec := NewDecoder(StdEncoding, strings.NewReader(encoded)) res1, err := ioutil.ReadAll(dec) if err != nil { t.Errorf("ReadAll failed: %v", err) } dec = NewDecoder(StdEncoding, strings.NewReader(encodedShort)) var res2 []byte res2, err = ioutil.ReadAll(dec) if err != nil { t.Errorf("ReadAll failed: %v", err) } if !bytes.Equal(res1, res2) { t.Error("Decoded results not equal") } } func TestDecoderIssue7733(t *testing.T) { s, err := StdEncoding.DecodeString("YWJjZA=====") want := CorruptInputError(8) if !reflect.DeepEqual(want, err) { t.Errorf("Error = %v; want CorruptInputError(8)", err) } if string(s) != "abcd" { t.Errorf("DecodeString = %q; want abcd", s) } } func BenchmarkEncodeToString(b *testing.B) { data := make([]byte, 8192) b.SetBytes(int64(len(data))) for i := 0; i < b.N; i++ { StdEncoding.EncodeToString(data) } } func BenchmarkDecodeString(b *testing.B) { data := StdEncoding.EncodeToString(make([]byte, 8192)) b.SetBytes(int64(len(data))) for i := 0; i < b.N; i++ { StdEncoding.DecodeString(data) } }