From 292420d95e3ac9fbb45bd9727b1af195d2acf826 Mon Sep 17 00:00:00 2001 From: Eckschi Date: Mon, 14 Oct 2024 08:18:01 +0200 Subject: [PATCH 1/4] content type of multipart might not be the first entry in each part --- sippy/MultipartMixBody.py | 3 ++- tests/test_Multipart.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/test_Multipart.py diff --git a/sippy/MultipartMixBody.py b/sippy/MultipartMixBody.py index 882548233..708ac9fce 100644 --- a/sippy/MultipartMixBody.py +++ b/sippy/MultipartMixBody.py @@ -40,7 +40,8 @@ def __init__(self, body = None, ctype = None): parts = [p.lstrip() for p in bparts[1:-1]] self.parts = [] for sect in parts: - ct, sect = sect.split('\r\n', 1) + headers, sect = sect.split('\r\n\r\n', 1) # do hots wos + ct = [line for line in headers.split('\r\n') if "Content-Type" in line][0] ct = SipHeader(ct).getBody() sect = MsgBody(sect, ct) self.parts.append(sect) diff --git a/tests/test_Multipart.py b/tests/test_Multipart.py new file mode 100644 index 000000000..f9b0785fa --- /dev/null +++ b/tests/test_Multipart.py @@ -0,0 +1,33 @@ +import unittest +from sippy.MsgBody import MsgBody +from sippy.MultipartMixBody import MultipartMixBody +from sippy.SipContentType import SipContentType + +# body from RFC 8147 https://datatracker.ietf.org/doc/html/rfc8147 +ecall_body1 = ( + "--boundaryZZZ\r\n" + "Content-Disposition: by-reference\r\n" + "Content-Type: application/EmergencyCallData.Control+xml\r\n" + "Content-ID: <3456789012@atlanta.example.com>\r\n" + "\r\n" + '\r\n' + '\r\n' + '\r\n' + "\r\n" + "--boundaryZZZ--\r\n" +) + + +class TestMultipart(unittest.TestCase): + def test_run(self): + ct = SipContentType("multipart/mixed;boundary=boundaryZZZ") + ct.parse() + got = MsgBody(ecall_body1, mtype=ct) + got.parse() + mp = got.content + self.assertEqual(MultipartMixBody, type(mp)) + self.assertEqual(1, len(mp.parts)) + p0 = mp.parts[0] + self.assertEqual(MsgBody, type(p0)) + print(p0.mtype) + self.assertEqual("application/EmergencyCallData.Control+xml", p0.mtype.localStr()) From 6ab991aa5ab102c1faadaab39b00b56a2a17e891 Mon Sep 17 00:00:00 2001 From: Eckschi Date: Mon, 14 Oct 2024 12:55:50 +0200 Subject: [PATCH 2/4] also remember sub-headers --- sippy/MultipartMixBody.py | 18 ++++++++++++++---- tests/test_Multipart.py | 3 +++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/sippy/MultipartMixBody.py b/sippy/MultipartMixBody.py index 708ac9fce..fa23f1d24 100644 --- a/sippy/MultipartMixBody.py +++ b/sippy/MultipartMixBody.py @@ -28,7 +28,7 @@ class MultipartMixBody(): parts = None boundary = None - + def __init__(self, body = None, ctype = None): if body is None: return @@ -39,12 +39,22 @@ def __init__(self, body = None, ctype = None): assert bparts[-1].strip() == '--' parts = [p.lstrip() for p in bparts[1:-1]] self.parts = [] + self.part_headers = [] for sect in parts: - headers, sect = sect.split('\r\n\r\n', 1) # do hots wos - ct = [line for line in headers.split('\r\n') if "Content-Type" in line][0] - ct = SipHeader(ct).getBody() + headers = [] + ct = None + headersect, sect = sect.split('\r\n\r\n', 1) + # parse sub headers + for hl in headersect.split('\r\n'): + h = SipHeader(hl) + if h.name == "content-type": + ct = h.getBody() + else: + headers.append(h) + # add part sect = MsgBody(sect, ct) self.parts.append(sect) + self.part_headers.append(headers) self.boundary = ctype.params["boundary"] def __str__(self): diff --git a/tests/test_Multipart.py b/tests/test_Multipart.py index f9b0785fa..e870cdeaf 100644 --- a/tests/test_Multipart.py +++ b/tests/test_Multipart.py @@ -31,3 +31,6 @@ def test_run(self): self.assertEqual(MsgBody, type(p0)) print(p0.mtype) self.assertEqual("application/EmergencyCallData.Control+xml", p0.mtype.localStr()) + h0 = mp.part_headers[0] + self.assertEqual("content-disposition", h0[0].name) + self.assertEqual("content-id", h0[1].name) From 4da466f0b09042259088e9635db7d4a4cb7d20a3 Mon Sep 17 00:00:00 2001 From: Manfred Eckschlager Date: Mon, 14 Oct 2024 14:52:11 +0200 Subject: [PATCH 3/4] also test content --- tests/test_Multipart.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/test_Multipart.py b/tests/test_Multipart.py index e870cdeaf..4d79336b1 100644 --- a/tests/test_Multipart.py +++ b/tests/test_Multipart.py @@ -16,6 +16,12 @@ "\r\n" "--boundaryZZZ--\r\n" ) +ecall_part0 = ( + '\r\n' + '\r\n' + '\r\n' + "\r\n" +) class TestMultipart(unittest.TestCase): @@ -29,8 +35,11 @@ def test_run(self): self.assertEqual(1, len(mp.parts)) p0 = mp.parts[0] self.assertEqual(MsgBody, type(p0)) - print(p0.mtype) - self.assertEqual("application/EmergencyCallData.Control+xml", p0.mtype.localStr()) + #print(p0.mtype) + self.assertEqual( + "application/EmergencyCallData.Control+xml", p0.mtype.localStr()) h0 = mp.part_headers[0] self.assertEqual("content-disposition", h0[0].name) self.assertEqual("content-id", h0[1].name) + # check that part body is without headers + self.assertEqual(ecall_part0, p0.content) From 9af47310e04633fe51d7de788494a44c9ccce555 Mon Sep 17 00:00:00 2001 From: Manfred Eckschlager Date: Mon, 14 Oct 2024 16:05:39 +0200 Subject: [PATCH 4/4] renamed test --- tests/test_Multipart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_Multipart.py b/tests/test_Multipart.py index 4d79336b1..6413bfab4 100644 --- a/tests/test_Multipart.py +++ b/tests/test_Multipart.py @@ -25,7 +25,7 @@ class TestMultipart(unittest.TestCase): - def test_run(self): + def test_multipart_unsorted_headers(self): ct = SipContentType("multipart/mixed;boundary=boundaryZZZ") ct.parse() got = MsgBody(ecall_body1, mtype=ct)