Skip to content
Merged
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
2 changes: 0 additions & 2 deletions Lib/test/test_binascii.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ def addnoise(line):
# empty strings. TBD: shouldn't it raise an exception instead ?
self.assertEqual(binascii.a2b_base64(self.type2test(fillers)), b'')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_base64_strict_mode(self):
# Test base64 with strict mode on
def _assertRegexTemplate(assert_regex: str, data: bytes, non_strict_mode_expected_result: bytes):
Expand Down Expand Up @@ -175,7 +174,6 @@ def assertExcessPadding(data, non_strict_mode_expected_result: bytes):
assertExcessPadding(b'abcd====', b'i\xb7\x1d')
assertExcessPadding(b'abcd=====', b'i\xb7\x1d')

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b'i' != b'i\xb7'
def test_base64_excess_data(self):
# Test excess data exceptions
def assertExcessData(data, expected):
Expand Down
63 changes: 40 additions & 23 deletions crates/stdlib/src/binascii.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,31 +291,38 @@ mod decl {
return Ok(vec![]);
}

if strict_mode && b[0] == PAD {
return Err(base64::DecodeError::InvalidByte(0, 61));
}

let mut decoded: Vec<u8> = vec![];

let mut quad_pos = 0; // position in the nibble
let mut pads = 0;
let mut left_char: u8 = 0;
let mut padding_started = false;
for (i, &el) in b.iter().enumerate() {
if el == PAD {
padding_started = true;

pads += 1;
if quad_pos >= 2 && quad_pos + pads >= 4 {
if strict_mode && i + 1 < b.len() {
// Represents excess data after padding error
return Err(base64::DecodeError::InvalidLastSymbol(i, PAD));
}
// A pad that finishes the quad it belongs to is the expected one.
if quad_pos >= 2 && quad_pos + pads <= 4 {
continue;
}

return Ok(decoded);
// RFC 4648 section 3.3 allows a decoder to ignore a pad that
// shows up anywhere else, so only strict mode complains.
if !strict_mode {
continue;
}

continue;
if quad_pos == 1 {
// A single data character cannot be padded into a quad,
// and the check after the loop already reports that.
break;
}

return Err(if quad_pos == 0 && i == 0 {
// Represents leading padding error
base64::DecodeError::InvalidByte(0, PAD)
} else {
// Represents excess padding error
base64::DecodeError::InvalidPadding
});
}

let binary_char = BASE64_TABLE[el as usize];
Expand All @@ -327,9 +334,14 @@ mod decl {
continue;
}

if strict_mode && padding_started {
// Represents discontinuous padding error
return Err(base64::DecodeError::InvalidByte(i, PAD));
if pads > 0 && strict_mode {
return Err(if quad_pos + pads == 4 {
// Represents excess data after padding error
base64::DecodeError::InvalidLastSymbol(i, PAD)
} else {
// Represents discontinuous padding error
base64::DecodeError::InvalidByte(i, PAD)
});
}
pads = 0;

Expand Down Expand Up @@ -361,14 +373,19 @@ mod decl {
}
}

match quad_pos {
0 => Ok(decoded),
1 => Err(base64::DecodeError::InvalidLastSymbol(
if quad_pos == 1 {
// One data character too many: no input encodes to that length.
return Err(base64::DecodeError::InvalidLastSymbol(
decoded.len() / 3 * 4 + 1,
0,
)),
_ => Err(base64::DecodeError::InvalidLength(quad_pos)),
));
}

if quad_pos != 0 && quad_pos + pads < 4 {
return Err(base64::DecodeError::InvalidLength(quad_pos));
}

Ok(decoded)
})
.map_err(|err| super::Base64DecodeError(err).to_pyexception(vm))
}
Expand Down Expand Up @@ -864,7 +881,7 @@ impl ToPyException for Base64DecodeError {
}
// TODO: clean up errors
DecodeError::InvalidLength(_) => "Incorrect padding".to_owned(),
DecodeError::InvalidPadding => "Incorrect padding".to_owned(),
DecodeError::InvalidPadding => "Excess padding not allowed".to_owned(),
};
new_binascii_error(format!("error decoding base64: {message}"), vm)
}
Expand Down
Loading