Skip to content

Commit b70395e

Browse files
handle invalid B2 server errors
1 parent a6a7f21 commit b70395e

File tree

3 files changed

+49
-4
lines changed

3 files changed

+49
-4
lines changed

b2sdk/b2http.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,9 @@ def _translate_errors(cls, fcn, post_params=None):
423423
# Decode the error object returned by the service
424424
try:
425425
error = json.loads(response.content.decode('utf-8')) if response.content else {}
426-
except (json.JSONDecodeError, UnicodeDecodeError):
426+
if not isinstance(error, dict):
427+
raise ValueError('json error value is not a dict')
428+
except (json.JSONDecodeError, UnicodeDecodeError, ValueError):
427429
logger.error('failed to decode error response: %r', response.content)
428430
# When the user points to an S3 endpoint, he won't receive the JSON error
429431
# he expects. In that case, we can provide at least a hint of "what happened".
@@ -439,10 +441,22 @@ def _translate_errors(cls, fcn, post_params=None):
439441
logger.debug(
440442
'received error has extra (unsupported) keys: %s', extra_error_keys
441443
)
444+
445+
try:
446+
status = int(error.get('status', response.status_code))
447+
if status != response.status_code:
448+
raise ValueError('status code is not equal to the one in the response')
449+
except (TypeError, ValueError) as exc:
450+
logger.warning(
451+
'Inconsistent status codes returned by the server %r != %r; parsing exception: %r',
452+
error.get('status'), response.status_code, exc
453+
)
454+
status = response.status_code
455+
442456
raise interpret_b2_error(
443-
int(error.get('status', response.status_code)),
444-
error.get('code'),
445-
error.get('message'),
457+
status,
458+
str(error['code']) if 'code' in error else None,
459+
str(error['message']) if 'message' in error else None,
446460
response.headers,
447461
post_params,
448462
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Handle json encoded, invalid B2 error responses, preventing exceptions such as `invalid literal for int() with base 10: 'service_unavailable'`.

test/unit/b2http/test_b2http.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,36 @@ def test_b2_error__nginx_html():
145145
assert response.content.decode('utf-8') in str(exc_info.value)
146146

147147

148+
def test_b2_error__invalid_error_format():
149+
"""
150+
Handling of invalid error format.
151+
152+
If server returns valid JSON, but not matching B2 error schema, we should still raise ServiceError.
153+
"""
154+
response = MagicMock()
155+
response.status_code = 503
156+
# valid JSON, but not a valid B2 error (it should be a dict, not a list)
157+
response.content = b'[]'
158+
with pytest.raises(ServiceError) as exc_info:
159+
B2Http._translate_errors(lambda: response)
160+
assert '503' in str(exc_info.value)
161+
162+
163+
def test_b2_error__invalid_error_values():
164+
"""
165+
Handling of invalid error values.
166+
167+
If server returns valid JSON, but not matching B2 error schema, we should still raise ServiceError.
168+
"""
169+
response = MagicMock()
170+
response.status_code = 503
171+
# valid JSON, but not a valid B2 error (code and status values (and therefore types!) are swapped)
172+
response.content = b'{"code": 503, "message": "Service temporarily unavailable", "status": "service_unavailable"}'
173+
with pytest.raises(ServiceError) as exc_info:
174+
B2Http._translate_errors(lambda: response)
175+
assert '503 Service temporarily unavailable' in str(exc_info.value)
176+
177+
148178
class TestTranslateAndRetry(TestBase):
149179
def setUp(self):
150180
self.response = MagicMock()

0 commit comments

Comments
 (0)