base64 0.9.3

encodes and decodes base64 as bytes or utf8
Documentation
extern crate base64;

use base64::*;

mod helpers;

use helpers::*;

fn compare_decode_mime(expected: &str, target: &str) {
    assert_eq!(
        expected,
        String::from_utf8(decode_config(target, MIME).unwrap()).unwrap()
    );
}

#[test]
fn decode_rfc4648_0() {
    compare_decode("", "");
}

#[test]
fn decode_rfc4648_1() {
    compare_decode("f", "Zg==");
}

#[test]
fn decode_rfc4648_1_just_a_bit_of_padding() {
    // allows less padding than required
    compare_decode("f", "Zg=");
}

#[test]
fn decode_rfc4648_1_no_padding() {
    compare_decode("f", "Zg");
}

#[test]
fn decode_rfc4648_2() {
    compare_decode("fo", "Zm8=");
}

#[test]
fn decode_rfc4648_2_no_padding() {
    compare_decode("fo", "Zm8");
}

#[test]
fn decode_rfc4648_3() {
    compare_decode("foo", "Zm9v");
}

#[test]
fn decode_rfc4648_4() {
    compare_decode("foob", "Zm9vYg==");
}

#[test]
fn decode_rfc4648_4_no_padding() {
    compare_decode("foob", "Zm9vYg");
}

#[test]
fn decode_rfc4648_5() {
    compare_decode("fooba", "Zm9vYmE=");
}

#[test]
fn decode_rfc4648_5_no_padding() {
    compare_decode("fooba", "Zm9vYmE");
}

#[test]
fn decode_rfc4648_6() {
    compare_decode("foobar", "Zm9vYmFy");
}

#[test]
fn decode_mime_allow_space() {
    assert!(decode_config("YWx pY2U=", MIME).is_ok());
}

#[test]
fn decode_mime_allow_tab() {
    assert!(decode_config("YWx\tpY2U=", MIME).is_ok());
}

#[test]
fn decode_mime_allow_ff() {
    assert!(decode_config("YWx\x0cpY2U=", MIME).is_ok());
}

#[test]
fn decode_mime_allow_vtab() {
    assert!(decode_config("YWx\x0bpY2U=", MIME).is_ok());
}

#[test]
fn decode_mime_allow_nl() {
    assert!(decode_config("YWx\npY2U=", MIME).is_ok());
}

#[test]
fn decode_mime_allow_crnl() {
    assert!(decode_config("YWx\r\npY2U=", MIME).is_ok());
}

#[test]
fn decode_mime_reject_null() {
    assert_eq!(
        DecodeError::InvalidByte(3, 0x0),
        decode_config("YWx\0pY2U==", MIME).unwrap_err()
    );
}

#[test]
fn decode_mime_absurd_whitespace() {
    compare_decode_mime(
        "how could you let this happen",
        "\n aG93I\n\nG\x0bNvd\r\nWxkI HlvdSB \tsZXQgdGh\rpcyBo\x0cYXBwZW4 =   ",
    );
}

#[test]
fn decode_single_pad_byte_after_2_chars_in_trailing_quad_ok() {
    for num_quads in 0..25 {
        let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
        s.push_str("Zg=");

        let input_len = num_quads * 3 + 1;

        // Since there are 3 bytes in the trailing quad, want to be sure this allows for the fact
        // that it could be bad padding rather than assuming that it will decode to 2 bytes and
        // therefore allow 1 extra round of fast decode logic (stage 1 / 2).

        let mut decoded = Vec::new();
        decoded.resize(input_len, 0);

        assert_eq!(
            input_len,
            decode_config_slice(&s, STANDARD, &mut decoded).unwrap()
        );
    }
}

//this is a MAY in the rfc: https://tools.ietf.org/html/rfc4648#section-3.3
#[test]
fn decode_1_pad_byte_in_fast_loop_then_extra_padding_chunk_error() {
    for num_quads in 0..25 {
        let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
        s.push_str("YWxpY2U=====");

        // since the first 8 bytes are handled in stage 1 or 2, the padding is detected as a
        // generic invalid byte, not specifcally a padding issue.
        // Could argue that the *next* padding byte (in the next quad) is technically the first
        // erroneous one, but reporting that accurately is more complex and probably nobody cares
        assert_eq!(
            DecodeError::InvalidByte(num_quads * 4 + 7, b'='),
            decode(&s).unwrap_err()
        );
    }
}

#[test]
fn decode_2_pad_bytes_in_leftovers_then_extra_padding_chunk_error() {
    for num_quads in 0..25 {
        let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
        s.push_str("YWxpY2UABB====");

        // 6 bytes (4 padding) after last 8-byte chunk, so it's decoded by stage 4.
        // First padding byte is invalid.
        assert_eq!(
            DecodeError::InvalidByte(num_quads * 4 + 10, b'='),
            decode(&s).unwrap_err()
        );
    }
}

#[test]
fn decode_valid_bytes_after_padding_in_leftovers_error() {
    for num_quads in 0..25 {
        let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
        s.push_str("YWxpY2UABB=B");

        // 4 bytes after last 8-byte chunk, so it's decoded by stage 4.
        // First (and only) padding byte is invalid.
        assert_eq!(
            DecodeError::InvalidByte(num_quads * 4 + 10, b'='),
            decode(&s).unwrap_err()
        );
    }
}

#[test]
fn decode_absurd_pad_error() {
    for num_quads in 0..25 {
        let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
        s.push_str("==Y=Wx===pY=2U=====");

        // Plenty of remaining bytes, so handled by stage 1 or 2.
        // first padding byte
        assert_eq!(
            DecodeError::InvalidByte(num_quads * 4, b'='),
            decode(&s).unwrap_err()
        );
    }
}

#[test]
fn decode_extra_padding_after_1_pad_bytes_in_trailing_quad_returns_error() {
    for num_quads in 0..25 {
        let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
        s.push_str("EEE===");

        // handled by stage 1, 2, or 4 depending on length
        // first padding byte -- which would be legal if it was the only padding
        assert_eq!(
            DecodeError::InvalidByte(num_quads * 4 + 3, b'='),
            decode(&s).unwrap_err()
        );
    }
}

#[test]
fn decode_extra_padding_after_2_pad_bytes_in_trailing_quad_2_returns_error() {
    for num_quads in 0..25 {
        let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
        s.push_str("EE====");

        // handled by stage 1, 2, or 4 depending on length
        // first padding byte -- which would be legal if it was by itself
        assert_eq!(
            DecodeError::InvalidByte(num_quads * 4 + 2, b'='),
            decode(&s).unwrap_err()
        );
    }
}

#[test]
fn decode_start_quad_with_padding_returns_error() {
    for num_quads in 0..25 {
        // add enough padding to ensure that we'll hit all 4 stages at the different lengths
        for pad_bytes in 1..32 {
            let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
            let padding: String = std::iter::repeat("=").take(pad_bytes).collect();
            s.push_str(&padding);

            if pad_bytes % 4 == 1 {
                // detected in early length check
                assert_eq!(DecodeError::InvalidLength, decode(&s).unwrap_err());
            } else {
                // padding lengths 2 - 8 are handled by stage 4
                // padding length >= 8 will hit at least one chunk at stages 1, 2, 3 at different
                // prefix lengths
                assert_eq!(
                    DecodeError::InvalidByte(num_quads * 4, b'='),
                    decode(&s).unwrap_err()
                );
            }
        }
    }
}

#[test]
fn decode_padding_followed_by_non_padding_returns_error() {
    for num_quads in 0..25 {
        for pad_bytes in 0..31 {
            let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
            let padding: String = std::iter::repeat("=").take(pad_bytes).collect();
            s.push_str(&padding);
            s.push_str("E");

            if pad_bytes % 4 == 0 {
                assert_eq!(DecodeError::InvalidLength, decode(&s).unwrap_err());
            } else {
                // pad len 1 - 8 will be handled by stage 4
                // pad len 9 (suffix len 10) will have 8 bytes of padding handled by stage 3
                // first padding byte
                assert_eq!(
                    DecodeError::InvalidByte(num_quads * 4, b'='),
                    decode(&s).unwrap_err()
                );
            }
        }
    }
}

#[test]
fn decode_one_char_in_quad_with_padding_error() {
    for num_quads in 0..25 {
        let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
        s.push_str("E=");

        assert_eq!(
            DecodeError::InvalidByte(num_quads * 4 + 1, b'='),
            decode(&s).unwrap_err()
        );

        // more padding doesn't change the error
        s.push_str("=");
        assert_eq!(
            DecodeError::InvalidByte(num_quads * 4 + 1, b'='),
            decode(&s).unwrap_err()
        );

        s.push_str("=");
        assert_eq!(
            DecodeError::InvalidByte(num_quads * 4 + 1, b'='),
            decode(&s).unwrap_err()
        );
    }
}

#[test]
fn decode_one_char_in_quad_without_padding_error() {
    for num_quads in 0..25 {
        let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
        s.push('E');

        assert_eq!(DecodeError::InvalidLength, decode(&s).unwrap_err());
    }
}

#[test]
fn decode_reject_invalid_bytes_with_correct_error() {
    for length in 1..100 {
        for index in 0_usize..length {
            for invalid_byte in " \t\n\r\x0C\x0B\x00%*.".bytes() {
                let prefix: String = std::iter::repeat("A").take(index).collect();
                let suffix: String = std::iter::repeat("B").take(length - index - 1).collect();

                let input = prefix + &String::from_utf8(vec![invalid_byte]).unwrap() + &suffix;
                assert_eq!(
                    length,
                    input.len(),
                    "length {} error position {}",
                    length,
                    index
                );

                if length % 4 == 1 {
                    assert_eq!(DecodeError::InvalidLength, decode(&input).unwrap_err());
                } else {
                    assert_eq!(
                        DecodeError::InvalidByte(index, invalid_byte),
                        decode(&input).unwrap_err()
                    );
                }
            }
        }
    }
}