{"record":{"id":"a9f5cf29204f4788","repo":"sgl-project/sglang","slug":"invalid-media-url-current-url-r","errorCode":null,"errorMessage":"Invalid media URL: {current_url!r}","messagePattern":"Invalid media URL: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/utils/common.py","lineNumber":1611,"sourceCode":"    a connection is made. The response is streamed to enforce both the total\n    request deadline and the configured byte limit without first buffering an\n    attacker-controlled body in memory.\n    \"\"\"\n\n    if timeout <= 0:\n        raise ValueError(\"media URL timeout must be positive\")\n\n    session = get_mm_http_session()\n    deadline = time.monotonic() + timeout\n    current_url = url\n\n    for redirect_count in range(_MAX_MEDIA_URL_REDIRECTS + 1):\n        # Validate the same normalized URL representation that requests sends\n        # to urllib3. This avoids parser disagreements around backslashes and\n        # userinfo separators.\n        prepared_url = requests.Request(\"GET\", current_url).prepare().url\n        if prepared_url is None:\n            raise ValueError(f\"Invalid media URL: {current_url!r}\")\n        _assert_media_url_allowed(prepared_url)\n\n        remaining = deadline - time.monotonic()\n        if remaining <= 0:\n            raise requests.exceptions.Timeout(\n                f\"Timed out while downloading media URL: {url}\"\n            )\n\n        with session.get(\n            prepared_url,\n            allow_redirects=False,\n            stream=True,\n            timeout=remaining,\n        ) as response:\n            location = response.headers.get(\"Location\")\n            if response.status_code in _MEDIA_URL_REDIRECT_STATUS_CODES and location:\n                if redirect_count == _MAX_MEDIA_URL_REDIRECTS:\n                    raise requests.exceptions.TooManyRedirects(","sourceCodeStart":1593,"sourceCodeEnd":1629,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/utils/common.py#L1593-L1629","documentation":"Before each fetch (and after each redirect hop), download_remote_media re-prepares the URL via requests.Request(...).prepare(); if preparation yields None the URL is unparseable by the actual HTTP client and is rejected. This validates the exact normalized URL string requests/urllib3 will send, avoiding parser-disagreement tricks (backslashes, userinfo).","triggerScenarios":"Passing malformed URLs that urlparse tolerates but requests cannot prepare (control characters, invalid percent-encoding, schemes like 'http:////x'); also reached on a redirect Location header containing such a URL.","commonSituations":"User-supplied image/audio URLs with unencoded spaces or control chars; malicious or broken redirect targets; double-encoded strings from JSON payloads.","solutions":["Validate/percent-encode URLs client-side before sending requests","Use urllib.parse.quote on path components when constructing URLs","Catch ValueError around the media fetch and return a 400 to the client instead of crashing the worker"],"exampleFix":"# before\nurl = 'https://example.com/a b.png'  # space -> prepare() may fail\n# after\nfrom urllib.parse import quote\ndata = download_remote_media('https://example.com/' + quote('a b.png'), timeout=30)","handlingStrategy":"try-catch","validationCode":"from urllib.parse import quote, urlparse\nu = urlparse(candidate)\nif u.scheme not in ('http', 'https') or not u.hostname: reject()\nurl = u._replace(path=quote(u.path, safe='/:%')).geturl()","typeGuard":null,"tryCatchPattern":"try:\n    data = download_remote_media(url, timeout=30)\nexcept ValueError as e:\n    if 'Invalid media URL' in str(e):\n        return HTTPException(400, f'unparseable media URL: {url!r}')\n    raise","preventionTips":["Percent-encode user-supplied URL components client-side","Treat unparseable URLs as client errors (400), not worker crashes"],"tags":["media","url-validation","network","security"],"backgroundTag":"url-validation-failed","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}