blob: 3de67b52cbad3ed5f1f08c28041c7f780bab11d1 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001From b7ecff22e77887626fd8e8608c4dd73bc7b7366f Mon Sep 17 00:00:00 2001
2From: George Joseph <gjoseph@sangoma.com>
3Date: Tue, 18 Jan 2022 06:14:31 -0700
4Subject: [PATCH] Additional multipart improvements
5
6Added the following APIs:
7pjsip_multipart_find_part_by_header()
8pjsip_multipart_find_part_by_header_str()
9pjsip_multipart_find_part_by_cid_str()
10pjsip_multipart_find_part_by_cid_uri()
11---
12 pjsip/include/pjsip/sip_multipart.h | 83 ++++++++++
13 pjsip/src/pjsip/sip_multipart.c | 223 +++++++++++++++++++++++++++
14 pjsip/src/test/multipart_test.c | 225 +++++++++++++++++++++++++++-
15 3 files changed, 530 insertions(+), 1 deletion(-)
16
17--- a/pjsip/include/pjsip/sip_multipart.h
18+++ b/pjsip/include/pjsip/sip_multipart.h
19@@ -154,6 +154,89 @@ pjsip_multipart_find_part( const pjsip_m
20 const pjsip_multipart_part *start);
21
22 /**
23+ * Find a body inside multipart bodies which has a header matching the
24+ * supplied one. Most useful for finding a part with a specific Content-ID.
25+ *
26+ * @param pool Memory pool to use for temp space.
27+ * @param mp The multipart body.
28+ * @param search_hdr Header to search for.
29+ * @param start If specified, the search will begin at
30+ * start->next part. Otherwise it will begin at
31+ * the first part in the multipart bodies.
32+ *
33+ * @return The first part which has a header matching the
34+ * specified one, or NULL if not found.
35+ */
36+PJ_DECL(pjsip_multipart_part*)
37+pjsip_multipart_find_part_by_header(pj_pool_t *pool,
38+ const pjsip_msg_body *mp,
39+ void *search_hdr,
40+ const pjsip_multipart_part *start);
41+
42+/**
43+ * Find a body inside multipart bodies which has a header matching the
44+ * supplied name and value. Most useful for finding a part with a specific
45+ * Content-ID.
46+ *
47+ * @param pool Memory pool to use for temp space.
48+ * @param mp The multipart body.
49+ * @param hdr_name Header name to search for.
50+ * @param hdr_value Header value search for.
51+ * @param start If specified, the search will begin at
52+ * start->next part. Otherwise it will begin at
53+ * the first part in the multipart bodies.
54+ *
55+ * @return The first part which has a header matching the
56+ * specified one, or NULL if not found.
57+ */
58+PJ_DECL(pjsip_multipart_part*)
59+pjsip_multipart_find_part_by_header_str(pj_pool_t *pool,
60+ const pjsip_msg_body *mp,
61+ const pj_str_t *hdr_name,
62+ const pj_str_t *hdr_value,
63+ const pjsip_multipart_part *start);
64+
65+
66+
67+/**
68+ * Find a body inside multipart bodies which has a Content-ID value matching the
69+ * supplied "cid" URI in pj_str form. The "cid:" scheme will be assumed if the
70+ * URL doesn't start with it. Enclosing angle brackets will also be handled
71+ * correctly if they exist.
72+ *
73+ * @see RFC2392 Content-ID and Message-ID Uniform Resource Locators
74+ *
75+ * @param pool Memory pool to use for temp space.
76+ * @param mp The multipart body.
77+ * @param cid The "cid" URI to search for in pj_str form.
78+ *
79+ * @return The first part which has a Content-ID header matching the
80+ * specified "cid" URI. or NULL if not found.
81+ */
82+PJ_DECL(pjsip_multipart_part*)
83+pjsip_multipart_find_part_by_cid_str(pj_pool_t *pool,
84+ const pjsip_msg_body *mp,
85+ pj_str_t *cid);
86+
87+/**
88+ * Find a body inside multipart bodies which has a Content-ID value matching the
89+ * supplied "cid" URI.
90+ *
91+ * @see RFC2392 Content-ID and Message-ID Uniform Resource Locators
92+ *
93+ * @param pool Memory pool to use for temp space.
94+ * @param mp The multipart body.
95+ * @param cid The "cid" URI to search for.
96+ *
97+ * @return The first part which had a Content-ID header matching the
98+ * specified "cid" URI. or NULL if not found.
99+ */
100+PJ_DECL(pjsip_multipart_part*)
101+pjsip_multipart_find_part_by_cid_uri(pj_pool_t *pool,
102+ const pjsip_msg_body *mp,
103+ pjsip_other_uri *cid_uri);
104+
105+/**
106 * Parse multipart message.
107 *
108 * @param pool Memory pool.
109--- a/pjsip/src/pjsip/sip_multipart.c
110+++ b/pjsip/src/pjsip/sip_multipart.c
111@@ -19,6 +19,7 @@
112 #include <pjsip/sip_multipart.h>
113 #include <pjsip/sip_parser.h>
114 #include <pjlib-util/scanner.h>
115+#include <pjlib-util/string.h>
116 #include <pj/assert.h>
117 #include <pj/ctype.h>
118 #include <pj/errno.h>
119@@ -416,6 +417,220 @@ pjsip_multipart_find_part( const pjsip_m
120 return NULL;
121 }
122
123+/*
124+ * Find a body inside multipart bodies which has the header and value.
125+ */
126+PJ_DEF(pjsip_multipart_part*)
127+pjsip_multipart_find_part_by_header_str(pj_pool_t *pool,
128+ const pjsip_msg_body *mp,
129+ const pj_str_t *hdr_name,
130+ const pj_str_t *hdr_value,
131+ const pjsip_multipart_part *start)
132+{
133+ struct multipart_data *m_data;
134+ pjsip_multipart_part *part;
135+ pjsip_hdr *found_hdr;
136+ pj_str_t found_hdr_str;
137+ pj_str_t found_hdr_value;
138+ pj_size_t expected_hdr_slen;
139+ pj_size_t buf_size;
140+ int hdr_name_len;
141+#define REASONABLE_PADDING 32
142+#define SEPARATOR_LEN 2
143+ /* Must specify mandatory params */
144+ PJ_ASSERT_RETURN(mp && hdr_name && hdr_value, NULL);
145+
146+ /* mp must really point to an actual multipart msg body */
147+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
148+
149+ /*
150+ * We'll need to "print" each header we find to test it but
151+ * allocating a buffer of PJSIP_MAX_URL_SIZE is overkill.
152+ * Instead, we'll allocate one large enough to hold the search
153+ * header name, the ": " separator, the search hdr value, and
154+ * the NULL terminator. If we can't print the found header
155+ * into that buffer then it can't be a match.
156+ *
157+ * Some header print functions such as generic_int require enough
158+ * space to print the maximum possible header length so we'll
159+ * add a reasonable amount to the print buffer size.
160+ */
161+ expected_hdr_slen = hdr_name->slen + SEPARATOR_LEN + hdr_value->slen;
162+ buf_size = expected_hdr_slen + REASONABLE_PADDING;
163+ found_hdr_str.ptr = pj_pool_alloc(pool, buf_size);
164+ found_hdr_str.slen = 0;
165+ hdr_name_len = hdr_name->slen + SEPARATOR_LEN;
166+
167+ m_data = (struct multipart_data*)mp->data;
168+
169+ if (start)
170+ part = start->next;
171+ else
172+ part = m_data->part_head.next;
173+
174+ while (part != &m_data->part_head) {
175+ found_hdr = NULL;
176+ while ((found_hdr = pjsip_hdr_find_by_name(&part->hdr, hdr_name,
177+ (found_hdr ? found_hdr->next : NULL))) != NULL) {
178+
179+ found_hdr_str.slen = pjsip_hdr_print_on((void*) found_hdr, found_hdr_str.ptr, buf_size);
180+ /*
181+ * If the buffer was too small (slen = -1) or the result wasn't
182+ * the same length as the search header, it can't be a match.
183+ */
184+ if (found_hdr_str.slen != expected_hdr_slen) {
185+ continue;
186+ }
187+ /*
188+ * Set the value overlay to start at the found header value...
189+ */
190+ found_hdr_value.ptr = found_hdr_str.ptr + hdr_name_len;
191+ found_hdr_value.slen = found_hdr_str.slen - hdr_name_len;
192+ /* ...and compare it to the supplied header value. */
193+ if (pj_strcmp(hdr_value, &found_hdr_value) == 0) {
194+ return part;
195+ }
196+ }
197+ part = part->next;
198+ }
199+ return NULL;
200+#undef SEPARATOR_LEN
201+#undef REASONABLE_PADDING
202+}
203+
204+PJ_DEF(pjsip_multipart_part*)
205+pjsip_multipart_find_part_by_header(pj_pool_t *pool,
206+ const pjsip_msg_body *mp,
207+ void *search_for,
208+ const pjsip_multipart_part *start)
209+{
210+ struct multipart_data *m_data;
211+ pjsip_hdr *search_hdr = search_for;
212+ pj_str_t search_buf;
213+
214+ /* Must specify mandatory params */
215+ PJ_ASSERT_RETURN(mp && search_hdr, NULL);
216+
217+ /* mp must really point to an actual multipart msg body */
218+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
219+
220+ /*
221+ * Unfortunately, there isn't enough information to determine
222+ * the maximum printed size of search_hdr at this point so we
223+ * have to allocate a reasonable max.
224+ */
225+ search_buf.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
226+ search_buf.slen = pjsip_hdr_print_on(search_hdr, search_buf.ptr, PJSIP_MAX_URL_SIZE - 1);
227+ if (search_buf.slen <= 0) {
228+ return NULL;
229+ }
230+ /*
231+ * Set the header value to start after the header name plus the ":", then
232+ * strip leading and trailing whitespace.
233+ */
234+ search_buf.ptr += (search_hdr->name.slen + 1);
235+ search_buf.slen -= (search_hdr->name.slen + 1);
236+ pj_strtrim(&search_buf);
237+
238+ return pjsip_multipart_find_part_by_header_str(pool, mp, &search_hdr->name, &search_buf, start);
239+}
240+
241+/*
242+ * Convert a Content-ID URI to it's corresponding header value.
243+ * RFC2392 says...
244+ * A "cid" URL is converted to the corresponding Content-ID message
245+ * header by removing the "cid:" prefix, converting the % encoded
246+ * character(s) to their equivalent US-ASCII characters, and enclosing
247+ * the remaining parts with an angle bracket pair, "<" and ">".
248+ *
249+ * This implementation will accept URIs with or without the "cid:"
250+ * scheme and optional angle brackets.
251+ */
252+static pj_str_t cid_uri_to_hdr_value(pj_pool_t *pool, pj_str_t *cid_uri)
253+{
254+ pj_size_t cid_len = pj_strlen(cid_uri);
255+ pj_size_t alloc_len = cid_len + 2 /* for the leading and trailing angle brackets */;
256+ pj_str_t uri_overlay;
257+ pj_str_t cid_hdr;
258+ pj_str_t hdr_overlay;
259+
260+ pj_strassign(&uri_overlay, cid_uri);
261+ /* If the URI is already enclosed in angle brackets, remove them. */
262+ if (uri_overlay.ptr[0] == '<') {
263+ uri_overlay.ptr++;
264+ uri_overlay.slen -= 2;
265+ }
266+ /* If the URI starts with the "cid:" scheme, skip over it. */
267+ if (pj_strncmp2(&uri_overlay, "cid:", 4) == 0) {
268+ uri_overlay.ptr += 4;
269+ uri_overlay.slen -= 4;
270+ }
271+ /* Start building */
272+ cid_hdr.ptr = pj_pool_alloc(pool, alloc_len);
273+ cid_hdr.ptr[0] = '<';
274+ cid_hdr.slen = 1;
275+ hdr_overlay.ptr = cid_hdr.ptr + 1;
276+ hdr_overlay.slen = 0;
277+ pj_strcpy_unescape(&hdr_overlay, &uri_overlay);
278+ cid_hdr.slen += hdr_overlay.slen;
279+ cid_hdr.ptr[cid_hdr.slen] = '>';
280+ cid_hdr.slen++;
281+
282+ return cid_hdr;
283+}
284+
285+PJ_DEF(pjsip_multipart_part*)
286+pjsip_multipart_find_part_by_cid_str(pj_pool_t *pool,
287+ const pjsip_msg_body *mp,
288+ pj_str_t *cid)
289+{
290+ struct multipart_data *m_data;
291+ pjsip_multipart_part *part;
292+ pjsip_generic_string_hdr *found_hdr;
293+ pj_str_t found_hdr_value;
294+ static pj_str_t hdr_name = { "Content-ID", 10};
295+ pj_str_t hdr_value;
296+
297+ PJ_ASSERT_RETURN(pool && mp && cid && (pj_strlen(cid) > 0), NULL);
298+
299+ hdr_value = cid_uri_to_hdr_value(pool, cid);
300+ if (pj_strlen(&hdr_value) == 0) {
301+ return NULL;
302+ }
303+
304+ m_data = (struct multipart_data*)mp->data;
305+ part = m_data->part_head.next;
306+
307+ while (part != &m_data->part_head) {
308+ found_hdr = NULL;
309+ while ((found_hdr = pjsip_hdr_find_by_name(&part->hdr, &hdr_name,
310+ (found_hdr ? found_hdr->next : NULL))) != NULL) {
311+ if (pj_strcmp(&hdr_value, &found_hdr->hvalue) == 0) {
312+ return part;
313+ }
314+ }
315+ part = part->next;
316+ }
317+ return NULL;
318+}
319+
320+PJ_DEF(pjsip_multipart_part*)
321+pjsip_multipart_find_part_by_cid_uri(pj_pool_t *pool,
322+ const pjsip_msg_body *mp,
323+ pjsip_other_uri *cid_uri)
324+{
325+ PJ_ASSERT_RETURN(pool && mp && cid_uri, NULL);
326+
327+ if (pj_strcmp2(&cid_uri->scheme, "cid") != 0) {
328+ return NULL;
329+ }
330+ /*
331+ * We only need to pass the URI content so we
332+ * can do that directly.
333+ */
334+ return pjsip_multipart_find_part_by_cid_str(pool, mp, &cid_uri->content);
335+}
336+
337 /* Parse a multipart part. "pct" is parent content-type */
338 static pjsip_multipart_part *parse_multipart_part(pj_pool_t *pool,
339 char *start,
340@@ -584,6 +799,7 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
341 (int)boundary.slen, boundary.ptr));
342 }
343
344+
345 /* Build the delimiter:
346 * delimiter = "--" boundary
347 */
348@@ -630,6 +846,8 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
349 if (*curptr=='\r') ++curptr;
350 if (*curptr!='\n') {
351 /* Expecting a newline here */
352+ PJ_LOG(2, (THIS_FILE, "Failed to find newline"));
353+
354 return NULL;
355 }
356 ++curptr;
357@@ -645,6 +863,7 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
358 curptr = pj_strstr(&subbody, &delim);
359 if (!curptr) {
360 /* We're really expecting end delimiter to be found. */
361+ PJ_LOG(2, (THIS_FILE, "Failed to find end-delimiter"));
362 return NULL;
363 }
364 }
365@@ -670,9 +889,13 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
366 part = parse_multipart_part(pool, start_body, end_body - start_body,
367 ctype);
368 if (part) {
369+ TRACE_((THIS_FILE, "Adding part"));
370 pjsip_multipart_add_part(pool, body, part);
371+ } else {
372+ PJ_LOG(2, (THIS_FILE, "Failed to add part"));
373 }
374 }
375+ TRACE_((THIS_FILE, "pjsip_multipart_parse finished: %p", body));
376
377 return body;
378 }
379--- a/pjsip/src/test/multipart_test.c
380+++ b/pjsip/src/test/multipart_test.c
381@@ -28,6 +28,7 @@
382 typedef pj_status_t (*verify_ptr)(pj_pool_t*,pjsip_msg_body*);
383
384 static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body);
385+static pj_status_t verify2(pj_pool_t *pool, pjsip_msg_body *body);
386
387 static struct test_t
388 {
389@@ -68,7 +69,41 @@ static struct test_t
390 "This is epilogue, which should be ignored too",
391
392 &verify1
393+ },
394+ {
395+ /* Content-type */
396+ "multipart", "mixed", "12345",
397+
398+ /* Body: */
399+ "This is the prolog, which should be ignored.\r\n"
400+ "--12345\r\n"
401+ "Content-Type: text/plain\r\n"
402+ "Content-ID: <header1@example.org>\r\n"
403+ "Content-ID: <\"header1\"@example.org>\r\n"
404+ "Content-Length: 13\r\n"
405+ "\r\n"
406+ "has header1\r\n"
407+ "--12345 \t\r\n"
408+ "Content-Type: application/pidf+xml\r\n"
409+ "Content-ID: <my header2@example.org>\r\n"
410+ "Content-ID: <my\xffheader2@example.org>\r\n"
411+ "Content-Length: 13\r\n"
412+ "\r\n"
413+ "has header2\r\n"
414+ "--12345\r\n"
415+ "Content-Type: text/plain\r\n"
416+ "Content-ID: <my header3@example.org>\r\n"
417+ "Content-ID: <header1@example.org>\r\n"
418+ "Content-ID: <my header4@example.org>\r\n"
419+ "Content-Length: 13\r\n"
420+ "\r\n"
421+ "has header4\r\n"
422+ "--12345--\r\n"
423+ "This is epilogue, which should be ignored too",
424+
425+ &verify2
426 }
427+
428 };
429
430 static void init_media_type(pjsip_media_type *mt,
431@@ -87,6 +122,192 @@ static void init_media_type(pjsip_media_
432 }
433 }
434
435+static int verify_hdr(pj_pool_t *pool, pjsip_msg_body *multipart_body,
436+ void *hdr, char *part_body)
437+{
438+ pjsip_media_type mt;
439+ pjsip_multipart_part *part;
440+ pj_str_t the_body;
441+
442+
443+ part = pjsip_multipart_find_part_by_header(pool, multipart_body, hdr, NULL);
444+ if (!part) {
445+ return -1;
446+ }
447+
448+ the_body.ptr = (char*)part->body->data;
449+ the_body.slen = part->body->len;
450+
451+ if (pj_strcmp2(&the_body, part_body) != 0) {
452+ return -2;
453+ }
454+
455+ return 0;
456+}
457+
458+static int verify_cid_str(pj_pool_t *pool, pjsip_msg_body *multipart_body,
459+ pj_str_t cid_url, char *part_body)
460+{
461+ pjsip_media_type mt;
462+ pjsip_multipart_part *part;
463+ pj_str_t the_body;
464+
465+ part = pjsip_multipart_find_part_by_cid_str(pool, multipart_body, &cid_url);
466+ if (!part) {
467+ return -3;
468+ }
469+
470+ the_body.ptr = (char*)part->body->data;
471+ the_body.slen = part->body->len;
472+
473+ if (pj_strcmp2(&the_body, part_body) != 0) {
474+ return -4;
475+ }
476+
477+ return 0;
478+}
479+
480+static int verify_cid_uri(pj_pool_t *pool, pjsip_msg_body *multipart_body,
481+ pjsip_other_uri *cid_uri, char *part_body)
482+{
483+ pjsip_media_type mt;
484+ pjsip_multipart_part *part;
485+ pj_str_t the_body;
486+
487+ part = pjsip_multipart_find_part_by_cid_uri(pool, multipart_body, cid_uri);
488+ if (!part) {
489+ return -5;
490+ }
491+
492+ the_body.ptr = (char*)part->body->data;
493+ the_body.slen = part->body->len;
494+
495+ if (pj_strcmp2(&the_body, part_body) != 0) {
496+ return -6;
497+ }
498+
499+ return 0;
500+}
501+
502+static pj_status_t verify2(pj_pool_t *pool, pjsip_msg_body *body)
503+{
504+ int rc = 0;
505+ int rcbase = 300;
506+ pjsip_other_uri *cid_uri;
507+ pjsip_ctype_hdr *ctype_hdr = pjsip_ctype_hdr_create(pool);
508+
509+ ctype_hdr->media.type = pj_str("application");
510+ ctype_hdr->media.subtype = pj_str("pidf+xml");
511+
512+ rc = verify_hdr(pool, body, ctype_hdr, "has header2");
513+ if (rc) {
514+ return (rc - rcbase);
515+ }
516+
517+ rcbase += 10;
518+ rc = verify_cid_str(pool, body, pj_str("cid:header1@example.org"), "has header1");
519+ if (rc) {
520+ return (rc - rcbase);
521+ }
522+
523+ rcbase += 10;
524+ rc = verify_cid_str(pool, body, pj_str("%22header1%22@example.org"), "has header1");
525+ if (rc) {
526+ return (rc - rcbase);
527+ }
528+
529+ cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:%22header1%22@example.org>",
530+ strlen("<cid:%22header1%22@example.org>"), 0));
531+ rcbase += 10;
532+ rc = verify_cid_uri(pool, body, cid_uri, "has header1");
533+ if (rc) {
534+ return (rc - rcbase);
535+ }
536+
537+ rcbase += 10;
538+ rc = verify_cid_str(pool, body, pj_str("<cid:my%20header2@example.org>"), "has header2");
539+ if (rc) {
540+ return (rc - rcbase);
541+ }
542+
543+ rcbase += 10;
544+ rc = verify_cid_str(pool, body, pj_str("cid:my%ffheader2@example.org"), "has header2");
545+ if (rc) {
546+ return (rc - rcbase);
547+ }
548+
549+ cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:my%ffheader2@example.org>",
550+ strlen("<cid:my%ffheader2@example.org>"), 0));
551+ rcbase += 10;
552+ rc = verify_cid_uri(pool, body, cid_uri, "has header2");
553+ if (rc) {
554+ return (rc - rcbase);
555+ }
556+
557+ rcbase += 10;
558+ rc = verify_cid_str(pool, body, pj_str("cid:my%20header3@example.org"), "has header4");
559+ if (rc) {
560+ return (rc - rcbase);
561+ }
562+
563+ rcbase += 10;
564+ rc = verify_cid_str(pool, body, pj_str("<cid:my%20header4@example.org>"), "has header4");
565+ if (rc) {
566+ return (rc - rcbase);
567+ }
568+
569+ cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:my%20header4@example.org>",
570+ strlen("<cid:my%20header4@example.org>"), 0));
571+ rcbase += 10;
572+ rc = verify_cid_uri(pool, body, cid_uri, "has header4");
573+ if (rc) {
574+ return (rc - rcbase);
575+ }
576+
577+ rcbase += 10;
578+ rc = verify_cid_str(pool, body, pj_str("<my%20header3@example.org>"), "has header4");
579+ if (rc) {
580+ return (rc - rcbase);
581+ }
582+
583+ /* These should all fail for malformed or missing URI */
584+ rcbase += 10;
585+ rc = verify_cid_str(pool, body, pj_str("cid:"), "has header4");
586+ if (!rc) {
587+ return (rc - rcbase);
588+ }
589+
590+ rcbase += 10;
591+ rc = verify_cid_str(pool, body, pj_str(""), "has header4");
592+ if (!rc) {
593+ return (rc - rcbase);
594+ }
595+
596+ rcbase += 10;
597+ rc = verify_cid_str(pool, body, pj_str("<>"), "has header4");
598+ if (!rc) {
599+ return (rc - rcbase);
600+ }
601+
602+ rcbase += 10;
603+ rc = verify_cid_str(pool, body, pj_str("<cid>"), "has header4");
604+ if (!rc) {
605+ return (rc - rcbase);
606+ }
607+
608+ /*
609+ * This is going to pass but the ' ' in the uri is un-encoded which is invalid
610+ * so we should never see it.
611+ */
612+ rcbase += 10;
613+ rc = verify_cid_str(pool, body, pj_str("cid:my header3@example.org"), "has header4");
614+ if (rc) {
615+ return (rc - rcbase);
616+ }
617+
618+ return 0;
619+}
620+
621 static int verify_part(pjsip_multipart_part *part,
622 char *h_content_type,
623 char *h_content_subtype,
624@@ -236,8 +457,10 @@ static int parse_test(void)
625
626 pj_strdup2_with_null(pool, &str, p_tests[i].msg);
627 body = pjsip_multipart_parse(pool, str.ptr, str.slen, &ctype, 0);
628- if (!body)
629+ if (!body) {
630+ pj_pool_release(pool);
631 return -100;
632+ }
633
634 if (p_tests[i].verify) {
635 rc = p_tests[i].verify(pool, body);