diff --git a/pyrit/prompt_target/http_target/http_target.py b/pyrit/prompt_target/http_target/http_target.py index 1aeee2b6e3..41d4192380 100644 --- a/pyrit/prompt_target/http_target/http_target.py +++ b/pyrit/prompt_target/http_target/http_target.py @@ -239,8 +239,10 @@ def parse_raw_http_request(self, http_request: str) -> tuple[dict[str, str], Req body = "" + # Split the request into headers and body by finding the double newlines (\n\n). + # Preserve body whitespace exactly as provided in the raw request. # Support both LF and CRLF raw HTTP requests (e.g. copied from Burp). - request_parts = re.split(r"\r?\n\r?\n", http_request.strip(), maxsplit=1) + request_parts = re.split(r"\r?\n\r?\n", maxsplit=1) # Parse out the header components header_lines = request_parts[0].strip().splitlines() diff --git a/tests/unit/target/test_http_target_parsing.py b/tests/unit/target/test_http_target_parsing.py index c0c64904ca..9ef40dc3d3 100644 --- a/tests/unit/target/test_http_target_parsing.py +++ b/tests/unit/target/test_http_target_parsing.py @@ -89,6 +89,19 @@ def test_parse_raw_http_request_preserves_relative_url_case(sqlite_instance): assert version == "HTTP/1.1" +def test_parse_raw_http_request_preserves_body_trailing_whitespace(sqlite_instance): + request = "POST /submit HTTP/1.1\nHost: example.com\nContent-Type: text/plain\n\nhello \n" + target = HTTPTarget(http_request=request) + + headers, body, url, method, version = target.parse_raw_http_request(request) + + assert url == "https://example.com/submit" + assert method == "POST" + assert headers == {"host": "example.com", "content-type": "text/plain"} + assert body == "hello \n" + assert version == "HTTP/1.1" + + def test_parse_regex_response_no_match(): mock_response = MagicMock() mock_response.content = b"No match here"