diff --git a/.changes/next-release/s3-bugfix-10161.json b/.changes/next-release/s3-bugfix-10161.json new file mode 100644 index 000000000000..49a8d06ae3f7 --- /dev/null +++ b/.changes/next-release/s3-bugfix-10161.json @@ -0,0 +1,7 @@ +[ + { + "category": "``s3``", + "description": "Fix ``TypeError`` in S3 error message handler when the error response contains a ``None`` ``Message`` value, which can occur with S3-compatible endpoints that return empty ```` XML elements.", + "type": "bugfix" + } +] diff --git a/awscli/customizations/s3errormsg.py b/awscli/customizations/s3errormsg.py index a7a0b9eb4f32..cfd94155683f 100644 --- a/awscli/customizations/s3errormsg.py +++ b/awscli/customizations/s3errormsg.py @@ -44,24 +44,26 @@ def enhance_error_msg(parsed, **kwargs): message += REGION_ERROR_MSG parsed['Error']['Message'] = message elif _is_permanent_redirect_message(parsed): - endpoint = parsed['Error']['Endpoint'] - message = parsed['Error']['Message'] + endpoint = parsed['Error'].get('Endpoint', '') + message = parsed['Error'].get('Message') or '' new_message = message[:-1] + ': %s\n' % endpoint new_message += REGION_ERROR_MSG parsed['Error']['Message'] = new_message elif _is_kms_sigv4_error_message(parsed): - parsed['Error']['Message'] += ENABLE_SIGV4_MSG + current_msg = parsed['Error'].get('Message') or '' + parsed['Error']['Message'] = current_msg + ENABLE_SIGV4_MSG def _is_sigv4_error_message(parsed): - return ('Please use AWS4-HMAC-SHA256' in - parsed.get('Error', {}).get('Message', '')) + error_message = parsed.get('Error', {}).get('Message') or '' + return 'Please use AWS4-HMAC-SHA256' in error_message def _is_permanent_redirect_message(parsed): - return parsed.get('Error', {}).get('Code', '') == 'PermanentRedirect' + error_code = parsed.get('Error', {}).get('Code') or '' + return error_code == 'PermanentRedirect' def _is_kms_sigv4_error_message(parsed): - return ('AWS KMS managed keys require AWS Signature Version 4' in - parsed.get('Error', {}).get('Message', '')) + error_message = parsed.get('Error', {}).get('Message') or '' + return 'AWS KMS managed keys require AWS Signature Version 4' in error_message diff --git a/tests/unit/customizations/test_s3errormsg.py b/tests/unit/customizations/test_s3errormsg.py index 0af91ac9a9a0..8a24c4d696b6 100644 --- a/tests/unit/customizations/test_s3errormsg.py +++ b/tests/unit/customizations/test_s3errormsg.py @@ -84,3 +84,77 @@ def test_not_an_error_message(self): s3errormsg.enhance_error_msg(parsed) # Nothing should have changed self.assertEqual(parsed, expected) + + def test_none_message_does_not_crash(self): + # Empty parsed as None must not raise TypeError. + parsed = { + 'Error': { + 'Code': 'AccessDenied', + 'Message': None, + } + } + # Should not raise TypeError + s3errormsg.enhance_error_msg(parsed) + # Message should remain None since no error pattern matched + self.assertIsNone(parsed['Error']['Message']) + + def test_none_message_with_sigv4_code(self): + # None Message should not match sigv4 pattern. + parsed = { + 'Error': { + 'Code': 'AuthorizationHeaderMalformed', + 'Message': None, + } + } + s3errormsg.enhance_error_msg(parsed) + # Should not have been enhanced (no match) + self.assertIsNone(parsed['Error']['Message']) + + def test_none_message_with_kms_context(self): + # None Message should not match KMS sigv4 pattern. + parsed = { + 'Error': { + 'Code': 'InvalidArgument', + 'Message': None, + } + } + s3errormsg.enhance_error_msg(parsed) + self.assertIsNone(parsed['Error']['Message']) + + def test_none_code_does_not_crash(self): + # None Code should not crash PermanentRedirect check. + parsed = { + 'Error': { + 'Code': None, + 'Message': 'Some error message.', + } + } + expected = copy.deepcopy(parsed) + s3errormsg.enhance_error_msg(parsed) + # Nothing should have changed + self.assertEqual(parsed, expected) + + def test_empty_string_message_does_not_crash(self): + # Empty string Message should be handled without crashing. + parsed = { + 'Error': { + 'Code': 'AccessDenied', + 'Message': '', + } + } + expected = copy.deepcopy(parsed) + s3errormsg.enhance_error_msg(parsed) + self.assertEqual(parsed, expected) + + def test_permanent_redirect_with_none_message(self): + # PermanentRedirect with None Message should not crash. + parsed = { + 'Error': { + 'Code': 'PermanentRedirect', + 'Message': None, + 'Endpoint': 'myendpoint', + } + } + s3errormsg.enhance_error_msg(parsed) + # Should have enhanced the message despite None original + self.assertIn('myendpoint', parsed['Error']['Message'])