From: Andre Heider Date: Thu, 7 Oct 2021 16:31:07 +0000 (+0200) Subject: pjproject: sync patches with asterisk 18.11.2 X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=ccc57cff3c3d540713dcd5e77b688da214a46de3;p=feed%2Ftelephony.git pjproject: sync patches with asterisk 18.11.2 pjproject: sync patches with asterisk 18.7.1 Signed-off-by: Andre Heider (cherry picked from commit 376473b38c0a6f4a8e742a26caafbf484ed1b9c5) pjproject: sync patches with asterisk 18.11.2 Signed-off-by: Sebastian Kemper (cherry picked from commit 0c67ab5831bb1509af035ce8f142eabbf14bd82a) --- diff --git a/libs/pjproject/Makefile b/libs/pjproject/Makefile index 8bba564..5e7b645 100644 --- a/libs/pjproject/Makefile +++ b/libs/pjproject/Makefile @@ -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 index 0000000..a3e668c --- /dev/null +++ b/libs/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch @@ -0,0 +1,27 @@ +From 2ae784030b0d9cf217c3d562af20e4967f19a3dc Mon Sep 17 00:00:00 2001 +From: George Joseph +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 index 0000000..8db8aab --- /dev/null +++ b/libs/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch @@ -0,0 +1,653 @@ +From 0ed41eb5fd0e4192e1b7dc374f819d17aef3e805 Mon Sep 17 00:00:00 2001 +From: George Joseph +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 index 0000000..deb9d8c --- /dev/null +++ b/libs/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch @@ -0,0 +1,116 @@ +From 3faf1d2b4da553bbaee04f9a13a5d084b381e5fb Mon Sep 17 00:00:00 2001 +From: sauwming +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(¶m->name, "p2")) + return -956; +- if (pj_strcmp2(¶m->value, "ab:cd")) ++ if (pj_strcmp2(¶m->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 index 0000000..5979a3b --- /dev/null +++ b/libs/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch @@ -0,0 +1,169 @@ +From 7e3dfd8a15fd0f98dbf0e04d2d7a5bded90ee401 Mon Sep 17 00:00:00 2001 +From: George Joseph +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 index 0000000..3de67b5 --- /dev/null +++ b/libs/pjproject/patches/0160-Additional-multipart-improvements.patch @@ -0,0 +1,635 @@ +From b7ecff22e77887626fd8e8608c4dd73bc7b7366f Mon Sep 17 00:00:00 2001 +From: George Joseph +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 + #include + #include ++#include + #include + #include + #include +@@ -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: \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: \r\n" ++ "Content-ID: \r\n" ++ "Content-Length: 13\r\n" ++ "\r\n" ++ "has header2\r\n" ++ "--12345\r\n" ++ "Content-Type: text/plain\r\n" ++ "Content-ID: \r\n" ++ "Content-ID: \r\n" ++ "Content-ID: \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, "", ++ strlen(""), 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(""), "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, "", ++ strlen(""), 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(""), "has header4"); ++ if (rc) { ++ return (rc - rcbase); ++ } ++ ++ cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "", ++ strlen(""), 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(""), "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(""), "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);