resolved RESOLVED PASS · 22 tool calls · 72 s · psf/requests
Trim excess leading path separators A URL with excess leading / (path-separator)s would cause urllib3 to attempt to reparse the request-uri as a full URI with a host and port. This bypasses that logic in ConnectionPool.urlopen by replacing these leading /s with just a single /. Closes #6643
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"path\" src/requests | grep \"separator\" -A 5 -B 5 | head -20"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 2 | run_command | {
"command": "grep -rn \"urllib3\" src/requests | grep \"ConnectionPool\" -A 5 -B 5 | head -20"
} | {"status": "ok", "stdout": "src/requests/adapters.py:209: :rtype: urllib3.ProxyManager\nsrc/requests/adapters.py:242: :param conn: The urllib3 connection objectfull{"status": "ok", "stdout": "src/requests/adapters.py:209: :rtype: urllib3.ProxyManager\nsrc/requests/adapters.py:242: :param conn: The urllib3 connection object associated with the cert.\nsrc/requests/adapters.py:294: \"\"\"Builds a :class:`Response <requests.Response>` object from a urllib3\nsrc/requests/adapters.py:300: :param resp: The urllib3 response object.\nsrc/requests/adapters.py:331: \"\"\"Returns a urllib3 connection for the given URL. This should not be\nsrc/requests/adapters.py:337: :rtype: urllib3.ConnectionPool\nsrc/requests/adapters.py:414: through a proxy. This works with urllib3 magic to ensure that they are\nsrc/requests/adapters.py:443: :type timeout: float or tuple or urllib3 Timeout object\nsrc/requests/adapters.py:515: # This branch is for urllib3 v1.22 and later.\nsrc/requests/adapters.py:528: # This branch is for urllib3 versions earlier than v1.22\nsrc/requests/help.py:9:import urllib3\n", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "grep -rn \"lstrip\" src/requests | head -20"
} | {"status": "ok", "stdout": "src/requests/models.py:422: url = url.lstrip()\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "src/requests/models.py:422: url = url.lstrip()\n", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "grep -rn \"PreparedRequest\" src/requests | head -20"
} | {"status": "ok", "stdout": "src/requests/__init__.py:173:from .models import PreparedRequest, Request, Response\nsrc/requests/models.py:233: Used to prepare a :full{"status": "ok", "stdout": "src/requests/__init__.py:173:from .models import PreparedRequest, Request, Response\nsrc/requests/models.py:233: Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server.\nsrc/requests/models.py:255: <PreparedRequest [GET]>\nsrc/requests/models.py:296: \"\"\"Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it.\"\"\"\nsrc/requests/models.py:297: p = PreparedRequest()\nsrc/requests/models.py:313:class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):\nsrc/requests/models.py:314: \"\"\"The fully mutable :class:`PreparedRequest <PreparedRequest>` object,\nsrc/requests/models.py:327: <PreparedRequest [GET]>\nsrc/requests/models.py:380: return f\"<PreparedRequest [{self.method}]>\"\nsrc/requests/models.py:383: p = PreparedRequest()\nsrc/requests/models.py:617: :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls\nsrc/requests/models.py:701: #: The :class:`PreparedRequest <PreparedRequest>` object to which this\nsrc/requests/models.py:786: \"\"\"Returns a PreparedRequest for the next request in a redirect chain, if there is one.\"\"\"\nsrc/requests/adapters.py:79: \"\"\"Sends PreparedRequest object. Returns Response object.\nsrc/requests/adapters.py:81: :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.\nsrc/requests/adapters.py:299: :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.\nsrc/requests/adapters.py:379: :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.\nsrc/requests/adapters.py:407: :param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to.\nsrc/requests/adapters.py:436: \"\"\"Sends PreparedRequest object. Returns Response object.\nsrc/requests/adapters.py:438: :param request: The :class:`PreparedRequest <PreparedReques |
| 5 | run_command | {
"command": "grep -rn \"url\" src/requests/models.py | grep \"PreparedRequest\" -A 5 -B 5 | head -40"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 6 | run_command | {
"command": "grep -n \"class PreparedRequest\" src/requests/models.py"
} | {"status": "ok", "stdout": "313:class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "313:class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):\n", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "sed -n '313,450p' src/requests/models.py"
} | {"status": "ok", "stdout": "class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):\n \"\"\"The fully mutable :class:`PreparedRequest <PreparedRequest>`full{"status": "ok", "stdout": "class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):\n \"\"\"The fully mutable :class:`PreparedRequest <PreparedRequest>` object,\n containing the exact bytes that will be sent to the server.\n\n Instances are generated from a :class:`Request <Request>` object, and\n should not be instantiated manually; doing so may produce undesirable\n effects.\n\n Usage::\n\n >>> import requests\n >>> req = requests.Request('GET', 'https://httpbin.org/get')\n >>> r = req.prepare()\n >>> r\n <PreparedRequest [GET]>\n\n >>> s = requests.Session()\n >>> s.send(r)\n <Response [200]>\n \"\"\"\n\n def __init__(self):\n #: HTTP verb to send to the server.\n self.method = None\n #: HTTP URL to send the request to.\n self.url = None\n #: dictionary of HTTP headers.\n self.headers = None\n # The `CookieJar` used to create the Cookie header will be stored here\n # after prepare_cookies is called\n self._cookies = None\n #: request body to send to the server.\n self.body = None\n #: dictionary of callback hooks, for internal usage.\n self.hooks = default_hooks()\n #: integer denoting starting position of a readable file-like body.\n self._body_position = None\n\n def prepare(\n self,\n method=None,\n url=None,\n headers=None,\n files=None,\n data=None,\n params=None,\n auth=None,\n cookies=None,\n hooks=None,\n json=None,\n ):\n \"\"\"Prepares the entire request with the given parameters.\"\"\"\n\n self.prepare_method(method)\n self.prepare_url(url, params)\n self.prepare_headers(headers)\n self.prepare_cookies(cookies)\n self.prepare_body(data, files, json)\n self.prepare_auth(auth, url)\n\n # Note that prepare_auth must be last to enable authenticat |
| 8 | run_command | {
"command": "sed -n '450,550p' src/requests/models.py"
} | {"status": "ok", "stdout": " if not unicode_is_ascii(host):\n try:\n host = self._get_idna_encoded_host(host)\n except UnicodeError:\n raise InvalidURL(\"URL hafull{"status": "ok", "stdout": " if not unicode_is_ascii(host):\n try:\n host = self._get_idna_encoded_host(host)\n except UnicodeError:\n raise InvalidURL(\"URL has an invalid label.\")\n elif host.startswith((\"*\", \".\")):\n raise InvalidURL(\"URL has an invalid label.\")\n\n # Carefully reconstruct the network location\n netloc = auth or \"\"\n if netloc:\n netloc += \"@\"\n netloc += host\n if port:\n netloc += f\":{port}\"\n\n # Bare domains aren't valid URLs.\n if not path:\n path = \"/\"\n\n if isinstance(params, (str, bytes)):\n params = to_native_string(params)\n\n enc_params = self._encode_params(params)\n if enc_params:\n if query:\n query = f\"{query}&{enc_params}\"\n else:\n query = enc_params\n\n url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))\n self.url = url\n\n def prepare_headers(self, headers):\n \"\"\"Prepares the given HTTP headers.\"\"\"\n\n self.headers = CaseInsensitiveDict()\n if headers:\n for header in headers.items():\n # Raise exception on invalid header value.\n check_header_validity(header)\n name, value = header\n self.headers[to_native_string(name)] = value\n\n def prepare_body(self, data, files, json=None):\n \"\"\"Prepares the given HTTP body data.\"\"\"\n\n # Check if file, fo, generator, iterator.\n # If not, run through normal process.\n\n # Nottin' on you.\n body = None\n content_type = None\n\n if not data and json is not None:\n # urllib3 requires a bytes-like body. Python 2's json.dumps\n # provides this natively, but Python 3 gives a Unicode string.\n content_type = \" |
| 9 | run_command | {
"command": "grep -n \"parse_url\" src/requests/models.py"
} | {"status": "ok", "stdout": "25:from urllib3.util import parse_url\n433: scheme, auth, host, port, path, query, fragment = parse_url(url)\n", "stderr": "", "exitfull{"status": "ok", "stdout": "25:from urllib3.util import parse_url\n433: scheme, auth, host, port, path, query, fragment = parse_url(url)\n", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "grep -n \"requote_uri\" src/requests/models.py"
} | {"status": "ok", "stdout": "63: requote_uri,\n480: url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))\n", "stderr": "", "exit_code": 0full{"status": "ok", "stdout": "63: requote_uri,\n480: url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))\n", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "grep -n \"urlunparse\" src/requests/models.py"
} | {"status": "ok", "stdout": "39:from .compat import urlencode, urlsplit, urlunparse\n480: url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragmefull{"status": "ok", "stdout": "39:from .compat import urlencode, urlsplit, urlunparse\n480: url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))\n", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "grep -n \"path =\" src/requests/models.py | grep -C 2 \"PreparedRequest\" | head -20"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "sed -n '460,485p' src/requests/models.py"
} | {"status": "ok", "stdout": " if netloc:\n netloc += \"@\"\n netloc += host\n if port:\n netloc += f\":{port}\"\n\n # Bare domains aren't valid URLs.\n if not pafull{"status": "ok", "stdout": " if netloc:\n netloc += \"@\"\n netloc += host\n if port:\n netloc += f\":{port}\"\n\n # Bare domains aren't valid URLs.\n if not path:\n path = \"/\"\n\n if isinstance(params, (str, bytes)):\n params = to_native_string(params)\n\n enc_params = self._encode_params(params)\n if enc_params:\n if query:\n query = f\"{query}&{enc_params}\"\n else:\n query = enc_params\n\n url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))\n self.url = url\n\n def prepare_headers(self, headers):\n \"\"\"Prepares the given HTTP headers.\"\"\"\n\n", "stderr": "", "exit_code": 0} |
| 14 | write_file | {
"content": "import requests\n\ndef test_excess_leading_slashes():\n s = requests.Session()\n # Test with multiple leading slashes in the path\n # The issue is that //path/to/resource might be interpreted as a network location (host)\n # by urllib3 if it's not handled correctly.\n # However, requests.Request usually takes a full URL.\n # If the user provides 'http://httpbin.org//get', it should be normalized to 'http://httpbin.org/get'\n # according to the issue description \"Trim excess leading path separators\".\n \n # Let's see what happens with multiple slashes\n url = 'http://httpbin.org///get'\n r = s.get(url)\n print(f\"URL: {url} -> Requested URL: {r.url}\")\n # If the fix is applied, r.url should be 'http://httpbin.org/get' (or at least not have multiple leading slashes in path)\n # Actually, the issue says \"Trim excess leading path separators\" and \"replacing these leading /s with just a single /\".\n # This refers to the path part of the URL.\n\nif __name__ == \"__main__\":\n test_excess_leading_slashes()\n",
"filepath": "/tmp/test_slashes.py"
} | {"status": "error", "error_type": "FileWriteError", "error_message": "Path traversal detected: '/tmp/test_slashes.py' escapes workspace root."}full{"status": "error", "error_type": "FileWriteError", "error_message": "Path traversal detected: '/tmp/test_slashes.py' escapes workspace root."} |
| 15 | write_file | {
"content": "import requests\n\ndef test_excess_leading_slashes():\n s = requests.Session()\n url = 'http://httpbin.org///get'\n # We can't actually make a network request, so we'll use PreparedRequest\n from requests.models import PreparedRequest\n p = PreparedRequest()\n p.prepare('GET', url, params=None)\n print(f\"URL: {url} -> Prepared URL: {p.url}\")\n\nif __name__ == \"__main__\":\n test_excess_leading_slashes()\n",
"filepath": "test_slashes.py"
} | {"status": "ok", "filepath": "test_slashes.py", "size": 418}full{"status": "ok", "filepath": "test_slashes.py", "size": 418} |
| 16 | run_command | {
"command": "python test_slashes.py"
} | {"status": "ok", "stdout": "URL: http://httpbin.org///get -> Prepared URL: http://httpbin.org///get\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "URL: http://httpbin.org///get -> Prepared URL: http://httpbin.org///get\n", "stderr": "", "exit_code": 0} |
| 17 | run_command | {
"command": "sed -n '460,485p' src/requests/models.py"
} | {"status": "ok", "stdout": " if netloc:\n netloc += \"@\"\n netloc += host\n if port:\n netloc += f\":{port}\"\n\n # Bare domains aren't valid URLs.\n if not pafull{"status": "ok", "stdout": " if netloc:\n netloc += \"@\"\n netloc += host\n if port:\n netloc += f\":{port}\"\n\n # Bare domains aren't valid URLs.\n if not path:\n path = \"/\"\n\n if isinstance(params, (str, bytes)):\n params = to_native_string(params)\n\n enc_params = self._encode_params(params)\n if enc_params:\n if query:\n query = f\"{query}&{enc_params}\"\n else:\n query = enc_params\n\n url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))\n self.url = url\n\n def prepare_headers(self, headers):\n \"\"\"Prepares the given HTTP headers.\"\"\"\n\n", "stderr": "", "exit_code": 0} |
| 18 | edit_file | {
"filepath": "src/requests/models.py",
"new_string": " # Bare domains aren't valid URLs.\n if not path:\n path = \"/\"\n elif path.startswith(\"//\"):\n path = \"/\" + path.lstrip(\"/\")\n\n if isinstance(params, (str, bytes)):",
"old_string": " # Bare domains aren't valid URLs.\n if not path:\n path = \"/\""
} | {"status": "ok", "filepath": "src/requests/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/requests/models.py\n+++ b/src/requests/models.pfull{"status": "ok", "filepath": "src/requests/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/requests/models.py\n+++ b/src/requests/models.py\n@@ -466,6 +466,10 @@\n # Bare domains aren't valid URLs.\n if not path:\n path = \"/\"\n+ elif path.startswith(\"//\"):\n+ path = \"/\" + path.lstrip(\"/\")\n+\n+ if isinstance(params, (str, bytes)):\n \n if isinstance(params, (str, bytes)):\n params = to_native_string(params)\n", "is_truncated": false} |
| 19 | run_command | {
"command": "python test_slashes.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"/workspace/test_slashes.py\", line 1, in <modulefull{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"/workspace/test_slashes.py\", line 1, in <module>\n import requests\n File \"/workspace/src/requests/__init__.py\", line 160, in <module>\n from .api import delete, get, head, options, patch, post, put, request\n File \"/workspace/src/requests/api.py\", line 11, in <module>\n from . import sessions\n File \"/workspace/src/requests/sessions.py\", line 15, in <module>\n from .adapters import HTTPAdapter\n File \"/workspace/src/requests/adapters.py\", line 44, in <module>\n from .models import Response\n File \"/workspace/src/requests/models.py\", line 474\n if isinstance(params, (str, bytes)):\n ^^\nIndentationError: expected an indented block after 'if' statement on line 472\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"/workspace/test_slashes.py\", line 1, in <module>\n import requests\n File \"/workspace/src/requests/__init__.py\", line 160, in <module>\n from .api import delete, get, head, options, patch, post, put, request\n File \"/workspace/src/requests/api.py\", line 11, in <module>\n from . import sessions\n File \"/workspace/src/requests/sessions.py\", line 15, in <module>\n from .adapters import HTTPAdapter\n File \"/workspace/src/requests/adapters.py\", line 44, in <module>\n from .models import Response\n File \"/workspace/src/requests/models.py\", line 474\n if isinstance(params, (str, bytes)):\n ^^\nIndentationError: expected an indented block after 'if' statement on line 472\n", "exit_code": 1}} |
| 20 | run_command | {
"command": "sed -n '460,480p' src/requests/models.py"
} | {"status": "ok", "stdout": " if netloc:\n netloc += \"@\"\n netloc += host\n if port:\n netloc += f\":{port}\"\n\n # Bare domains aren't valid URLs.\n if not pafull{"status": "ok", "stdout": " if netloc:\n netloc += \"@\"\n netloc += host\n if port:\n netloc += f\":{port}\"\n\n # Bare domains aren't valid URLs.\n if not path:\n path = \"/\"\n elif path.startswith(\"//\"):\n path = \"/\" + path.lstrip(\"/\")\n\n if isinstance(params, (str, bytes)):\n\n if isinstance(params, (str, bytes)):\n params = to_native_string(params)\n\n enc_params = self._encode_params(params)\n if enc_params:\n if query:\n query = f\"{query}&{enc_params}\"\n", "stderr": "", "exit_code": 0} |
| 21 | edit_file | {
"filepath": "src/requests/models.py",
"new_string": " # Bare domains aren't valid URLs.\n if not path:\n path = \"/\"\n elif path.startswith(\"//\"):\n path = \"/\" + path.lstrip(\"/\")\n\n if isinstance(params, (str, bytes)):\n params = to_native_string(params)",
"old_string": " # Bare domains aren't valid URLs.\n if not path:\n path = \"/\"\n elif path.startswith(\"//\"):\n path = \"/\" + path.lstrip(\"/\")\n\n if isinstance(params, (str, bytes)):\n\n if isinstance(params, (str, bytes)):\n params = to_native_string(params)"
} | {"status": "ok", "filepath": "src/requests/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/requests/models.py\n+++ b/src/requests/models.pfull{"status": "ok", "filepath": "src/requests/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/requests/models.py\n+++ b/src/requests/models.py\n@@ -470,8 +470,6 @@\n path = \"/\" + path.lstrip(\"/\")\n \n if isinstance(params, (str, bytes)):\n-\n- if isinstance(params, (str, bytes)):\n params = to_native_string(params)\n \n enc_params = self._encode_params(params)\n", "is_truncated": false} |
| 22 | run_command | {
"command": "python test_slashes.py"
} | {"status": "ok", "stdout": "URL: http://httpbin.org///get -> Prepared URL: http://httpbin.org/get\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "URL: http://httpbin.org///get -> Prepared URL: http://httpbin.org/get\n", "stderr": "", "exit_code": 0} |
| 23 | submit_patch | {} | {"status": "ok", "patch_size": 1076, "files_changed": 2}full{"status": "ok", "patch_size": 1076, "files_changed": 2} |
diff --git a/src/requests/models.py b/src/requests/models.py
index 44556394..efed0d91 100644
--- a/src/requests/models.py
+++ b/src/requests/models.py
@@ -466,6 +466,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# Bare domains aren't valid URLs.
if not path:
path = "/"
+ elif path.startswith("//"):
+ path = "/" + path.lstrip("/")
if isinstance(params, (str, bytes)):
params = to_native_string(params)
diff --git a/test_slashes.py b/test_slashes.py
new file mode 100644
index 00000000..bf9fda3b
--- /dev/null
+++ b/test_slashes.py
@@ -0,0 +1,13 @@
+import requests
+
+def test_excess_leading_slashes():
+ s = requests.Session()
+ url = 'http://httpbin.org///get'
+ # We can't actually make a network request, so we'll use PreparedRequest
+ from requests.models import PreparedRequest
+ p = PreparedRequest()
+ p.prepare('GET', url, params=None)
+ print(f"URL: {url} -> Prepared URL: {p.url}")
+
+if __name__ == "__main__":
+ test_excess_leading_slashes()
. [100%]
=============================== warnings summary ===============================
../../../../../../kaggle/tmp/envs/requests/lib/python3.13/site-packages/_pytest/config/__init__.py:1464
/kaggle/tmp/envs/requests/lib/python3.13/site-packages/_pytest/config/__init__.py:1464: PytestConfigWarning: Unknown config option: timeout
self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
1 passed, 1 warning in 0.01s
[2026-09-26 02:23:59,558] WARNING in core: flasgger is not installed; serving the static landing page at / and skipping the Swagger UI and /spec.json.