pjproject: sync patches with asterisk 18.11.2
authorAndre Heider <a.heider@gmail.com>
Thu, 7 Oct 2021 16:31:07 +0000 (18:31 +0200)
committerSebastian Kemper <sebastian_ml@gmx.net>
Mon, 18 Apr 2022 15:55:41 +0000 (17:55 +0200)
pjproject: sync patches with asterisk 18.7.1

Signed-off-by: Andre Heider <a.heider@gmail.com>
(cherry picked from commit 376473b38c0a6f4a8e742a26caafbf484ed1b9c5)

pjproject: sync patches with asterisk 18.11.2

Signed-off-by: Sebastian Kemper <sebastian_ml@gmx.net>
(cherry picked from commit 0c67ab5831bb1509af035ce8f142eabbf14bd82a)

libs/pjproject/Makefile
libs/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch [new file with mode: 0644]
libs/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch [new file with mode: 0644]
libs/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch [new file with mode: 0644]
libs/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch [new file with mode: 0644]
libs/pjproject/patches/0160-Additional-multipart-improvements.patch [new file with mode: 0644]

index 8bba564b507f945ce6a6164349c30ae4c8949c1b..5e7b64590a56774e001c5a25a3eec3dc71e15141 100644 (file)
@@ -11,7 +11,7 @@ include $(TOPDIR)/rules.mk
 
 PKG_NAME:=pjproject
 PKG_VERSION:=2.10
-PKG_RELEASE:=6
+PKG_RELEASE:=7
 
 # download "vX.Y.tar.gz" as "pjproject-vX.Y.tar.gz"
 PKG_SOURCE_URL_FILE:=$(PKG_VERSION).tar.gz
diff --git a/libs/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch b/libs/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch
new file mode 100644 (file)
index 0000000..a3e668c
--- /dev/null
@@ -0,0 +1,27 @@
+From 2ae784030b0d9cf217c3d562af20e4967f19a3dc Mon Sep 17 00:00:00 2001
+From: George Joseph <gjoseph@sangoma.com>
+Date: Tue, 14 Sep 2021 10:47:29 -0600
+Subject: [PATCH] pjmedia_sdp_attr_get_rtpmap: Strip param trailing whitespace
+
+Use pj_scan_get() to parse the param part of rtpmap so
+trailing whitespace is automatically stripped.
+
+Fixes #2827
+---
+ pjmedia/src/pjmedia/sdp.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/pjmedia/src/pjmedia/sdp.c
++++ b/pjmedia/src/pjmedia/sdp.c
+@@ -313,9 +313,9 @@ PJ_DEF(pj_status_t) pjmedia_sdp_attr_get
+       /* Expecting either '/' or EOF */
+       if (*scanner.curptr == '/') {
++          /* Skip the '/' */
+           pj_scan_get_char(&scanner);
+-          rtpmap->param.ptr = scanner.curptr;
+-          rtpmap->param.slen = scanner.end - scanner.curptr;
++          pj_scan_get(&scanner, &cs_token, &rtpmap->param);
+       } else {
+           rtpmap->param.slen = 0;
+       }
diff --git a/libs/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch b/libs/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch
new file mode 100644 (file)
index 0000000..8db8aab
--- /dev/null
@@ -0,0 +1,653 @@
+From 0ed41eb5fd0e4192e1b7dc374f819d17aef3e805 Mon Sep 17 00:00:00 2001
+From: George Joseph <gtjoseph@users.noreply.github.com>
+Date: Tue, 21 Dec 2021 19:32:22 -0700
+Subject: [PATCH] sip_inv:  Additional multipart support (#2919) (#2920)
+
+---
+ pjsip/include/pjsip-ua/sip_inv.h       | 108 ++++++++++-
+ pjsip/src/pjsip-ua/sip_inv.c           | 240 ++++++++++++++++++++-----
+ pjsip/src/test/inv_offer_answer_test.c | 103 ++++++++++-
+ 3 files changed, 394 insertions(+), 57 deletions(-)
+
+--- a/pjsip/include/pjsip-ua/sip_inv.h
++++ b/pjsip/include/pjsip-ua/sip_inv.h
+@@ -451,11 +451,11 @@ struct pjsip_inv_session
+ /**
+- * This structure represents SDP information in a pjsip_rx_data. Application
+- * retrieve this information by calling #pjsip_rdata_get_sdp_info(). This
++ * This structure represents SDP information in a pjsip_(rx|tx)_data. Application
++ * retrieve this information by calling #pjsip_get_sdp_info(). This
+  * mechanism supports multipart message body.
+  */
+-typedef struct pjsip_rdata_sdp_info
++typedef struct pjsip_sdp_info
+ {
+     /**
+      * Pointer and length of the text body in the incoming message. If
+@@ -475,7 +475,15 @@ typedef struct pjsip_rdata_sdp_info
+      */
+     pjmedia_sdp_session *sdp;
+-} pjsip_rdata_sdp_info;
++} pjsip_sdp_info;
++
++/**
++ * For backwards compatibility and completeness,
++ * pjsip_rdata_sdp_info and pjsip_tdata_sdp_info
++ * are typedef'd to pjsip_sdp_info.
++ */
++typedef pjsip_sdp_info pjsip_rdata_sdp_info;
++typedef pjsip_sdp_info pjsip_tdata_sdp_info;
+ /**
+@@ -1046,6 +1054,44 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_bo
+                                          pjsip_msg_body **p_body);
+ /**
++ * This is a utility function to create a multipart body with the
++ * SIP body as the first part.
++ *
++ * @param pool                Pool to allocate memory.
++ * @param sdp         SDP session to be put in the SIP message body.
++ * @param p_body      Pointer to receive SIP message body containing
++ *                    the SDP session.
++ *
++ * @return            PJ_SUCCESS on success.
++ */
++PJ_DECL(pj_status_t) pjsip_create_multipart_sdp_body( pj_pool_t *pool,
++                                           pjmedia_sdp_session *sdp,
++                                           pjsip_msg_body **p_body);
++
++/**
++ * Retrieve SDP information from a message body. Application should
++ * prefer to use this function rather than parsing the SDP manually since
++ * this function supports multipart message body.
++ *
++ * This function will only parse the SDP once, the first time it is called
++ * on the same message. Subsequent call on the same message will just pick
++ * up the already parsed SDP from the message.
++ *
++ * @param pool               Pool to allocate memory.
++ * @param body               The message body.
++ * @param msg_media_type     From the rdata or tdata Content-Type header, if available.
++ *                           If NULL, the content_type from the body will be used.
++ * @param search_media_type  The media type to search for.
++ *                           If NULL, "application/sdp" will be used.
++ *
++ * @return                   The SDP info.
++ */
++PJ_DECL(pjsip_sdp_info*) pjsip_get_sdp_info(pj_pool_t *pool,
++                                         pjsip_msg_body *body,
++                                         pjsip_media_type *msg_media_type,
++                                         const pjsip_media_type *search_media_type);
++
++/**
+  * Retrieve SDP information from an incoming message. Application should
+  * prefer to use this function rather than parsing the SDP manually since
+  * this function supports multipart message body.
+@@ -1061,6 +1107,60 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_bo
+ PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata);
++/**
++ * Retrieve SDP information from an incoming message. Application should
++ * prefer to use this function rather than parsing the SDP manually since
++ * this function supports multipart message body.
++ *
++ * This function will only parse the SDP once, the first time it is called
++ * on the same message. Subsequent call on the same message will just pick
++ * up the already parsed SDP from the message.
++ *
++ * @param rdata               The incoming message.
++ * @param search_media_type   The SDP media type to search for.
++ *                            If NULL, "application/sdp" will be used.
++ *
++ * @return                    The SDP info.
++ */
++PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2(
++                                          pjsip_rx_data *rdata,
++                                          const pjsip_media_type *search_media_type);
++
++/**
++ * Retrieve SDP information from an outgoing message. Application should
++ * prefer to use this function rather than parsing the SDP manually since
++ * this function supports multipart message body.
++ *
++ * This function will only parse the SDP once, the first time it is called
++ * on the same message. Subsequent call on the same message will just pick
++ * up the already parsed SDP from the message.
++ *
++ * @param tdata    The outgoing message.
++ *
++ * @return         The SDP info.
++ */
++PJ_DECL(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata);
++
++/**
++ * Retrieve SDP information from an outgoing message. Application should
++ * prefer to use this function rather than parsing the SDP manually since
++ * this function supports multipart message body.
++ *
++ * This function will only parse the SDP once, the first time it is called
++ * on the same message. Subsequent call on the same message will just pick
++ * up the already parsed SDP from the message.
++ *
++ * @param tdata               The outgoing message.
++ * @param search_media_type   The SDP media type to search for.
++ *                            If NULL, "application/sdp" will be used.
++ *
++ * @return                    The SDP info.
++ */
++PJ_DECL(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info2(
++                                          pjsip_tx_data *tdata,
++                                          const pjsip_media_type *search_media_type);
++
++
+ PJ_END_DECL
+ /**
+--- a/pjsip/src/pjsip-ua/sip_inv.c
++++ b/pjsip/src/pjsip-ua/sip_inv.c
+@@ -118,6 +118,8 @@ static pj_status_t handle_timer_response
+ static pj_bool_t inv_check_secure_dlg(pjsip_inv_session *inv,
+                                     pjsip_event *e);
++static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len);
++
+ static void (*inv_state_handler[])( pjsip_inv_session *inv, pjsip_event *e) = 
+ {
+     &inv_on_state_null,
+@@ -956,66 +958,170 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac
+     return PJ_SUCCESS;
+ }
+-PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
+-{
+-    pjsip_rdata_sdp_info *sdp_info;
+-    pjsip_msg_body *body = rdata->msg_info.msg->body;
+-    pjsip_ctype_hdr *ctype_hdr = rdata->msg_info.ctype;
+-    pjsip_media_type app_sdp;
++PJ_DEF(pjsip_sdp_info*) pjsip_get_sdp_info(pj_pool_t *pool,
++                                           pjsip_msg_body *body,
++                                           pjsip_media_type *msg_media_type,
++                                           const pjsip_media_type *search_media_type)
++{
++    pjsip_sdp_info *sdp_info;
++    pjsip_media_type search_type;
++    pjsip_media_type multipart_mixed;
++    pjsip_media_type multipart_alternative;
++    pjsip_media_type *msg_type;
++    pj_status_t status;
+-    sdp_info = (pjsip_rdata_sdp_info*)
+-             rdata->endpt_info.mod_data[mod_inv.mod.id];
+-    if (sdp_info)
+-      return sdp_info;
++    sdp_info = PJ_POOL_ZALLOC_T(pool,
++                                pjsip_sdp_info);
+-    sdp_info = PJ_POOL_ZALLOC_T(rdata->tp_info.pool,
+-                              pjsip_rdata_sdp_info);
+     PJ_ASSERT_RETURN(mod_inv.mod.id >= 0, sdp_info);
+-    rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
+-    pjsip_media_type_init2(&app_sdp, "application", "sdp");
++    if (!body) {
++        return sdp_info;
++    }
+-    if (body && ctype_hdr &&
+-      pj_stricmp(&ctype_hdr->media.type, &app_sdp.type)==0 &&
+-      pj_stricmp(&ctype_hdr->media.subtype, &app_sdp.subtype)==0)
++    if (msg_media_type) {
++      msg_type = msg_media_type;
++    } else {
++      if (body->content_type.type.slen == 0) {
++          return sdp_info;
++      }
++      msg_type = &body->content_type;
++    }
++
++    if (!search_media_type) {
++        pjsip_media_type_init2(&search_type, "application", "sdp");
++    } else {
++        pj_memcpy(&search_type, search_media_type, sizeof(search_type));
++    }
++
++    pjsip_media_type_init2(&multipart_mixed, "multipart", "mixed");
++    pjsip_media_type_init2(&multipart_alternative, "multipart", "alternative");
++
++    if (pjsip_media_type_cmp(msg_type, &search_type, PJ_FALSE) == 0)
+     {
+-      sdp_info->body.ptr = (char*)body->data;
+-      sdp_info->body.slen = body->len;
+-    } else if  (body && ctype_hdr &&
+-              pj_stricmp2(&ctype_hdr->media.type, "multipart")==0 &&
+-              (pj_stricmp2(&ctype_hdr->media.subtype, "mixed")==0 ||
+-               pj_stricmp2(&ctype_hdr->media.subtype, "alternative")==0))
++      /*
++       * If the print_body function is print_sdp, we know that
++       * body->data is a pjmedia_sdp_session object and came from
++       * a tx_data.  If not, it's the text representation of the
++       * sdp from an rx_data.
++       */
++        if (body->print_body == print_sdp) {
++            sdp_info->sdp = body->data;
++        } else {
++            sdp_info->body.ptr = (char*)body->data;
++            sdp_info->body.slen = body->len;
++        }
++    } else if (pjsip_media_type_cmp(&multipart_mixed, msg_type, PJ_FALSE) == 0 ||
++      pjsip_media_type_cmp(&multipart_alternative, msg_type, PJ_FALSE) == 0)
+     {
+-      pjsip_multipart_part *part;
++        pjsip_multipart_part *part;
++        part = pjsip_multipart_find_part(body, &search_type, NULL);
++        if (part) {
++            if (part->body->print_body == print_sdp) {
++                sdp_info->sdp = part->body->data;
++            } else {
++                sdp_info->body.ptr = (char*)part->body->data;
++                sdp_info->body.slen = part->body->len;
++            }
++        }
++    }
+-      part = pjsip_multipart_find_part(body, &app_sdp, NULL);
+-      if (part) {
+-          sdp_info->body.ptr = (char*)part->body->data;
+-          sdp_info->body.slen = part->body->len;
+-      }
++    /*
++     * If the body was already a pjmedia_sdp_session, we can just
++     * return it.  If not and there wasn't a text representation
++     * of the sdp either, we can also just return.
++     */
++    if (sdp_info->sdp || !sdp_info->body.ptr) {
++      return sdp_info;
+     }
+-    if (sdp_info->body.ptr) {
+-      pj_status_t status;
+-      status = pjmedia_sdp_parse(rdata->tp_info.pool,
+-                                 sdp_info->body.ptr,
+-                                 sdp_info->body.slen,
+-                                 &sdp_info->sdp);
+-      if (status == PJ_SUCCESS)
+-          status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE);
++    /*
++     * If the body was the text representation of teh SDP, we need
++     * to parse it to create a pjmedia_sdp_session object.
++     */
++    status = pjmedia_sdp_parse(pool,
++                              sdp_info->body.ptr,
++                              sdp_info->body.slen,
++                              &sdp_info->sdp);
++    if (status == PJ_SUCCESS)
++      status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE);
+-      if (status != PJ_SUCCESS) {
+-          sdp_info->sdp = NULL;
+-          PJ_PERROR(1,(THIS_FILE, status,
+-                       "Error parsing/validating SDP body"));
+-      }
++    if (status != PJ_SUCCESS) {
++      sdp_info->sdp = NULL;
++      PJ_PERROR(1, (THIS_FILE, status,
++          "Error parsing/validating SDP body"));
++    }
++
++    sdp_info->sdp_err = status;
++
++    return sdp_info;
++}
++
++PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2(
++                                            pjsip_rx_data *rdata,
++                                            const pjsip_media_type *search_media_type)
++{
++    pjsip_media_type *msg_media_type = NULL;
++    pjsip_rdata_sdp_info *sdp_info;
+-      sdp_info->sdp_err = status;
++    if (rdata->endpt_info.mod_data[mod_inv.mod.id]) {
++      return (pjsip_rdata_sdp_info *)rdata->endpt_info.mod_data[mod_inv.mod.id];
++    }
++
++    /*
++     * rdata should have a Content-Type header at this point but we'll
++     * make sure.
++     */
++    if (rdata->msg_info.ctype) {
++      msg_media_type = &rdata->msg_info.ctype->media;
+     }
++    sdp_info = pjsip_get_sdp_info(rdata->tp_info.pool,
++                                 rdata->msg_info.msg->body,
++                                 msg_media_type,
++                                 search_media_type);
++    rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
+     return sdp_info;
+ }
++PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
++{
++    return pjsip_rdata_get_sdp_info2(rdata, NULL);
++}
++
++PJ_DEF(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info2(
++                                            pjsip_tx_data *tdata,
++                                            const pjsip_media_type *search_media_type)
++{
++    pjsip_ctype_hdr *ctype_hdr = NULL;
++    pjsip_media_type *msg_media_type = NULL;
++    pjsip_tdata_sdp_info *sdp_info;
++
++    if (tdata->mod_data[mod_inv.mod.id]) {
++      return (pjsip_tdata_sdp_info *)tdata->mod_data[mod_inv.mod.id];
++    }
++    /*
++     * tdata won't usually have a Content-Type header at this point
++     * but we'll check just the same,
++     */
++    ctype_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTENT_TYPE, NULL);
++    if (ctype_hdr) {
++      msg_media_type = &ctype_hdr->media;
++    }
++
++    sdp_info = pjsip_get_sdp_info(tdata->pool,
++                                 tdata->msg->body,
++                                 msg_media_type,
++                                 search_media_type);
++    tdata->mod_data[mod_inv.mod.id] = sdp_info;
++
++    return sdp_info;
++}
++
++PJ_DEF(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata)
++{
++    return pjsip_tdata_get_sdp_info2(tdata, NULL);
++}
+ /*
+  * Verify incoming INVITE request.
+@@ -1740,13 +1846,55 @@ PJ_DEF(pj_status_t) pjsip_create_sdp_bod
+     return PJ_SUCCESS;
+ }
++static pjsip_multipart_part* create_sdp_part(pj_pool_t *pool, pjmedia_sdp_session *sdp)
++{
++    pjsip_multipart_part *sdp_part;
++    pjsip_media_type media_type;
++
++    pjsip_media_type_init2(&media_type, "application", "sdp");
++
++    sdp_part = pjsip_multipart_create_part(pool);
++    PJ_ASSERT_RETURN(sdp_part != NULL, NULL);
++
++    sdp_part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
++    PJ_ASSERT_RETURN(sdp_part->body != NULL, NULL);
++
++    pjsip_media_type_cp(pool, &sdp_part->body->content_type, &media_type);
++
++    sdp_part->body->data = sdp;
++    sdp_part->body->clone_data = clone_sdp;
++    sdp_part->body->print_body = print_sdp;
++
++    return sdp_part;
++}
++
++PJ_DEF(pj_status_t) pjsip_create_multipart_sdp_body(pj_pool_t *pool,
++                                                   pjmedia_sdp_session *sdp,
++                                                   pjsip_msg_body **p_body)
++{
++    pjsip_media_type media_type;
++    pjsip_msg_body *multipart;
++    pjsip_multipart_part *sdp_part;
++
++    pjsip_media_type_init2(&media_type, "multipart", "mixed");
++    multipart = pjsip_multipart_create(pool, &media_type, NULL);
++    PJ_ASSERT_RETURN(multipart != NULL, PJ_ENOMEM);
++
++    sdp_part = create_sdp_part(pool, sdp);
++    PJ_ASSERT_RETURN(sdp_part != NULL, PJ_ENOMEM);
++    pjsip_multipart_add_part(pool, multipart, sdp_part);
++    *p_body = multipart;
++
++    return PJ_SUCCESS;
++}
++
+ static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
+                                      const pjmedia_sdp_session *c_sdp)
+ {
+     pjsip_msg_body *body;
+     pj_status_t status;
+-    status = pjsip_create_sdp_body(pool, 
++    status = pjsip_create_sdp_body(pool,
+                                  pjmedia_sdp_session_clone(pool, c_sdp),
+                                  &body);
+@@ -2069,6 +2217,7 @@ static pj_status_t inv_check_sdp_in_inco
+              )
+          )
+       {
++          pjsip_sdp_info *tdata_sdp_info;
+           const pjmedia_sdp_session *reoffer_sdp = NULL;
+           PJ_LOG(4,(inv->obj_name, "Received %s response "
+@@ -2077,14 +2226,15 @@ static pj_status_t inv_check_sdp_in_inco
+                     (st_code/10==18? "early" : "final" )));
+           /* Retrieve original SDP offer from INVITE request */
+-          reoffer_sdp = (const pjmedia_sdp_session*) 
+-                        tsx->last_tx->msg->body->data;
++          tdata_sdp_info = pjsip_tdata_get_sdp_info(tsx->last_tx);
++          reoffer_sdp = tdata_sdp_info->sdp;
+           /* Feed the original offer to negotiator */
+           status = pjmedia_sdp_neg_modify_local_offer2(inv->pool_prov, 
+                                                        inv->neg,
+                                                          inv->sdp_neg_flags,
+                                                        reoffer_sdp);
++
+           if (status != PJ_SUCCESS) {
+               PJ_LOG(1,(inv->obj_name, "Error updating local offer for "
+                         "forked 2xx/18x response (err=%d)", status));
+--- a/pjsip/src/test/inv_offer_answer_test.c
++++ b/pjsip/src/test/inv_offer_answer_test.c
+@@ -137,6 +137,7 @@ typedef struct inv_test_param_t
+     pj_bool_t need_established;
+     unsigned  count;
+     oa_t      oa[4];
++    pj_bool_t multipart_body;
+ } inv_test_param_t;
+ typedef struct inv_test_t
+@@ -257,6 +258,17 @@ static void on_media_update(pjsip_inv_se
+           }
+       }
++      /* Special handling for standard offer/answer */
++      if (inv_test.param.count == 1 &&
++          inv_test.param.oa[0] == OFFERER_UAC &&
++          inv_test.param.need_established)
++      {
++          jobs[job_cnt].type = ESTABLISH_CALL;
++          jobs[job_cnt].who = PJSIP_ROLE_UAS;
++          job_cnt++;
++          TRACE_((THIS_FILE, "      C+++"));
++      }
++
+       pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs));
+     }
+ }
+@@ -333,6 +345,15 @@ static pj_bool_t on_rx_request(pjsip_rx_
+                                         NULL, &tdata);
+       pj_assert(status == PJ_SUCCESS);
++      /* Use multipart body, if configured */
++      if (sdp && inv_test.param.multipart_body) {
++           status = pjsip_create_multipart_sdp_body(
++                              tdata->pool,
++                              pjmedia_sdp_session_clone(tdata->pool, sdp),
++                              &tdata->msg->body);
++      }
++      pj_assert(status == PJ_SUCCESS);
++
+       status = pjsip_inv_send_msg(inv_test.uas, tdata);
+       pj_assert(status == PJ_SUCCESS);
+@@ -426,6 +447,7 @@ static int perform_test(inv_test_param_t
+       sdp = NULL;
+     status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac);
++    //inv_test.uac->create_multipart = param->multipart_body;
+     PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20);
+     TRACE_((THIS_FILE, "    Sending INVITE %s offer", (sdp ? "with" : "without")));
+@@ -436,8 +458,17 @@ static int perform_test(inv_test_param_t
+     status = pjsip_inv_invite(inv_test.uac, &tdata);
+     PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
++    /* Use multipart body, if configured */
++    if (sdp && param->multipart_body) {
++       status = pjsip_create_multipart_sdp_body(
++                          tdata->pool,
++                          pjmedia_sdp_session_clone(tdata->pool, sdp),
++                          &tdata->msg->body);
++    }
++    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -40);
++
+     status = pjsip_inv_send_msg(inv_test.uac, tdata);
+-    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
++    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -50);
+     /*
+      * Wait until test completes
+@@ -525,13 +556,14 @@ static inv_test_param_t test_params[] =
+     200/INVITE (answer)       <--
+     ACK               -->
+  */
+-#if 0
++#if 1
+     {
+       "Standard INVITE with offer",
+       0,
+       PJ_TRUE,
+       1,
+-      { OFFERER_UAC }
++      { OFFERER_UAC },
++      PJ_FALSE
+     },
+     {
+@@ -539,7 +571,25 @@ static inv_test_param_t test_params[] =
+       PJSIP_INV_REQUIRE_100REL,
+       PJ_TRUE,
+       1,
+-      { OFFERER_UAC }
++      { OFFERER_UAC },
++      PJ_FALSE
++    },
++    {
++      "Standard INVITE with offer, with Multipart",
++      0,
++      PJ_TRUE,
++      1,
++      { OFFERER_UAC },
++      PJ_TRUE
++    },
++
++    {
++      "Standard INVITE with offer, with 100rel, with Multipart",
++      PJSIP_INV_REQUIRE_100REL,
++      PJ_TRUE,
++      1,
++      { OFFERER_UAC },
++      PJ_TRUE
+     },
+ #endif
+@@ -555,7 +605,8 @@ static inv_test_param_t test_params[] =
+       0,
+       PJ_TRUE,
+       1,
+-      { OFFERER_UAS }
++      { OFFERER_UAS },
++      PJ_FALSE
+     },
+     {
+@@ -563,7 +614,25 @@ static inv_test_param_t test_params[] =
+       PJSIP_INV_REQUIRE_100REL,
+       PJ_TRUE,
+       1,
+-      { OFFERER_UAS }
++      { OFFERER_UAS },
++      PJ_FALSE
++    },
++    {
++      "INVITE with no offer, with Multipart",
++      0,
++      PJ_TRUE,
++      1,
++      { OFFERER_UAS },
++      PJ_TRUE
++    },
++
++    {
++      "INVITE with no offer, with 100rel, with Multipart",
++      PJSIP_INV_REQUIRE_100REL,
++      PJ_TRUE,
++      1,
++      { OFFERER_UAS },
++      PJ_TRUE
+     },
+ #endif
+@@ -584,14 +653,24 @@ static inv_test_param_t test_params[] =
+       0,
+       PJ_TRUE,
+       2,
+-      { OFFERER_UAC, OFFERER_UAC }
++      { OFFERER_UAC, OFFERER_UAC },
++      PJ_FALSE
++    },
++    {
++      "INVITE and UPDATE by UAC, with Multipart",
++      0,
++      PJ_TRUE,
++      2,
++      { OFFERER_UAC, OFFERER_UAC },
++      PJ_TRUE
+     },
+     {
+       "INVITE and UPDATE by UAC, with 100rel",
+       PJSIP_INV_REQUIRE_100REL,
+       PJ_TRUE,
+       2,
+-      { OFFERER_UAC, OFFERER_UAC }
++      { OFFERER_UAC, OFFERER_UAC },
++      PJ_FALSE
+     },
+ #endif
+@@ -617,6 +696,14 @@ static inv_test_param_t test_params[] =
+       4,
+       { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS }
+     },
++    {
++      "INVITE and many UPDATE by UAC and UAS, with Multipart",
++      0,
++      PJ_TRUE,
++      4,
++      { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS },
++      PJ_TRUE
++    },
+ };
diff --git a/libs/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch b/libs/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch
new file mode 100644 (file)
index 0000000..deb9d8c
--- /dev/null
@@ -0,0 +1,116 @@
+From 3faf1d2b4da553bbaee04f9a13a5d084b381e5fb Mon Sep 17 00:00:00 2001
+From: sauwming <ming@teluu.com>
+Date: Tue, 4 Jan 2022 15:28:49 +0800
+Subject: [PATCH] Fix incorrect unescaping of tokens during parsing (#2933)
+
+---
+ pjsip/src/pjsip/sip_parser.c | 29 +++++++++++++++++++++++++----
+ pjsip/src/test/msg_test.c    |  6 +++---
+ 2 files changed, 28 insertions(+), 7 deletions(-)
+
+--- a/pjsip/src/pjsip/sip_parser.c
++++ b/pjsip/src/pjsip/sip_parser.c
+@@ -378,17 +378,23 @@ static pj_status_t init_parser()
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+     pj_cis_add_str( &pconst.pjsip_TOKEN_SPEC, TOKEN);
++    /* Token is allowed to have '%' so we do not need this. */
++    /*
+     status = pj_cis_dup(&pconst.pjsip_TOKEN_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC);
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+     pj_cis_del_str(&pconst.pjsip_TOKEN_SPEC_ESC, "%");
++    */
+     status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC, &pconst.pjsip_TOKEN_SPEC);
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+     pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC, "[:]");
++    /* Token is allowed to have '%' */
++    /*
+     status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC_ESC);
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+     pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC_ESC, "[:]");
++    */
+     status = pj_cis_dup(&pconst.pjsip_HOST_SPEC, &pconst.pjsip_ALNUM_SPEC);
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+@@ -1210,7 +1216,11 @@ static void parse_param_imp( pj_scanner
+                            unsigned option)
+ {
+     /* pname */
+-    parser_get_and_unescape(scanner, pool, spec, esc_spec, pname);
++    if (!esc_spec) {
++      pj_scan_get(scanner, spec, pname);
++    } else {
++      parser_get_and_unescape(scanner, pool, spec, esc_spec, pname);
++    }
+     /* init pvalue */
+     pvalue->ptr = NULL;
+@@ -1240,7 +1250,12 @@ static void parse_param_imp( pj_scanner
+               // pj_scan_get_until_ch(scanner, ']', pvalue);
+               // pj_scan_get_char(scanner);
+           } else if(pj_cis_match(spec, *scanner->curptr)) {
+-              parser_get_and_unescape(scanner, pool, spec, esc_spec, pvalue);
++              if (!esc_spec) {
++                  pj_scan_get(scanner, spec, pvalue);
++              } else {
++                  parser_get_and_unescape(scanner, pool, spec, esc_spec,
++                                          pvalue);
++              }
+           }
+       }
+     }
+@@ -1252,7 +1267,10 @@ PJ_DEF(void) pjsip_parse_param_imp(pj_sc
+                                  unsigned option)
+ {
+     parse_param_imp(scanner, pool, pname, pvalue, &pconst.pjsip_TOKEN_SPEC,
+-                  &pconst.pjsip_TOKEN_SPEC_ESC, option);
++                  // Token does not need to be unescaped.
++                  // Refer to PR #2933.
++                  // &pconst.pjsip_TOKEN_SPEC_ESC,
++                  NULL, option);
+ }
+@@ -2168,7 +2186,10 @@ static void int_parse_via_param( pjsip_v
+       pj_scan_get_char(scanner);
+       parse_param_imp(scanner, pool, &pname, &pvalue,
+                       &pconst.pjsip_VIA_PARAM_SPEC,
+-                      &pconst.pjsip_VIA_PARAM_SPEC_ESC,
++                      // Token does not need to be unescaped.
++                      // Refer to PR #2933.
++                      // &pconst.pjsip_VIA_PARAM_SPEC_ESC,
++                      NULL,
+                       0);
+       if (!parser_stricmp(pname, pconst.pjsip_BRANCH_STR) && pvalue.slen) {
+--- a/pjsip/src/test/msg_test.c
++++ b/pjsip/src/test/msg_test.c
+@@ -953,7 +953,7 @@ static int hdr_test_subject_utf(pjsip_hd
+ #define GENERIC_PARAM      "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
+-#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab:cd;p3"
++#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
+ #define PARAM_CHAR         "][/:&+$"
+ #define SIMPLE_ADDR_SPEC     "sip:host"
+ #define ADDR_SPEC          SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\""
+@@ -1401,7 +1401,7 @@ static int generic_param_test(pjsip_para
+     param = param->next;
+     if (pj_strcmp2(&param->name, "p2"))
+       return -956;
+-    if (pj_strcmp2(&param->value, "ab:cd"))
++    if (pj_strcmp2(&param->value, "ab%3acd"))
+       return -957;
+     param = param->next;
+@@ -1621,7 +1621,7 @@ static int hdr_test_content_type(pjsip_h
+     prm = prm->next;
+     if (prm == &hdr->media.param) return -1960;
+     if (pj_strcmp2(&prm->name, "p2")) return -1961;
+-    if (pj_strcmp2(&prm->value, "ab:cd")) return -1962;
++    if (pj_strcmp2(&prm->value, "ab%3acd")) return -1962;
+     prm = prm->next;
+     if (prm == &hdr->media.param) return -1970;
diff --git a/libs/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch b/libs/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch
new file mode 100644 (file)
index 0000000..5979a3b
--- /dev/null
@@ -0,0 +1,169 @@
+From 7e3dfd8a15fd0f98dbf0e04d2d7a5bded90ee401 Mon Sep 17 00:00:00 2001
+From: George Joseph <gjoseph@sangoma.com>
+Date: Tue, 11 Jan 2022 09:27:23 -0700
+Subject: [PATCH] Create generic pjsip_hdr_find functions
+
+pjsip_msg_find_hdr(), pjsip_msg_find_hdr_by_name(), and
+pjsip_msg_find_hdr_by_names() require a pjsip_msg to be passed in
+so if you need to search a header list that's not in a pjsip_msg,
+you have to do it yourself.  This commit adds generic versions of
+those 3 functions that take in the actual header list head instead
+of a pjsip_msg so if you need to search a list of headers in
+something like a pjsip_multipart_part, you can do so easily.
+---
+ pjsip/include/pjsip/sip_msg.h | 53 +++++++++++++++++++++++++++++++++++
+ pjsip/src/pjsip/sip_msg.c     | 51 +++++++++++++++++++++++----------
+ 2 files changed, 89 insertions(+), 15 deletions(-)
+
+--- a/pjsip/include/pjsip/sip_msg.h
++++ b/pjsip/include/pjsip/sip_msg.h
+@@ -363,6 +363,59 @@ PJ_DECL(void*) pjsip_hdr_shallow_clone(
+ PJ_DECL(int) pjsip_hdr_print_on( void *hdr, char *buf, pj_size_t len);
+ /**
++ * Find a header in a header list by the header type.
++ *
++ * @param hdr_list  The "head" of the header list.
++ * @param type      The header type to find.
++ * @param start     The first header field where the search should begin.
++ *                  If NULL is specified, then the search will begin from the
++ *                  first header, otherwise the search will begin at the
++ *                  specified header.
++ *
++ * @return          The header field, or NULL if no header with the specified
++ *                  type is found.
++ */
++PJ_DECL(void*)  pjsip_hdr_find( const void *hdr_list,
++                              pjsip_hdr_e type,
++                              const void *start);
++
++/**
++ * Find a header in a header list by its name.
++ *
++ * @param hdr_list  The "head" of the header list.
++ * @param name      The header name to find.
++ * @param start     The first header field where the search should begin.
++ *                  If NULL is specified, then the search will begin from the
++ *                  first header, otherwise the search will begin at the
++ *                  specified header.
++ *
++ * @return          The header field, or NULL if no header with the specified
++ *                  type is found.
++ */
++PJ_DECL(void*)  pjsip_hdr_find_by_name( const void *hdr_list,
++                                      const pj_str_t *name,
++                                      const void *start);
++
++/**
++ * Find a header in a header list by its name and short name version.
++ *
++ * @param hdr_list  The "head" of the header list.
++ * @param name      The header name to find.
++ * @param sname     The short name version of the header name.
++ * @param start     The first header field where the search should begin.
++ *                  If NULL is specified, then the search will begin from the
++ *                  first header, otherwise the search will begin at the
++ *                  specified header.
++ *
++ * @return        The header field, or NULL if no header with the specified
++ *                type is found.
++ */
++PJ_DECL(void*)  pjsip_hdr_find_by_names( const void *hdr_list,
++                                       const pj_str_t *name,
++                                       const pj_str_t *sname,
++                                       const void *start);
++
++/**
+  * @}
+  */
+--- a/pjsip/src/pjsip/sip_msg.c
++++ b/pjsip/src/pjsip/sip_msg.c
+@@ -334,13 +334,13 @@ PJ_DEF(pjsip_msg*) pjsip_msg_clone( pj_p
+     return dst;
+ }
+-PJ_DEF(void*)  pjsip_msg_find_hdr( const pjsip_msg *msg, 
+-                                 pjsip_hdr_e hdr_type, const void *start)
++PJ_DEF(void*)  pjsip_hdr_find( const void *hdr_list,
++                             pjsip_hdr_e hdr_type, const void *start)
+ {
+-    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=&msg->hdr;
++    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=hdr_list;
+     if (hdr == NULL) {
+-      hdr = msg->hdr.next;
++      hdr = end->next;
+     }
+     for (; hdr!=end; hdr = hdr->next) {
+       if (hdr->type == hdr_type)
+@@ -349,14 +349,14 @@ PJ_DEF(void*)  pjsip_msg_find_hdr( const
+     return NULL;
+ }
+-PJ_DEF(void*)  pjsip_msg_find_hdr_by_name( const pjsip_msg *msg, 
+-                                         const pj_str_t *name, 
+-                                         const void *start)
++PJ_DEF(void*)  pjsip_hdr_find_by_name( const void *hdr_list,
++                                     const pj_str_t *name,
++                                     const void *start)
+ {
+-    const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
++    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=hdr_list;
+     if (hdr == NULL) {
+-      hdr = msg->hdr.next;
++      hdr = end->next;
+     }
+     for (; hdr!=end; hdr = hdr->next) {
+       if (pj_stricmp(&hdr->name, name) == 0)
+@@ -365,15 +365,15 @@ PJ_DEF(void*)  pjsip_msg_find_hdr_by_nam
+     return NULL;
+ }
+-PJ_DEF(void*)  pjsip_msg_find_hdr_by_names( const pjsip_msg *msg, 
+-                                          const pj_str_t *name, 
+-                                          const pj_str_t *sname,
+-                                          const void *start)
++PJ_DEF(void*)  pjsip_hdr_find_by_names( const void *hdr_list,
++                                      const pj_str_t *name,
++                                      const pj_str_t *sname,
++                                      const void *start)
+ {
+-    const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
++    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=hdr_list;
+     if (hdr == NULL) {
+-      hdr = msg->hdr.next;
++      hdr = end->next;
+     }
+     for (; hdr!=end; hdr = hdr->next) {
+       if (pj_stricmp(&hdr->name, name) == 0)
+@@ -384,6 +384,27 @@ PJ_DEF(void*)  pjsip_msg_find_hdr_by_nam
+     return NULL;
+ }
++PJ_DEF(void*)  pjsip_msg_find_hdr( const pjsip_msg *msg,
++                                 pjsip_hdr_e hdr_type, const void *start)
++{
++    return pjsip_hdr_find(&msg->hdr, hdr_type, start);
++}
++
++PJ_DEF(void*)  pjsip_msg_find_hdr_by_name( const pjsip_msg *msg,
++                                         const pj_str_t *name,
++                                         const void *start)
++{
++    return pjsip_hdr_find_by_name(&msg->hdr, name, start);
++}
++
++PJ_DEF(void*)  pjsip_msg_find_hdr_by_names( const pjsip_msg *msg,
++                                          const pj_str_t *name,
++                                          const pj_str_t *sname,
++                                          const void *start)
++{
++    return pjsip_hdr_find_by_names(&msg->hdr, name, sname, start);
++}
++
+ PJ_DEF(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg, 
+                                        pjsip_hdr_e hdr_type, void *start)
+ {
diff --git a/libs/pjproject/patches/0160-Additional-multipart-improvements.patch b/libs/pjproject/patches/0160-Additional-multipart-improvements.patch
new file mode 100644 (file)
index 0000000..3de67b5
--- /dev/null
@@ -0,0 +1,635 @@
+From b7ecff22e77887626fd8e8608c4dd73bc7b7366f Mon Sep 17 00:00:00 2001
+From: George Joseph <gjoseph@sangoma.com>
+Date: Tue, 18 Jan 2022 06:14:31 -0700
+Subject: [PATCH] Additional multipart improvements
+
+Added the following APIs:
+pjsip_multipart_find_part_by_header()
+pjsip_multipart_find_part_by_header_str()
+pjsip_multipart_find_part_by_cid_str()
+pjsip_multipart_find_part_by_cid_uri()
+---
+ pjsip/include/pjsip/sip_multipart.h |  83 ++++++++++
+ pjsip/src/pjsip/sip_multipart.c     | 223 +++++++++++++++++++++++++++
+ pjsip/src/test/multipart_test.c     | 225 +++++++++++++++++++++++++++-
+ 3 files changed, 530 insertions(+), 1 deletion(-)
+
+--- a/pjsip/include/pjsip/sip_multipart.h
++++ b/pjsip/include/pjsip/sip_multipart.h
+@@ -154,6 +154,89 @@ pjsip_multipart_find_part( const pjsip_m
+                          const pjsip_multipart_part *start);
+ /**
++ * Find a body inside multipart bodies which has a header matching the
++ * supplied one. Most useful for finding a part with a specific Content-ID.
++ *
++ * @param pool                Memory pool to use for temp space.
++ * @param mp          The multipart body.
++ * @param search_hdr  Header to search for.
++ * @param start               If specified, the search will begin at
++ *                    start->next part. Otherwise it will begin at
++ *                    the first part in the multipart bodies.
++ *
++ * @return            The first part which has a header matching the
++ *                    specified one, or NULL if not found.
++ */
++PJ_DECL(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_header(pj_pool_t *pool,
++                                  const pjsip_msg_body *mp,
++                                  void *search_hdr,
++                                  const pjsip_multipart_part *start);
++
++/**
++ * Find a body inside multipart bodies which has a header matching the
++ * supplied name and value. Most useful for finding a part with a specific
++ * Content-ID.
++ *
++ * @param pool                Memory pool to use for temp space.
++ * @param mp          The multipart body.
++ * @param hdr_name    Header name to search for.
++ * @param hdr_value   Header value search for.
++ * @param start               If specified, the search will begin at
++ *                    start->next part. Otherwise it will begin at
++ *                    the first part in the multipart bodies.
++ *
++ * @return            The first part which has a header matching the
++ *                    specified one, or NULL if not found.
++ */
++PJ_DECL(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_header_str(pj_pool_t *pool,
++                                  const pjsip_msg_body *mp,
++                                  const pj_str_t *hdr_name,
++                                  const pj_str_t *hdr_value,
++                                  const pjsip_multipart_part *start);
++
++
++
++/**
++ * Find a body inside multipart bodies which has a Content-ID value matching the
++ * supplied "cid" URI in pj_str form.  The "cid:" scheme will be assumed if the
++ * URL doesn't start with it.  Enclosing angle brackets will also be handled
++ * correctly if they exist.
++ *
++ * @see RFC2392 Content-ID and Message-ID Uniform Resource Locators
++ *
++ * @param pool        Memory pool to use for temp space.
++ * @param mp  The multipart body.
++ * @param cid The "cid" URI to search for in pj_str form.
++ *
++ * @return            The first part which has a Content-ID header matching the
++ *                    specified "cid" URI. or NULL if not found.
++ */
++PJ_DECL(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_cid_str(pj_pool_t *pool,
++                               const pjsip_msg_body *mp,
++                               pj_str_t *cid);
++
++/**
++ * Find a body inside multipart bodies which has a Content-ID value matching the
++ * supplied "cid" URI.
++ *
++ * @see RFC2392 Content-ID and Message-ID Uniform Resource Locators
++ *
++ * @param pool        Memory pool to use for temp space.
++ * @param mp  The multipart body.
++ * @param cid The "cid" URI to search for.
++ *
++ * @return            The first part which had a Content-ID header matching the
++ *                    specified "cid" URI. or NULL if not found.
++ */
++PJ_DECL(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_cid_uri(pj_pool_t *pool,
++                               const pjsip_msg_body *mp,
++                               pjsip_other_uri *cid_uri);
++
++/**
+  * Parse multipart message.
+  *
+  * @param pool                Memory pool.
+--- a/pjsip/src/pjsip/sip_multipart.c
++++ b/pjsip/src/pjsip/sip_multipart.c
+@@ -19,6 +19,7 @@
+ #include <pjsip/sip_multipart.h>
+ #include <pjsip/sip_parser.h>
+ #include <pjlib-util/scanner.h>
++#include <pjlib-util/string.h>
+ #include <pj/assert.h>
+ #include <pj/ctype.h>
+ #include <pj/errno.h>
+@@ -416,6 +417,220 @@ pjsip_multipart_find_part( const pjsip_m
+     return NULL;
+ }
++/*
++ * Find a body inside multipart bodies which has the header and value.
++ */
++PJ_DEF(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_header_str(pj_pool_t *pool,
++                                  const pjsip_msg_body *mp,
++                                  const pj_str_t *hdr_name,
++                                  const pj_str_t *hdr_value,
++                                  const pjsip_multipart_part *start)
++{
++    struct multipart_data *m_data;
++    pjsip_multipart_part *part;
++    pjsip_hdr *found_hdr;
++    pj_str_t found_hdr_str;
++    pj_str_t found_hdr_value;
++    pj_size_t expected_hdr_slen;
++    pj_size_t buf_size;
++    int hdr_name_len;
++#define REASONABLE_PADDING 32
++#define SEPARATOR_LEN 2
++    /* Must specify mandatory params */
++    PJ_ASSERT_RETURN(mp && hdr_name && hdr_value, NULL);
++
++    /* mp must really point to an actual multipart msg body */
++    PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
++
++    /*
++     * We'll need to "print" each header we find to test it but
++     * allocating a buffer of PJSIP_MAX_URL_SIZE is overkill.
++     * Instead, we'll allocate one large enough to hold the search
++     * header name, the ": " separator, the search hdr value, and
++     * the NULL terminator.  If we can't print the found header
++     * into that buffer then it can't be a match.
++     *
++     * Some header print functions such as generic_int require enough
++     * space to print the maximum possible header length so we'll
++     * add a reasonable amount to the print buffer size.
++     */
++    expected_hdr_slen = hdr_name->slen + SEPARATOR_LEN + hdr_value->slen;
++    buf_size = expected_hdr_slen + REASONABLE_PADDING;
++    found_hdr_str.ptr = pj_pool_alloc(pool, buf_size);
++    found_hdr_str.slen = 0;
++    hdr_name_len = hdr_name->slen + SEPARATOR_LEN;
++
++    m_data = (struct multipart_data*)mp->data;
++
++    if (start)
++      part = start->next;
++    else
++      part = m_data->part_head.next;
++
++    while (part != &m_data->part_head) {
++      found_hdr = NULL;
++      while ((found_hdr = pjsip_hdr_find_by_name(&part->hdr, hdr_name,
++          (found_hdr ? found_hdr->next : NULL))) != NULL) {
++
++          found_hdr_str.slen = pjsip_hdr_print_on((void*) found_hdr, found_hdr_str.ptr, buf_size);
++          /*
++           * If the buffer was too small (slen = -1) or the result wasn't
++           * the same length as the search header, it can't be a match.
++           */
++          if (found_hdr_str.slen != expected_hdr_slen) {
++              continue;
++          }
++          /*
++           * Set the value overlay to start at the found header value...
++           */
++          found_hdr_value.ptr = found_hdr_str.ptr + hdr_name_len;
++          found_hdr_value.slen = found_hdr_str.slen - hdr_name_len;
++          /* ...and compare it to the supplied header value. */
++          if (pj_strcmp(hdr_value, &found_hdr_value) == 0) {
++              return part;
++          }
++      }
++      part = part->next;
++    }
++    return NULL;
++#undef SEPARATOR_LEN
++#undef REASONABLE_PADDING
++}
++
++PJ_DEF(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_header(pj_pool_t *pool,
++                                  const pjsip_msg_body *mp,
++                                  void *search_for,
++                                  const pjsip_multipart_part *start)
++{
++    struct multipart_data *m_data;
++    pjsip_hdr *search_hdr = search_for;
++    pj_str_t search_buf;
++
++    /* Must specify mandatory params */
++    PJ_ASSERT_RETURN(mp && search_hdr, NULL);
++
++    /* mp must really point to an actual multipart msg body */
++    PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
++
++    /*
++     * Unfortunately, there isn't enough information to determine
++     * the maximum printed size of search_hdr at this point so we
++     * have to allocate a reasonable max.
++     */
++    search_buf.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
++    search_buf.slen = pjsip_hdr_print_on(search_hdr, search_buf.ptr, PJSIP_MAX_URL_SIZE - 1);
++    if (search_buf.slen <= 0) {
++      return NULL;
++    }
++    /*
++     * Set the header value to start after the header name plus the ":", then
++     * strip leading and trailing whitespace.
++     */
++    search_buf.ptr += (search_hdr->name.slen + 1);
++    search_buf.slen -= (search_hdr->name.slen + 1);
++    pj_strtrim(&search_buf);
++
++    return pjsip_multipart_find_part_by_header_str(pool, mp, &search_hdr->name, &search_buf, start);
++}
++
++/*
++ * Convert a Content-ID URI to it's corresponding header value.
++ * RFC2392 says...
++ * A "cid" URL is converted to the corresponding Content-ID message
++ * header by removing the "cid:" prefix, converting the % encoded
++ * character(s) to their equivalent US-ASCII characters, and enclosing
++ * the remaining parts with an angle bracket pair, "<" and ">".
++ *
++ * This implementation will accept URIs with or without the "cid:"
++ * scheme and optional angle brackets.
++ */
++static pj_str_t cid_uri_to_hdr_value(pj_pool_t *pool, pj_str_t *cid_uri)
++{
++    pj_size_t cid_len = pj_strlen(cid_uri);
++    pj_size_t alloc_len = cid_len + 2 /* for the leading and trailing angle brackets */;
++    pj_str_t uri_overlay;
++    pj_str_t cid_hdr;
++    pj_str_t hdr_overlay;
++
++    pj_strassign(&uri_overlay, cid_uri);
++    /* If the URI is already enclosed in angle brackets, remove them. */
++    if (uri_overlay.ptr[0] == '<') {
++      uri_overlay.ptr++;
++      uri_overlay.slen -= 2;
++    }
++    /* If the URI starts with the "cid:" scheme, skip over it. */
++    if (pj_strncmp2(&uri_overlay, "cid:", 4) == 0) {
++      uri_overlay.ptr += 4;
++      uri_overlay.slen -= 4;
++    }
++    /* Start building */
++    cid_hdr.ptr = pj_pool_alloc(pool, alloc_len);
++    cid_hdr.ptr[0] = '<';
++    cid_hdr.slen = 1;
++    hdr_overlay.ptr = cid_hdr.ptr + 1;
++    hdr_overlay.slen = 0;
++    pj_strcpy_unescape(&hdr_overlay, &uri_overlay);
++    cid_hdr.slen += hdr_overlay.slen;
++    cid_hdr.ptr[cid_hdr.slen] = '>';
++    cid_hdr.slen++;
++
++    return cid_hdr;
++}
++
++PJ_DEF(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_cid_str(pj_pool_t *pool,
++                               const pjsip_msg_body *mp,
++                               pj_str_t *cid)
++{
++    struct multipart_data *m_data;
++    pjsip_multipart_part *part;
++    pjsip_generic_string_hdr *found_hdr;
++    pj_str_t found_hdr_value;
++    static pj_str_t hdr_name = { "Content-ID", 10};
++    pj_str_t hdr_value;
++
++    PJ_ASSERT_RETURN(pool && mp && cid && (pj_strlen(cid) > 0), NULL);
++
++    hdr_value = cid_uri_to_hdr_value(pool, cid);
++    if (pj_strlen(&hdr_value) == 0) {
++      return NULL;
++    }
++
++    m_data = (struct multipart_data*)mp->data;
++    part = m_data->part_head.next;
++
++    while (part != &m_data->part_head) {
++      found_hdr = NULL;
++      while ((found_hdr = pjsip_hdr_find_by_name(&part->hdr, &hdr_name,
++          (found_hdr ? found_hdr->next : NULL))) != NULL) {
++          if (pj_strcmp(&hdr_value, &found_hdr->hvalue) == 0) {
++              return part;
++          }
++      }
++      part = part->next;
++    }
++    return NULL;
++}
++
++PJ_DEF(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_cid_uri(pj_pool_t *pool,
++                               const pjsip_msg_body *mp,
++                               pjsip_other_uri *cid_uri)
++{
++    PJ_ASSERT_RETURN(pool && mp && cid_uri, NULL);
++
++    if (pj_strcmp2(&cid_uri->scheme, "cid") != 0) {
++      return NULL;
++    }
++    /*
++     * We only need to pass the URI content so we
++     * can do that directly.
++     */
++    return pjsip_multipart_find_part_by_cid_str(pool, mp, &cid_uri->content);
++}
++
+ /* Parse a multipart part. "pct" is parent content-type  */
+ static pjsip_multipart_part *parse_multipart_part(pj_pool_t *pool,
+                                                 char *start,
+@@ -584,6 +799,7 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
+               (int)boundary.slen, boundary.ptr));
+     }
++
+     /* Build the delimiter:
+      *   delimiter = "--" boundary
+      */
+@@ -630,6 +846,8 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
+       if (*curptr=='\r') ++curptr;
+       if (*curptr!='\n') {
+           /* Expecting a newline here */
++          PJ_LOG(2, (THIS_FILE, "Failed to find newline"));
++
+           return NULL;
+       }
+       ++curptr;
+@@ -645,6 +863,7 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
+           curptr = pj_strstr(&subbody, &delim);
+           if (!curptr) {
+               /* We're really expecting end delimiter to be found. */
++              PJ_LOG(2, (THIS_FILE, "Failed to find end-delimiter"));
+               return NULL;
+           }
+       }
+@@ -670,9 +889,13 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
+       part = parse_multipart_part(pool, start_body, end_body - start_body,
+                                   ctype);
+       if (part) {
++          TRACE_((THIS_FILE, "Adding part"));
+           pjsip_multipart_add_part(pool, body, part);
++      } else {
++          PJ_LOG(2, (THIS_FILE, "Failed to add part"));
+       }
+     }
++    TRACE_((THIS_FILE, "pjsip_multipart_parse finished: %p", body));
+     return body;
+ }
+--- a/pjsip/src/test/multipart_test.c
++++ b/pjsip/src/test/multipart_test.c
+@@ -28,6 +28,7 @@
+ typedef pj_status_t (*verify_ptr)(pj_pool_t*,pjsip_msg_body*);
+ static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body);
++static pj_status_t verify2(pj_pool_t *pool, pjsip_msg_body *body);
+ static struct test_t
+ {
+@@ -68,7 +69,41 @@ static struct test_t
+               "This is epilogue, which should be ignored too",
+               &verify1
++      },
++      {
++              /* Content-type */
++              "multipart", "mixed", "12345",
++
++              /* Body: */
++              "This is the prolog, which should be ignored.\r\n"
++              "--12345\r\n"
++              "Content-Type: text/plain\r\n"
++              "Content-ID: <header1@example.org>\r\n"
++              "Content-ID: <\"header1\"@example.org>\r\n"
++              "Content-Length: 13\r\n"
++              "\r\n"
++              "has header1\r\n"
++              "--12345 \t\r\n"
++              "Content-Type: application/pidf+xml\r\n"
++              "Content-ID: <my header2@example.org>\r\n"
++              "Content-ID: <my\xffheader2@example.org>\r\n"
++              "Content-Length: 13\r\n"
++              "\r\n"
++              "has header2\r\n"
++              "--12345\r\n"
++              "Content-Type: text/plain\r\n"
++              "Content-ID: <my header3@example.org>\r\n"
++              "Content-ID: <header1@example.org>\r\n"
++              "Content-ID: <my header4@example.org>\r\n"
++              "Content-Length: 13\r\n"
++              "\r\n"
++              "has header4\r\n"
++              "--12345--\r\n"
++              "This is epilogue, which should be ignored too",
++
++              &verify2
+       }
++
+ };
+ static void init_media_type(pjsip_media_type *mt,
+@@ -87,6 +122,192 @@ static void init_media_type(pjsip_media_
+     }
+ }
++static int verify_hdr(pj_pool_t *pool, pjsip_msg_body *multipart_body,
++    void *hdr, char *part_body)
++{
++    pjsip_media_type mt;
++    pjsip_multipart_part *part;
++    pj_str_t the_body;
++
++
++    part = pjsip_multipart_find_part_by_header(pool, multipart_body, hdr, NULL);
++    if (!part) {
++      return -1;
++    }
++
++    the_body.ptr = (char*)part->body->data;
++    the_body.slen = part->body->len;
++
++    if (pj_strcmp2(&the_body, part_body) != 0) {
++      return -2;
++    }
++
++    return 0;
++}
++
++static int verify_cid_str(pj_pool_t *pool, pjsip_msg_body *multipart_body,
++    pj_str_t cid_url, char *part_body)
++{
++    pjsip_media_type mt;
++    pjsip_multipart_part *part;
++    pj_str_t the_body;
++
++    part = pjsip_multipart_find_part_by_cid_str(pool, multipart_body, &cid_url);
++    if (!part) {
++      return -3;
++    }
++
++    the_body.ptr = (char*)part->body->data;
++    the_body.slen = part->body->len;
++
++    if (pj_strcmp2(&the_body, part_body) != 0) {
++      return -4;
++    }
++
++    return 0;
++}
++
++static int verify_cid_uri(pj_pool_t *pool, pjsip_msg_body *multipart_body,
++    pjsip_other_uri *cid_uri, char *part_body)
++{
++    pjsip_media_type mt;
++    pjsip_multipart_part *part;
++    pj_str_t the_body;
++
++    part = pjsip_multipart_find_part_by_cid_uri(pool, multipart_body, cid_uri);
++    if (!part) {
++      return -5;
++    }
++
++    the_body.ptr = (char*)part->body->data;
++    the_body.slen = part->body->len;
++
++    if (pj_strcmp2(&the_body, part_body) != 0) {
++      return -6;
++    }
++
++    return 0;
++}
++
++static pj_status_t verify2(pj_pool_t *pool, pjsip_msg_body *body)
++{
++    int rc = 0;
++    int rcbase = 300;
++    pjsip_other_uri *cid_uri;
++    pjsip_ctype_hdr *ctype_hdr = pjsip_ctype_hdr_create(pool);
++
++    ctype_hdr->media.type = pj_str("application");
++    ctype_hdr->media.subtype = pj_str("pidf+xml");
++
++    rc = verify_hdr(pool, body, ctype_hdr, "has header2");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:header1@example.org"), "has header1");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("%22header1%22@example.org"), "has header1");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:%22header1%22@example.org>",
++      strlen("<cid:%22header1%22@example.org>"), 0));
++    rcbase += 10;
++    rc = verify_cid_uri(pool, body, cid_uri, "has header1");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<cid:my%20header2@example.org>"), "has header2");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:my%ffheader2@example.org"), "has header2");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:my%ffheader2@example.org>",
++      strlen("<cid:my%ffheader2@example.org>"), 0));
++    rcbase += 10;
++    rc = verify_cid_uri(pool, body, cid_uri, "has header2");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:my%20header3@example.org"), "has header4");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<cid:my%20header4@example.org>"), "has header4");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:my%20header4@example.org>",
++      strlen("<cid:my%20header4@example.org>"), 0));
++    rcbase += 10;
++    rc = verify_cid_uri(pool, body, cid_uri, "has header4");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<my%20header3@example.org>"), "has header4");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    /* These should all fail for malformed or missing URI */
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:"), "has header4");
++    if (!rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str(""), "has header4");
++    if (!rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<>"), "has header4");
++    if (!rc) {
++      return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<cid>"), "has header4");
++    if (!rc) {
++      return (rc - rcbase);
++    }
++
++    /*
++     * This is going to pass but the ' ' in the uri is un-encoded which is invalid
++     * so we should never see it.
++     */
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:my header3@example.org"), "has header4");
++    if (rc) {
++      return (rc - rcbase);
++    }
++
++    return 0;
++}
++
+ static int verify_part(pjsip_multipart_part *part,
+                      char *h_content_type,
+                      char *h_content_subtype,
+@@ -236,8 +457,10 @@ static int parse_test(void)
+       pj_strdup2_with_null(pool, &str, p_tests[i].msg);
+       body = pjsip_multipart_parse(pool, str.ptr, str.slen, &ctype, 0);
+-      if (!body)
++      if (!body) {
++          pj_pool_release(pool);
+           return -100;
++      }
+       if (p_tests[i].verify) {
+           rc = p_tests[i].verify(pool, body);