Fix multipart/form-data boundary issue in HTTP Call node (#23903)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -279,14 +279,22 @@ class Executor:
|
|||||||
headers[authorization.config.header] = authorization.config.api_key or ""
|
headers[authorization.config.header] = authorization.config.api_key or ""
|
||||||
|
|
||||||
# Handle Content-Type for multipart/form-data requests
|
# Handle Content-Type for multipart/form-data requests
|
||||||
# Fix for issue #22880: Missing boundary when using multipart/form-data
|
# Fix for issue #23829: Missing boundary when using multipart/form-data
|
||||||
body = self.node_data.body
|
body = self.node_data.body
|
||||||
if body and body.type == "form-data":
|
if body and body.type == "form-data":
|
||||||
# For multipart/form-data with files, let httpx handle the boundary automatically
|
# For multipart/form-data with files (including placeholder files),
|
||||||
# by not setting Content-Type header when files are present
|
# remove any manually set Content-Type header to let httpx handle
|
||||||
if not self.files or all(f[0] == "__multipart_placeholder__" for f in self.files):
|
# For multipart/form-data, if any files are present (including placeholder files),
|
||||||
# Only set Content-Type when there are no actual files
|
# we must remove any manually set Content-Type header. This is because httpx needs to
|
||||||
# This ensures httpx generates the correct boundary
|
# automatically set the Content-Type and boundary for multipart encoding whenever files
|
||||||
|
# are included, even if they are placeholders, to avoid boundary issues and ensure correct
|
||||||
|
# file upload behaviour. Manually setting Content-Type can cause httpx to fail to set the
|
||||||
|
# boundary, resulting in invalid requests.
|
||||||
|
if self.files:
|
||||||
|
# Remove Content-Type if it was manually set to avoid boundary issues
|
||||||
|
headers = {k: v for k, v in headers.items() if k.lower() != "content-type"}
|
||||||
|
else:
|
||||||
|
# No files at all, set Content-Type manually
|
||||||
if "content-type" not in (k.lower() for k in headers):
|
if "content-type" not in (k.lower() for k in headers):
|
||||||
headers["Content-Type"] = "multipart/form-data"
|
headers["Content-Type"] = "multipart/form-data"
|
||||||
elif body and body.type in BODY_TYPE_TO_CONTENT_TYPE:
|
elif body and body.type in BODY_TYPE_TO_CONTENT_TYPE:
|
||||||
|
@@ -243,8 +243,6 @@ def test_executor_with_form_data():
|
|||||||
# Check the executor's data
|
# Check the executor's data
|
||||||
assert executor.method == "post"
|
assert executor.method == "post"
|
||||||
assert executor.url == "https://api.example.com/upload"
|
assert executor.url == "https://api.example.com/upload"
|
||||||
assert "Content-Type" in executor.headers
|
|
||||||
assert "multipart/form-data" in executor.headers["Content-Type"]
|
|
||||||
assert executor.params is None
|
assert executor.params is None
|
||||||
assert executor.json is None
|
assert executor.json is None
|
||||||
# '__multipart_placeholder__' is expected when no file inputs exist,
|
# '__multipart_placeholder__' is expected when no file inputs exist,
|
||||||
@@ -252,6 +250,11 @@ def test_executor_with_form_data():
|
|||||||
assert executor.files == [("__multipart_placeholder__", ("", b"", "application/octet-stream"))]
|
assert executor.files == [("__multipart_placeholder__", ("", b"", "application/octet-stream"))]
|
||||||
assert executor.content is None
|
assert executor.content is None
|
||||||
|
|
||||||
|
# After fix for #23829: When placeholder files exist, Content-Type is removed
|
||||||
|
# to let httpx handle Content-Type and boundary automatically
|
||||||
|
headers = executor._assembling_headers()
|
||||||
|
assert "Content-Type" not in headers or "multipart/form-data" not in headers.get("Content-Type", "")
|
||||||
|
|
||||||
# Check that the form data is correctly loaded in executor.data
|
# Check that the form data is correctly loaded in executor.data
|
||||||
assert isinstance(executor.data, dict)
|
assert isinstance(executor.data, dict)
|
||||||
assert "text_field" in executor.data
|
assert "text_field" in executor.data
|
||||||
|
Reference in New Issue
Block a user