From 50ab779bf7c7c1d781101a4b8da738e171e4bfc3 Mon Sep 17 00:00:00 2001
From: KennyMcSimpson <70093673+KennyMcSimpson@users.noreply.github.com>
Date: Sat, 24 Oct 2026 23:23:30 +0800
Subject: [PATCH] Preserve binary SOAP attachment content
---
CHANGES | 5 +++++
src/zeep/wsdl/attachments.py | 3 +--
tests/test_soap_xop.py | 35 ++++++++++++++++++++++++++++++++---
3 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/CHANGES b/CHANGES
index 4d6e8cec..164228ba 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+Unreleased
+----------
+ - Preserve leading and trailing CR/LF bytes in binary SOAP attachments
+ (#1249, #1481).
+
4.3.3 (2026-06-18)
------------------
- Wire up the ``forbid_external`` setting (previously defined but unused
diff --git a/src/zeep/wsdl/attachments.py b/src/zeep/wsdl/attachments.py
index 44e09900..6a4737da 100644
--- a/src/zeep/wsdl/attachments.py
+++ b/src/zeep/wsdl/attachments.py
@@ -74,7 +74,6 @@ def content(self):
if encoding == "base64":
return base64.b64decode(content)
- elif encoding == "binary":
- return content.strip(b"\r\n")
else:
+ # MultipartDecoder has already removed the MIME boundary framing.
return content
diff --git a/tests/test_soap_xop.py b/tests/test_soap_xop.py
index 9c010114..c89ec37b 100644
--- a/tests/test_soap_xop.py
+++ b/tests/test_soap_xop.py
@@ -1,5 +1,6 @@
from io import StringIO
+import pytest
import requests_mock
from lxml import etree
from pretend import stub
@@ -68,7 +69,7 @@ def test_rebuild_xml():
5XJ45-3B2
accident
- Li4uYmluYXJ5IEpQRyBpbWFnZS4uLg==
+ Li4uYmluYXJ5IEpQRyBpbWFnZS4uLg0K
@@ -76,6 +77,33 @@ def test_rebuild_xml():
assert_nodes_equal(etree.tostring(document), expected)
+@pytest.mark.parametrize(
+ "content",
+ [
+ b"binary\r\n",
+ b"\r\nbinary",
+ b"\r\nbinary\r\n",
+ b"\r\n",
+ b"\x00\xff\r\n",
+ b"binary",
+ b"",
+ ],
+)
+def test_binary_attachment_preserves_content(content):
+ data = (
+ b"--MIME_boundary\r\n"
+ b"Content-Type: application/octet-stream\r\n"
+ b"Content-Transfer-Encoding: binary\r\n"
+ b"Content-ID: \r\n\r\n"
+ + content
+ + b"\r\n--MIME_boundary--\r\n"
+ )
+ decoder = MultipartDecoder(data, "multipart/related; boundary=MIME_boundary")
+ assert decoder.parts[0].content == content
+ message_pack = MessagePack(parts=decoder.parts)
+ assert message_pack.attachments[0].content == content
+
+
def get_client_service():
wsdl_main = StringIO(
"""
@@ -249,7 +277,8 @@ def test_xop():
headers={"Content-Type": content_type},
)
result = service.TestOperation2("")
- assert result["_value_1"] == b"BINARYDATA"
+ # The extra CRLF before the MIME boundary belongs to the binary payload.
+ assert result["_value_1"] == b"BINARYDATA\r\n"
m.post(
"http://tests.python-zeep.org/test",
@@ -302,4 +331,4 @@ def test_xop_cid_encoded():
headers={"Content-Type": content_type},
)
result = service.TestOperation2("")
- assert result["_value_1"] == b"BINARYDATA"
+ assert result["_value_1"] == b"BINARYDATA\r\n"