mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-29 01:42:19 +00:00
fix(proxy): also percent-encode a literal % in header values
Greptile flagged that a latin-1-safe value containing a literal "%" (e.g. "model%20name") passed through unchanged, making it indistinguishable from a genuinely percent-encoded value once a caller starts unquoting these headers unconditionally.
This commit is contained in:
parent
45e102785e
commit
6cad229179
2 changed files with 31 additions and 17 deletions
|
|
@ -58,12 +58,24 @@ def safe_header_value(value: str) -> str:
|
|||
characters, which crashes response header assembly with a UnicodeEncodeError.
|
||||
Percent-encode such values so they stay ASCII-safe and reversible via
|
||||
``urllib.parse.unquote``, instead of dropping them or crashing the request.
|
||||
|
||||
A value that is already latin-1 safe but contains a literal ``%`` is also
|
||||
percent-encoded: left alone, it would be indistinguishable from a genuinely
|
||||
encoded value once mixed with the encoded case, so a caller applying
|
||||
``unquote`` unconditionally would silently decode the wrong string.
|
||||
"""
|
||||
is_latin1_safe: Final = _is_latin1_safe(value)
|
||||
if is_latin1_safe and "%" not in value:
|
||||
return value
|
||||
return quote(value, safe="")
|
||||
|
||||
|
||||
def _is_latin1_safe(value: str) -> bool:
|
||||
try:
|
||||
value.encode("latin-1")
|
||||
return value
|
||||
return True
|
||||
except UnicodeEncodeError:
|
||||
return quote(value, safe="")
|
||||
return False
|
||||
|
||||
|
||||
def ensure_response_additional_headers(response: object) -> dict[str, object]:
|
||||
|
|
|
|||
|
|
@ -50,15 +50,8 @@ def test_add_fallback_headers_serializes_fallback_errors():
|
|||
)
|
||||
|
||||
assert result is response
|
||||
assert response._hidden_params["additional_headers"][
|
||||
"x-litellm-attempted-fallbacks"
|
||||
] == 1
|
||||
assert (
|
||||
json.loads(
|
||||
response._hidden_params["additional_headers"]["x-litellm-fallback-errors"]
|
||||
)
|
||||
== fallback_errors
|
||||
)
|
||||
assert response._hidden_params["additional_headers"]["x-litellm-attempted-fallbacks"] == 1
|
||||
assert json.loads(response._hidden_params["additional_headers"]["x-litellm-fallback-errors"]) == fallback_errors
|
||||
|
||||
|
||||
def test_add_retry_headers_to_streaming_wrapper():
|
||||
|
|
@ -84,9 +77,7 @@ def test_get_hidden_params_dict_with_pydantic_model_hidden_params():
|
|||
|
||||
class Response:
|
||||
def __init__(self):
|
||||
self._hidden_params = InnerHiddenParams(
|
||||
additional_headers={"x-custom": "value"}
|
||||
)
|
||||
self._hidden_params = InnerHiddenParams(additional_headers={"x-custom": "value"})
|
||||
|
||||
result = get_hidden_params_dict(Response())
|
||||
assert result == {"additional_headers": {"x-custom": "value"}}
|
||||
|
|
@ -133,9 +124,7 @@ def test_get_fallback_errors_from_headers_existing_list_passthrough():
|
|||
|
||||
|
||||
def test_get_fallback_errors_from_headers_invalid_json_returns_empty():
|
||||
result = get_fallback_errors_from_headers(
|
||||
{"x-litellm-fallback-errors": "not-valid-json-{"}
|
||||
)
|
||||
result = get_fallback_errors_from_headers({"x-litellm-fallback-errors": "not-valid-json-{"})
|
||||
assert result == []
|
||||
|
||||
|
||||
|
|
@ -175,3 +164,16 @@ def test_safe_header_value_percent_encodes_non_latin1_values():
|
|||
|
||||
assert result.encode("latin-1")
|
||||
assert unquote(result) == "azure/中文模型名稱"
|
||||
|
||||
|
||||
def test_safe_header_value_escapes_a_literal_percent_sign_too():
|
||||
"""
|
||||
A latin-1-safe value that happens to contain a literal "%" (e.g.
|
||||
"model%20name") must not pass through unchanged: it would then be
|
||||
indistinguishable from a genuinely percent-encoded value, and a caller
|
||||
that always unquotes would silently decode it into the wrong string.
|
||||
"""
|
||||
result = safe_header_value("model%20name")
|
||||
|
||||
assert "%" not in result.replace("%25", "")
|
||||
assert unquote(result) == "model%20name"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue