Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changes/next-release/s3-bugfix-10161.json
Original file line number Diff line number Diff line change
@@ -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 ``<Message>`` XML elements.",
"type": "bugfix"
}
]
18 changes: 10 additions & 8 deletions awscli/customizations/s3errormsg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
74 changes: 74 additions & 0 deletions tests/unit/customizations/test_s3errormsg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Message></Message> 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'])