mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Follow CPython's padding rules in a2b_base64 (#8570)
With strict_mode=True the decoder accepted padding CPython rejects:
>>> binascii.a2b_base64(b'YWJj=', strict_mode=True)
b'abc'
It also dropped data in the default mode, because it returned at the
first pad that completed a quad:
>>> binascii.a2b_base64(b'abc=a')
b'i\xb7' # CPython: b'i\xb7\x1a'
The loop now counts pads and decides at the end, the way CPython does,
so a stray pad is ignored outside strict mode and named as leading,
excess or discontinuous inside it. DecodeError::InvalidPadding was dead
in this file and now carries the excess padding message.
test_base64_strict_mode and test_base64_excess_data were marked as
expected failures and pass now.
Assisted-by: Claude Code:claude-opus-5
This commit is contained in:
1 parent
f9e65c0995
commit
377c9dbc78
2 files changed
+41
-26
No files matched your search
Vendored
-2
@@ -117,7 +117,6 @@ class BinASCIITest(unittest.TestCase):
|
||||
# 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):
|
||||
@@ -175,7 +174,6 @@ class BinASCIITest(unittest.TestCase):
|
||||
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):
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
return Ok(decoded);
|
||||
// A pad that finishes the quad it belongs to is the expected one.
|
||||
if quad_pos >= 2 && quad_pos + pads <= 4 {
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
// 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;
|
||||
}
|
||||
|
||||
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];
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user