Skip to content

Commit

Permalink
Merge pull request #463 from reef-technologies/fix_invalid_error_msg_…
Browse files Browse the repository at this point in the history
…handling

handle invalid B2 server errors
  • Loading branch information
mjurbanski-reef committed Jan 25, 2024
2 parents f2ee664 + b70395e commit b03b330
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 4 deletions.
22 changes: 18 additions & 4 deletions b2sdk/b2http.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,9 @@ def _translate_errors(cls, fcn, post_params=None):
# Decode the error object returned by the service
try:
error = json.loads(response.content.decode('utf-8')) if response.content else {}
except (json.JSONDecodeError, UnicodeDecodeError):
if not isinstance(error, dict):
raise ValueError('json error value is not a dict')
except (json.JSONDecodeError, UnicodeDecodeError, ValueError):
logger.error('failed to decode error response: %r', response.content)
# When the user points to an S3 endpoint, he won't receive the JSON error
# he expects. In that case, we can provide at least a hint of "what happened".
Expand All @@ -439,10 +441,22 @@ def _translate_errors(cls, fcn, post_params=None):
logger.debug(
'received error has extra (unsupported) keys: %s', extra_error_keys
)

try:
status = int(error.get('status', response.status_code))
if status != response.status_code:
raise ValueError('status code is not equal to the one in the response')
except (TypeError, ValueError) as exc:
logger.warning(
'Inconsistent status codes returned by the server %r != %r; parsing exception: %r',
error.get('status'), response.status_code, exc
)
status = response.status_code

raise interpret_b2_error(
int(error.get('status', response.status_code)),
error.get('code'),
error.get('message'),
status,
str(error['code']) if 'code' in error else None,
str(error['message']) if 'message' in error else None,
response.headers,
post_params,
)
Expand Down
1 change: 1 addition & 0 deletions changelog.d/+handle_invalid_error_format.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Handle json encoded, invalid B2 error responses, preventing exceptions such as `invalid literal for int() with base 10: 'service_unavailable'`.
30 changes: 30 additions & 0 deletions test/unit/b2http/test_b2http.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,36 @@ def test_b2_error__nginx_html():
assert response.content.decode('utf-8') in str(exc_info.value)


def test_b2_error__invalid_error_format():
"""
Handling of invalid error format.
If server returns valid JSON, but not matching B2 error schema, we should still raise ServiceError.
"""
response = MagicMock()
response.status_code = 503
# valid JSON, but not a valid B2 error (it should be a dict, not a list)
response.content = b'[]'
with pytest.raises(ServiceError) as exc_info:
B2Http._translate_errors(lambda: response)
assert '503' in str(exc_info.value)


def test_b2_error__invalid_error_values():
"""
Handling of invalid error values.
If server returns valid JSON, but not matching B2 error schema, we should still raise ServiceError.
"""
response = MagicMock()
response.status_code = 503
# valid JSON, but not a valid B2 error (code and status values (and therefore types!) are swapped)
response.content = b'{"code": 503, "message": "Service temporarily unavailable", "status": "service_unavailable"}'
with pytest.raises(ServiceError) as exc_info:
B2Http._translate_errors(lambda: response)
assert '503 Service temporarily unavailable' in str(exc_info.value)


class TestTranslateAndRetry(TestBase):
def setUp(self):
self.response = MagicMock()
Expand Down

0 comments on commit b03b330

Please sign in to comment.