diff --git a/build.symbian/pjsip.mmp b/build.symbian/pjsip.mmp index 6269b47df..1fd7440ca 100644 --- a/build.symbian/pjsip.mmp +++ b/build.symbian/pjsip.mmp @@ -43,6 +43,7 @@ SOURCE sip_dialog_wrap.cpp SOURCE sip_endpoint_wrap.cpp SOURCE sip_errno.c SOURCE sip_msg.c +SOURCE sip_multipart.c SOURCE sip_parser_wrap.cpp SOURCE sip_resolve.c SOURCE sip_tel_uri_wrap.cpp diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index c3d6ca089..f51a30cd5 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -26,6 +26,7 @@ //#define STEREO_DEMO //#define TRANSPORT_ADAPTER_SAMPLE +//#define HAVE_MULTIPART_TEST /* Ringtones US UK */ #define RINGBACK_FREQ1 440 /* 400 */ @@ -2218,6 +2219,36 @@ static void ring_start(pjsua_call_id call_id) } } +#ifdef HAVE_MULTIPART_TEST + /* + * Enable multipart in msg_data and add a dummy body into the + * multipart bodies. + */ + static void add_multipart(pjsua_msg_data *msg_data) + { + static pjsip_multipart_part *alt_part; + + if (!alt_part) { + pj_str_t type, subtype, content; + + alt_part = pjsip_multipart_create_part(app_config.pool); + + type = pj_str("text"); + subtype = pj_str("plain"); + content = pj_str("Sample text body of a multipart bodies"); + alt_part->body = pjsip_msg_body_create(app_config.pool, &type, + &subtype, &content); + } + + msg_data->multipart_ctype.type = pj_str("multipart"); + msg_data->multipart_ctype.subtype = pj_str("mixed"); + pj_list_push_back(&msg_data->multipart_parts, alt_part); + } +# define TEST_MULTIPART(msg_data) add_multipart(msg_data) +#else +# define TEST_MULTIPART(msg_data) +#endif + /* * Find next call when current call is disconnected or when user * press ']' @@ -3432,6 +3463,7 @@ void console_app_main(const pj_str_t *uri_to_call) char *uri; pj_str_t tmp; struct input_result result; + pjsua_msg_data msg_data; pjsua_call_info call_info; pjsua_acc_info acc_info; @@ -3500,7 +3532,9 @@ void console_app_main(const pj_str_t *uri_to_call) tmp.slen = 0; } - pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL); + pjsua_msg_data_init(&msg_data); + TEST_MULTIPART(&msg_data); + pjsua_call_make_call( current_acc, &tmp, 0, NULL, &msg_data, NULL); break; case 'M': @@ -3638,7 +3672,6 @@ void console_app_main(const pj_str_t *uri_to_call) pj_str_t hname = { "Contact", 7 }; pj_str_t hvalue; pjsip_generic_string_hdr hcontact; - pjsua_msg_data msg_data; if (!simple_input("Answer with code (100-699)", buf, sizeof(buf))) continue; @@ -3886,7 +3919,6 @@ void console_app_main(const pj_str_t *uri_to_call) } else { int call = current_call; - pjsua_msg_data msg_data; pjsip_generic_string_hdr refer_sub; pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 }; pj_str_t STR_FALSE = { "false", 5 }; @@ -3941,7 +3973,6 @@ void console_app_main(const pj_str_t *uri_to_call) } else { int call = current_call; int dst_call; - pjsua_msg_data msg_data; pjsip_generic_string_hdr refer_sub; pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 }; pj_str_t STR_FALSE = { "false", 5 }; @@ -4083,7 +4114,6 @@ void console_app_main(const pj_str_t *uri_to_call) digits = pj_str(buf); for (i=0; i #include #include +#include #include /* Core */ diff --git a/pjsip/include/pjsip/sip_multipart.h b/pjsip/include/pjsip/sip_multipart.h new file mode 100644 index 000000000..12a27bada --- /dev/null +++ b/pjsip/include/pjsip/sip_multipart.h @@ -0,0 +1,179 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJSIP_SIP_MULTIPART_H__ +#define __PJSIP_SIP_MULTIPART_H__ + +/** + * @file pjsip/sip_multipart.h + * @brief Multipart support. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJSIP_MULTIPART Multipart message bodies. + * @ingroup PJSIP_MSG + * @brief Support for multipart message bodies. + * @{ + */ + +/** + * This structure describes the individual body part inside a multipart + * message body. It mainly contains the message body itself and optional + * headers. + */ +typedef struct pjsip_multipart_part +{ + /** + * Standard list element. + */ + PJ_DECL_LIST_MEMBER(struct pjsip_multipart_part); + + /** + * Optional message headers. + */ + pjsip_hdr hdr; + + /** + * Pointer to the message body. + */ + pjsip_msg_body *body; + +} pjsip_multipart_part; + +/** + * Create an empty multipart body. + * + * @param pool Memory pool to allocate memory from. + * @param ctype Optional MIME media type of the multipart + * bodies. If not specified, "multipart/mixed" + * will be used. + * @param boundary Optional string to be set as part boundary. + * The boundary string excludes the leading + * hyphens. If this parameter is NULL or empty, + * a random boundary will be generated. + * + * @return Multipart body instance with no part. + */ +PJ_DECL(pjsip_msg_body*) pjsip_multipart_create(pj_pool_t *pool, + const pjsip_media_type *ctype, + const pj_str_t *boundary); + +/** + * Create an empty multipart part. + * + * @param pool The memory pool. + * + * @return The multipart part. + */ +PJ_DECL(pjsip_multipart_part*) pjsip_multipart_create_part(pj_pool_t *pool); + + +/** + * Perform a deep clone to a multipart part. + * + * @param pool The memory pool. + * @param part The part to be duplicated. + * + * @return Copy of the multipart part. + */ +PJ_DECL(pjsip_multipart_part*) +pjsip_multipart_clone_part(pj_pool_t *pool, + const pjsip_multipart_part *part); + +/** + * Add a part into multipart bodies. + * + * @param pool The memory pool. + * @param mp The multipart bodies. + * @param part The part to be added into the bodies. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_multipart_add_part(pj_pool_t *pool, + pjsip_msg_body *mp, + pjsip_multipart_part *part); + +/** + * Get the first part of multipart bodies. + * + * @param mp The multipart bodies. + * + * @return The first part, or NULL if the multipart + * bodies currently doesn't hold any elements. + */ +PJ_DECL(pjsip_multipart_part*) +pjsip_multipart_get_first_part(const pjsip_msg_body *mp); + +/** + * Get the next part after the specified part. + * + * @param mp The multipart bodies. + * @param part The part. + * + * @return The next part, or NULL if there is no other part after + * the part. + */ +PJ_DECL(pjsip_multipart_part*) +pjsip_multipart_get_next_part(const pjsip_msg_body *mp, + pjsip_multipart_part *part); + +/** + * Find a body inside multipart bodies which has the specified content type. + * + * @param mp The multipart body. + * @param content_type Content type to find. + * @param start If specified, the search will begin at + * start->next. Otherwise it will begin at + * the first part in the multipart bodies. + * + * @return The first part with the specified content type + * if found, or NULL. + */ +PJ_DECL(pjsip_multipart_part*) +pjsip_multipart_find_part( const pjsip_msg_body *mp, + const pjsip_media_type *content_type, + const pjsip_multipart_part *start); + +/** + * Parse multipart message. + * + * @param pool Memory pool. + * @param buf Input buffer. + * @param len The buffer length. + * @param ctype Content type of the multipart body. + * @param options Parsing options, must be zero for now. + * + * @return Multipart message body. + */ +PJ_DECL(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool, + char *buf, pj_size_t len, + const pjsip_media_type *ctype, + unsigned options); + +/** + * @} PJSIP_MULTIPART + */ + + +PJ_END_DECL + +#endif /* __PJSIP_SIP_MULTIPART_H__ */ diff --git a/pjsip/include/pjsip/sip_parser.h b/pjsip/include/pjsip/sip_parser.h index 872b87cc7..8634d3b11 100644 --- a/pjsip/include/pjsip/sip_parser.h +++ b/pjsip/include/pjsip/sip_parser.h @@ -276,7 +276,7 @@ PJ_DECL(pj_status_t) pjsip_find_msg(const char *buf, * lines, and two when an error happen the value can * pinpoint the location of the error in the buffer. * - * @return The instance of the header if parsing was successfull, + * @return The instance of the header if parsing was successful, * or otherwise a NULL pointer will be returned. */ PJ_DECL(void*) pjsip_parse_hdr( pj_pool_t *pool, const pj_str_t *hname, @@ -287,21 +287,25 @@ PJ_DECL(void*) pjsip_parse_hdr( pj_pool_t *pool, const pj_str_t *hname, * Parse header line(s). Multiple headers can be parsed by this function. * When there are multiple headers, the headers MUST be separated by either * a newline (as in SIP message) or ampersand mark (as in URI). This separator - * however is optional for the last header. + * is optional for the last header. * - * @param pool the pool. - * @param input the input text to parse, which must be NULL terminated. - * @param size the text length. - * @param hlist the header list to store the parsed headers. + * @param pool The pool. + * @param input The input text to parse, which must be NULL terminated. + * @param size The text length. + * @param hlist The header list to store the parsed headers. * This list must have been initialized before calling * this function. + * @param options Specify 1 here to make parsing stop when error is + * encountered when parsing the header. Otherwise the + * error is silently ignored and parsing resumes to the + * next line. * @return zero if successfull, or -1 if error is encountered. * Upon error, the \a hlist argument MAY contain * successfully parsed headers. */ -PJ_DECL(pj_status_t) pjsip_parse_headers( pj_pool_t *pool, - char *input, pj_size_t size, - pj_list *hlist ); +PJ_DECL(pj_status_t) pjsip_parse_headers( pj_pool_t *pool, char *input, + pj_size_t size, pjsip_hdr *hlist, + unsigned options); /** diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index add306b94..7c770e054 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -1186,10 +1186,26 @@ struct pjsua_msg_data pj_str_t content_type; /** - * Optional message body. + * Optional message body to be added to the message, only when the + * message doesn't have a body. */ pj_str_t msg_body; + /** + * Content type of the multipart body. If application wants to send + * multipart message bodies, it puts the parts in \a parts and set + * the content type in \a multipart_ctype. If the message already + * contains a body, the body will be added to the multipart bodies. + */ + pjsip_media_type multipart_ctype; + + /** + * List of multipart parts. If application wants to send multipart + * message bodies, it puts the parts in \a parts and set the content + * type in \a multipart_ctype. If the message already contains a body, + * the body will be added to the multipart bodies. + */ + pjsip_multipart_part multipart_parts; }; diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c index b522b3bdf..ac136825a 100644 --- a/pjsip/src/pjsip-ua/sip_inv.c +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -745,6 +746,67 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg, 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; + + 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(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 && ctype_hdr && + pj_stricmp(&ctype_hdr->media.type, &app_sdp.type)==0 && + pj_stricmp(&ctype_hdr->media.subtype, &app_sdp.subtype)==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)) + { + pjsip_multipart_part *part; + + 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 (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_validate(sdp_info->sdp); + + 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; +} + + /* * Verify incoming INVITE request. */ @@ -765,6 +827,7 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request2(pjsip_rx_data *rdata, unsigned rem_option = 0; pj_status_t status = PJ_SUCCESS; pjsip_hdr res_hdr_list; + pjsip_rdata_sdp_info *sdp_info; /* Init return arguments. */ if (p_tdata) *p_tdata = NULL; @@ -821,17 +884,17 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request2(pjsip_rx_data *rdata, /* Check the request body, see if it's something that we support, * only when the body hasn't been parsed before. */ - if (r_sdp==NULL && msg->body) { - pjsip_msg_body *body = msg->body; - pj_str_t str_application = {"application", 11}; - pj_str_t str_sdp = { "sdp", 3 }; - pjmedia_sdp_session *sdp; + if (r_sdp == NULL) { + sdp_info = pjsip_rdata_get_sdp_info(rdata); + } else { + sdp_info = NULL; + } - /* Check content type. */ - if (pj_stricmp(&body->content_type.type, &str_application) != 0 || - pj_stricmp(&body->content_type.subtype, &str_sdp) != 0) - { - /* Not "application/sdp" */ + if (r_sdp==NULL && msg->body) { + + /* Check if body really contains SDP. */ + if (sdp_info->body.ptr == NULL) { + /* Couldn't find "application/sdp" */ code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE; status = PJSIP_ERRNO_FROM_SIP_STATUS(code); @@ -848,13 +911,7 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request2(pjsip_rx_data *rdata, goto on_return; } - /* Parse and validate SDP */ - status = pjmedia_sdp_parse(rdata->tp_info.pool, - (char*)body->data, body->len, &sdp); - if (status == PJ_SUCCESS) - status = pjmedia_sdp_validate(sdp); - - if (status != PJ_SUCCESS) { + if (sdp_info->sdp_err != PJ_SUCCESS) { /* Unparseable or invalid SDP */ code = PJSIP_SC_BAD_REQUEST; @@ -864,7 +921,7 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request2(pjsip_rx_data *rdata, w = pjsip_warning_hdr_create_from_status(rdata->tp_info.pool, pjsip_endpt_name(endpt), - status); + sdp_info->sdp_err); PJ_ASSERT_RETURN(w, PJ_ENOMEM); pj_list_push_back(&res_hdr_list, w); @@ -873,7 +930,7 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request2(pjsip_rx_data *rdata, goto on_return; } - r_sdp = sdp; + r_sdp = sdp_info->sdp; } if (r_sdp) { @@ -1163,7 +1220,7 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uas( pjsip_dialog *dlg, pjsip_inv_session *inv; struct tsx_inv_data *tsx_inv_data; pjsip_msg *msg; - pjmedia_sdp_session *rem_sdp = NULL; + pjsip_rdata_sdp_info *sdp_info; pj_status_t status; /* Verify arguments. */ @@ -1211,26 +1268,17 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uas( pjsip_dialog *dlg, /* Object name will use the same dialog pointer. */ pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg); - /* Parse SDP in message body, if present. */ - if (msg->body) { - pjsip_msg_body *body = msg->body; - - /* Parse and validate SDP */ - status = pjmedia_sdp_parse(inv->pool, (char*)body->data, body->len, - &rem_sdp); - if (status == PJ_SUCCESS) - status = pjmedia_sdp_validate(rem_sdp); - - if (status != PJ_SUCCESS) { - pjsip_dlg_dec_lock(dlg); - return status; - } + /* Process SDP in message body, if present. */ + sdp_info = pjsip_rdata_get_sdp_info(rdata); + if (sdp_info->sdp_err) { + pjsip_dlg_dec_lock(dlg); + return sdp_info->sdp_err; } /* Create negotiator. */ - if (rem_sdp) { - status = pjmedia_sdp_neg_create_w_remote_offer(inv->pool, - local_sdp, rem_sdp, + if (sdp_info->sdp) { + status = pjmedia_sdp_neg_create_w_remote_offer(inv->pool, local_sdp, + sdp_info->sdp, &inv->neg); } else if (local_sdp) { @@ -1374,8 +1422,8 @@ PJ_DEF(pj_status_t) pjsip_create_sdp_body( pj_pool_t *pool, body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); PJ_ASSERT_RETURN(body != NULL, PJ_ENOMEM); - body->content_type.type = STR_APPLICATION; - body->content_type.subtype = STR_SDP; + pjsip_media_type_init(&body->content_type, (pj_str_t*)&STR_APPLICATION, + (pj_str_t*)&STR_SDP); body->data = sdp; body->len = 0; body->clone_data = &clone_sdp; @@ -1527,6 +1575,7 @@ static void swap_pool(pj_pool_t **p1, pj_pool_t **p2) *p2 = tmp; } + /* * Initiate SDP negotiation in the SDP negotiator. */ @@ -1575,11 +1624,9 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, pjsip_rx_data *rdata) { struct tsx_inv_data *tsx_inv_data; - static const pj_str_t str_application = { "application", 11 }; - static const pj_str_t str_sdp = { "sdp", 3 }; pj_status_t status; pjsip_msg *msg; - pjmedia_sdp_session *rem_sdp; + pjsip_rdata_sdp_info *sdp_info; /* Check if SDP is present in the message. */ @@ -1589,9 +1636,8 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, return PJ_SUCCESS; } - if (pj_stricmp(&msg->body->content_type.type, &str_application) || - pj_stricmp(&msg->body->content_type.subtype, &str_sdp)) - { + sdp_info = pjsip_rdata_get_sdp_info(rdata); + if (sdp_info->body.ptr == NULL) { /* Message body is not "application/sdp" */ return PJMEDIA_SDP_EINSDP; } @@ -1660,22 +1706,16 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, } } - /* Parse the SDP body. */ - - status = pjmedia_sdp_parse(rdata->tp_info.pool, - (char*)msg->body->data, - msg->body->len, &rem_sdp); - if (status == PJ_SUCCESS) - status = pjmedia_sdp_validate(rem_sdp); - - if (status != PJ_SUCCESS) { - char errmsg[PJ_ERR_MSG_SIZE]; - pj_strerror(status, errmsg, sizeof(errmsg)); - PJ_LOG(4,(THIS_FILE, "Error parsing SDP in %s: %s", - pjsip_rx_data_get_info(rdata), errmsg)); + /* Process the SDP body. */ + if (sdp_info->sdp_err) { + PJ_PERROR(4,(THIS_FILE, sdp_info->sdp_err, + "Error parsing SDP in %s", + pjsip_rx_data_get_info(rdata))); return PJMEDIA_SDP_EINSDP; } + pj_assert(sdp_info->sdp != NULL); + /* The SDP can be an offer or answer, depending on negotiator's state */ if (inv->neg == NULL || @@ -1689,17 +1729,16 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, if (inv->neg == NULL) { status=pjmedia_sdp_neg_create_w_remote_offer(inv->pool, NULL, - rem_sdp, &inv->neg); + sdp_info->sdp, + &inv->neg); } else { status=pjmedia_sdp_neg_set_remote_offer(inv->pool_prov, inv->neg, - rem_sdp); + sdp_info->sdp); } if (status != PJ_SUCCESS) { - char errmsg[PJ_ERR_MSG_SIZE]; - pj_strerror(status, errmsg, sizeof(errmsg)); - PJ_LOG(4,(THIS_FILE, "Error processing SDP offer in %s: %s", - pjsip_rx_data_get_info(rdata), errmsg)); + PJ_PERROR(4,(THIS_FILE, status, "Error processing SDP offer in %", + pjsip_rx_data_get_info(rdata))); return PJMEDIA_SDP_EINSDP; } @@ -1707,7 +1746,7 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, if (mod_inv.cb.on_rx_offer && inv->notify) { - (*mod_inv.cb.on_rx_offer)(inv, rem_sdp); + (*mod_inv.cb.on_rx_offer)(inv, sdp_info->sdp); } @@ -1724,13 +1763,11 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, pjsip_rx_data_get_info(rdata))); status = pjmedia_sdp_neg_set_remote_answer(inv->pool_prov, inv->neg, - rem_sdp); + sdp_info->sdp); if (status != PJ_SUCCESS) { - char errmsg[PJ_ERR_MSG_SIZE]; - pj_strerror(status, errmsg, sizeof(errmsg)); - PJ_LOG(4,(THIS_FILE, "Error processing SDP answer in %s: %s", - pjsip_rx_data_get_info(rdata), errmsg)); + PJ_PERROR(4,(THIS_FILE, status, "Error processing SDP answer in %s", + pjsip_rx_data_get_info(rdata))); return PJMEDIA_SDP_EINSDP; } @@ -3855,6 +3892,7 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; pjsip_tx_data *tdata; pj_status_t status; + pjsip_rdata_sdp_info *sdp_info; pjsip_status_code st_code; /* Check if we have INVITE pending. */ @@ -3925,7 +3963,8 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) /* If the INVITE request has SDP body, send answer. * Otherwise generate offer from local active SDP. */ - if (rdata->msg_info.msg->body != NULL) { + sdp_info = pjsip_rdata_get_sdp_info(rdata); + if (sdp_info->sdp != NULL) { status = process_answer(inv, 200, tdata, NULL); } else { /* INVITE does not have SDP. diff --git a/pjsip/src/pjsip/sip_multipart.c b/pjsip/src/pjsip/sip_multipart.c new file mode 100644 index 000000000..5d40ddb5d --- /dev/null +++ b/pjsip/src/pjsip/sip_multipart.c @@ -0,0 +1,644 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "sip_multipart.c" + +#define IS_SPACE(c) ((c)==' ' || (c)=='\t') + +#if 0 +# define TRACE_(x) PJ_LOG(4,x) +#else +# define TRACE_(x) +#endif + +extern pj_bool_t pjsip_use_compact_form; + +/* Type of "data" in multipart pjsip_msg_body */ +struct multipart_data +{ + pj_str_t boundary; + pjsip_multipart_part part_head; +}; + + +static int multipart_print_body(struct pjsip_msg_body *msg_body, + char *buf, pj_size_t size) +{ + const struct multipart_data *m_data; + pj_str_t clen_hdr = { "Content-Length: ", 16}; + pjsip_multipart_part *part; + char *p = buf, *end = buf+size; + +#define SIZE_LEFT() (end-p) + + m_data = (const struct multipart_data*)msg_body->data; + + PJ_ASSERT_RETURN(m_data && !pj_list_empty(&m_data->part_head), PJ_EINVAL); + + part = m_data->part_head.next; + while (part != &m_data->part_head) { + enum { CLEN_SPACE = 5 }; + char *clen_pos; + const pjsip_hdr *hdr; + + clen_pos = NULL; + + /* Print delimiter */ + if (SIZE_LEFT() <= (m_data->boundary.slen+8) << 1) + return -1; + *p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-'; + pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen); + p += m_data->boundary.slen; + *p++ = 13; *p++ = 10; + + /* Print optional headers */ + hdr = part->hdr.next; + while (hdr != &part->hdr) { + int printed = pjsip_hdr_print_on((pjsip_hdr*)hdr, p, SIZE_LEFT()); + if (printed < 0) + return -1; + p += printed; + hdr = hdr->next; + } + + /* Automaticly adds Content-Type and Content-Length headers, only + * if content_type is set in the message body. + */ + if (part->body && part->body->content_type.type.slen) { + pj_str_t ctype_hdr = { "Content-Type: ", 14}; + const pjsip_media_type *media = &part->body->content_type; + + if (pjsip_use_compact_form) { + ctype_hdr.ptr = "c: "; + ctype_hdr.slen = 3; + } + + /* Add Content-Type header. */ + if ( (end-p) < 24 + media->type.slen + media->subtype.slen) { + return -1; + } + pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen); + p += ctype_hdr.slen; + p += pjsip_media_type_print(p, end-p, media); + *p++ = '\r'; + *p++ = '\n'; + + /* Add Content-Length header. */ + if ((end-p) < clen_hdr.slen + 12 + 2) { + return -1; + } + pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen); + p += clen_hdr.slen; + + /* Print blanks after "Content-Length:", this is where we'll put + * the content length value after we know the length of the + * body. + */ + pj_memset(p, ' ', CLEN_SPACE); + clen_pos = p; + p += CLEN_SPACE; + *p++ = '\r'; + *p++ = '\n'; + } + + /* Empty newline */ + *p++ = 13; *p++ = 10; + + /* Print the body */ + pj_assert(part->body != NULL); + if (part->body) { + int printed = part->body->print_body(part->body, p, SIZE_LEFT()); + if (printed < 0) + return -1; + p += printed; + + /* Now that we have the length of the body, print this to the + * Content-Length header. + */ + if (clen_pos) { + char tmp[16]; + int len; + + len = pj_utoa(printed, tmp); + if (len > CLEN_SPACE) len = CLEN_SPACE; + pj_memcpy(clen_pos+CLEN_SPACE-len, tmp, len); + } + } + + part = part->next; + } + + /* Print closing delimiter */ + if (SIZE_LEFT() < m_data->boundary.slen+8) + return -1; + *p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-'; + pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen); + p += m_data->boundary.slen; + *p++ = '-'; *p++ = '-'; *p++ = 13; *p++ = 10; + +#undef SIZE_LEFT + + return p - buf; +} + +static void* multipart_clone_data(pj_pool_t *pool, const void *data, + unsigned len) +{ + const struct multipart_data *src; + struct multipart_data *dst; + const pjsip_multipart_part *src_part; + + src = (const struct multipart_data*) data; + dst = PJ_POOL_ALLOC_T(pool, struct multipart_data); + + pj_strdup(pool, &dst->boundary, &src->boundary); + + src_part = src->part_head.next; + while (src_part != &src->part_head) { + pjsip_multipart_part *dst_part; + const pjsip_hdr *src_hdr; + + dst_part = pjsip_multipart_create_part(pool); + + src_hdr = src_part->hdr.next; + while (src_hdr != &src_part->hdr) { + pjsip_hdr *dst_hdr = pjsip_hdr_clone(pool, src_hdr); + pj_list_push_back(&dst_part->hdr, dst_hdr); + src_hdr = src_hdr->next; + } + + dst_part->body = pjsip_msg_body_clone(pool, src_part->body); + + pj_list_push_back(&dst->part_head, dst_part); + + src_part = src_part->next; + } + + return (void*)dst; +} + +/* + * Create an empty multipart body. + */ +PJ_DEF(pjsip_msg_body*) pjsip_multipart_create( pj_pool_t *pool, + const pjsip_media_type *ctype, + const pj_str_t *boundary) +{ + pjsip_msg_body *body; + pjsip_param *ctype_param; + struct multipart_data *mp_data; + pj_str_t STR_BOUNDARY = { "boundary", 8 }; + + PJ_ASSERT_RETURN(pool, NULL); + + body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + + /* content-type */ + if (ctype && ctype->type.slen) { + pjsip_media_type_cp(pool, &body->content_type, ctype); + } else { + const pj_str_t STR_MULTIPART = {"multipart", 9}; + const pj_str_t STR_MIXED = { "mixed", 5 }; + + body->content_type.type = STR_MULTIPART; + body->content_type.subtype = STR_MIXED; + } + + /* multipart data */ + mp_data = PJ_POOL_ZALLOC_T(pool, struct multipart_data); + pj_list_init(&mp_data->part_head); + if (boundary) { + pj_strdup(pool, &mp_data->boundary, boundary); + } else { + pj_create_unique_string(pool, &mp_data->boundary); + } + body->data = mp_data; + + /* Add ";boundary" parameter to content_type parameter. */ + ctype_param = pjsip_param_find(&body->content_type.param, &STR_BOUNDARY); + if (!ctype_param) { + ctype_param = PJ_POOL_ALLOC_T(pool, pjsip_param); + ctype_param->name = STR_BOUNDARY; + pj_list_push_back(&body->content_type.param, ctype_param); + } + ctype_param->value = mp_data->boundary; + + /* function pointers */ + body->print_body = &multipart_print_body; + body->clone_data = &multipart_clone_data; + + return body; +} + +/* + * Create an empty multipart part. + */ +PJ_DEF(pjsip_multipart_part*) pjsip_multipart_create_part(pj_pool_t *pool) +{ + pjsip_multipart_part *mp; + + mp = PJ_POOL_ZALLOC_T(pool, pjsip_multipart_part); + pj_list_init(&mp->hdr); + + return mp; +} + + +/* + * Deep clone. + */ +PJ_DEF(pjsip_multipart_part*) +pjsip_multipart_clone_part(pj_pool_t *pool, + const pjsip_multipart_part *src) +{ + pjsip_multipart_part *dst; + const pjsip_hdr *hdr; + + dst = pjsip_multipart_create_part(pool); + + hdr = src->hdr.next; + while (hdr != &src->hdr) { + pj_list_push_back(&dst->hdr, pjsip_hdr_clone(pool, hdr)); + hdr = hdr->next; + } + + dst->body = pjsip_msg_body_clone(pool, src->body); + + return dst; +} + + +/* + * Add a part into multipart bodies. + */ +PJ_DEF(pj_status_t) pjsip_multipart_add_part( pj_pool_t *pool, + pjsip_msg_body *mp, + pjsip_multipart_part *part) +{ + struct multipart_data *m_data; + + /* All params must be specified */ + PJ_ASSERT_RETURN(pool && mp && part, PJ_EINVAL); + + /* mp must really point to an actual multipart msg body */ + PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, PJ_EINVAL); + + /* The multipart part must contain a valid message body */ + PJ_ASSERT_RETURN(part->body && part->body->print_body, PJ_EINVAL); + + m_data = (struct multipart_data*)mp->data; + pj_list_push_back(&m_data->part_head, part); + + PJ_UNUSED_ARG(pool); + + return PJ_SUCCESS; +} + +/* + * Get the first part of multipart bodies. + */ +PJ_DEF(pjsip_multipart_part*) +pjsip_multipart_get_first_part(const pjsip_msg_body *mp) +{ + struct multipart_data *m_data; + + /* Must specify mandatory params */ + PJ_ASSERT_RETURN(mp, NULL); + + /* mp must really point to an actual multipart msg body */ + PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL); + + m_data = (struct multipart_data*)mp->data; + if (pj_list_empty(&m_data->part_head)) + return NULL; + + return m_data->part_head.next; +} + +/* + * Get the next part after the specified part. + */ +PJ_DEF(pjsip_multipart_part*) +pjsip_multipart_get_next_part(const pjsip_msg_body *mp, + pjsip_multipart_part *part) +{ + struct multipart_data *m_data; + + /* Must specify mandatory params */ + PJ_ASSERT_RETURN(mp && part, NULL); + + /* mp must really point to an actual multipart msg body */ + PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL); + + m_data = (struct multipart_data*)mp->data; + + /* the part parameter must be really member of the list */ + PJ_ASSERT_RETURN(pj_list_find_node(&m_data->part_head, part) != NULL, + NULL); + + if (part->next == &m_data->part_head) + return NULL; + + return part->next; +} + +/* + * Find a body inside multipart bodies which has the specified content type. + */ +PJ_DEF(pjsip_multipart_part*) +pjsip_multipart_find_part( const pjsip_msg_body *mp, + const pjsip_media_type *content_type, + const pjsip_multipart_part *start) +{ + struct multipart_data *m_data; + pjsip_multipart_part *part; + + /* Must specify mandatory params */ + PJ_ASSERT_RETURN(mp && content_type, NULL); + + /* mp must really point to an actual multipart msg body */ + PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL); + + 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) { + if (pjsip_media_type_cmp(&part->body->content_type, content_type)==0) + return part; + part = part->next; + } + + return NULL; +} + +/* Parse a multipart part. "pct" is parent content-type */ +static pjsip_multipart_part *parse_multipart_part(pj_pool_t *pool, + char *start, + pj_size_t len, + const pjsip_media_type *pct) +{ + pjsip_multipart_part *part = pjsip_multipart_create_part(pool); + char *p = start, *end = start+len, *end_hdr = NULL, *start_body = NULL; + pjsip_ctype_hdr *ctype_hdr = NULL; + + TRACE_((THIS_FILE, "Parsing part: begin--\n%.*s\n--end", + (int)len, start)); + + /* Find the end of header area, by looking at an empty line */ + for (;;) { + while (p!=end && *p!='\n') ++p; + if (p==end) { + start_body = end; + break; + } + if ((p==start) || (p==start+1 && *(p-1)=='\r')) { + /* Empty header section */ + end_hdr = start; + start_body = ++p; + break; + } else if (p==end-1) { + /* Empty body section */ + end_hdr = end; + start_body = ++p; + } else if ((p>=start+1 && *(p-1)=='\n') || + (p>=start+2 && *(p-1)=='\r' && *(p-2)=='\n')) + { + /* Found it */ + end_hdr = (*(p-1)=='\r') ? (p-1) : p; + start_body = ++p; + break; + } else { + ++p; + } + } + + /* Parse the headers */ + if (end_hdr-start > 0) { + pjsip_hdr *hdr; + pj_status_t status; + + status = pjsip_parse_headers(pool, start, end_hdr-start, + &part->hdr, 0); + if (status != PJ_SUCCESS) { + PJ_PERROR(2,(THIS_FILE, status, "Warning: error parsing multipart" + " header")); + } + + /* Find Content-Type header */ + hdr = part->hdr.next; + while (hdr != &part->hdr) { + TRACE_((THIS_FILE, "Header parsed: %.*s", (int)hdr->name.slen, + hdr->name.ptr)); + if (hdr->type == PJSIP_H_CONTENT_TYPE) { + ctype_hdr = (pjsip_ctype_hdr*)hdr; + } + hdr = hdr->next; + } + } + + /* Assign the body */ + part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + if (ctype_hdr) { + pjsip_media_type_cp(pool, &part->body->content_type, &ctype_hdr->media); + } else if (pct && pj_stricmp2(&pct->subtype, "digest")==0) { + part->body->content_type.type = pj_str("message"); + part->body->content_type.subtype = pj_str("rfc822"); + } else { + part->body->content_type.type = pj_str("text"); + part->body->content_type.subtype = pj_str("plain"); + } + + if (start_body < end) { + part->body->data = start_body; + part->body->len = end - start_body; + } else { + part->body->data = ""; + part->body->len = 0; + } + TRACE_((THIS_FILE, "Body parsed: \"%.*s\"", (int)part->body->len, + part->body->data)); + part->body->print_body = &pjsip_print_text_body; + part->body->clone_data = &pjsip_clone_text_data; + + return part; +} + +/* Public function to parse multipart message bodies into its parts */ +PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool, + char *buf, pj_size_t len, + const pjsip_media_type *ctype, + unsigned options) +{ + pj_str_t boundary, delim; + char *curptr, *endptr; + const pjsip_param *ctype_param; + const pj_str_t STR_BOUNDARY = { "boundary", 8 }; + pjsip_msg_body *body = NULL; + + PJ_ASSERT_RETURN(pool && buf && len && ctype && !options, NULL); + + TRACE_((THIS_FILE, "Started parsing multipart body")); + + /* Get the boundary value in the ctype */ + boundary.slen = 0; + ctype_param = pjsip_param_find(&ctype->param, &STR_BOUNDARY); + if (ctype_param) { + boundary = ctype_param->value; + TRACE_((THIS_FILE, "Boundary is specified: '%.*s'", (int)boundary.slen, + boundary.ptr)); + } + + if (!boundary.slen) { + /* Boundary not found or not specified. Try to be clever, get + * the boundary from the body. + */ + char *p=buf, *end=buf+len; + + PJ_LOG(4,(THIS_FILE, "Warning: boundary parameter not found or " + "not specified when parsing multipart body")); + + /* Find the first "--". This "--" must be right after a CRLF, unless + * it really appears at the start of the buffer. + */ + for (;;) { + while (p!=end && *p!='-') ++p; + if (p!=end && *(p+1)=='-' && + ((p>buf && *(p-1)=='\n') || (p==buf))) + { + p+=2; + break; + } else { + ++p; + } + } + + if (p==end) { + /* Unable to determine boundary. Maybe this is not a multipart + * message? + */ + PJ_LOG(4,(THIS_FILE, "Error: multipart boundary not specified and" + " unable to calculate from the body")); + return NULL; + } + + boundary.ptr = p; + while (p!=end && !pj_isspace(*p)) ++p; + boundary.slen = p - boundary.ptr; + + TRACE_((THIS_FILE, "Boundary is calculated: '%.*s'", + (int)boundary.slen, boundary.ptr)); + } + + /* Build the delimiter: + * delimiter = "--" boundary + */ + delim.slen = boundary.slen+2; + delim.ptr = (char*)pj_pool_alloc(pool, (int)delim.slen); + delim.ptr[0] = '-'; + delim.ptr[1] = '-'; + pj_memcpy(delim.ptr+2, boundary.ptr, boundary.slen); + + /* Start parsing the body, skip until the first delimiter. */ + curptr = buf; + endptr = buf + len; + { + pj_str_t body; + + body.ptr = buf; body.slen = len; + curptr = pj_strstr(&body, &delim); + if (!curptr) + return NULL; + } + + body = pjsip_multipart_create(pool, ctype, &boundary); + + for (;;) { + char *start_body, *end_body; + pjsip_multipart_part *part; + + /* Eat the boundary */ + curptr += delim.slen; + if (*curptr=='-' && curptr #include #include +#include #include #include #include /* rdata structure */ @@ -34,6 +35,8 @@ #include #include +#define THIS_FILE "sip_parser.c" + #define ALNUM #define RESERVED ";/?:@&=+$," #define MARK "-_.!~*'()" @@ -268,13 +271,6 @@ PJ_DEF(void) pjsip_concat_param_imp(pj_str_t *param, pj_pool_t *pool, param->slen = p - new_param; } -/* Concatenate unrecognized params into single string. */ -static void concat_param( pj_str_t *param, pj_pool_t *pool, - const pj_str_t *pname, const pj_str_t *pvalue ) -{ - pjsip_concat_param_imp(param, pool, pname, pvalue, ';'); -} - /* Initialize static properties of the parser. */ static pj_status_t init_parser() { @@ -1052,15 +1048,27 @@ parse_headers: * as body. */ if (ctype_hdr && scanner->curptr!=scanner->end) { - pjsip_msg_body *body = PJ_POOL_ALLOC_T(pool, pjsip_msg_body); - body->content_type.type = ctype_hdr->media.type; - body->content_type.subtype = ctype_hdr->media.subtype; - body->content_type.param = ctype_hdr->media.param; + /* New: if Content-Type indicates that this is a multipart + * message body, parse it. + */ + const pj_str_t STR_MULTIPART = { "multipart", 9 }; + pjsip_msg_body *body; - body->data = scanner->curptr; - body->len = scanner->end - scanner->curptr; - body->print_body = &pjsip_print_text_body; - body->clone_data = &pjsip_clone_text_data; + if (pj_stricmp(&ctype_hdr->media.type, &STR_MULTIPART)==0) { + body = pjsip_multipart_parse(pool, scanner->curptr, + scanner->end - scanner->curptr, + &ctype_hdr->media, 0); + } else { + body = PJ_POOL_ALLOC_T(pool, pjsip_msg_body); + body->content_type.type = ctype_hdr->media.type; + body->content_type.subtype = ctype_hdr->media.subtype; + body->content_type.param = ctype_hdr->media.param; + + body->data = scanner->curptr; + body->len = scanner->end - scanner->curptr; + body->print_body = &pjsip_print_text_body; + body->clone_data = &pjsip_clone_text_data; + } msg->body = body; } @@ -1847,9 +1855,9 @@ static pjsip_hdr* parse_hdr_content_type( pjsip_parse_ctx *ctx ) /* Parse media parameters */ while (*scanner->curptr == ';') { - pj_str_t pname, pvalue; - int_parse_param(scanner, ctx->pool, &pname, &pvalue, 0); - concat_param(&hdr->media.param, ctx->pool, &pname, &pvalue); + pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param); + int_parse_param(scanner, ctx->pool, ¶m->name, ¶m->value, 0); + pj_list_push_back(&hdr->media.param, param); } parse_hdr_end(ctx->scanner); @@ -2264,3 +2272,109 @@ PJ_DEF(void*) pjsip_parse_hdr( pj_pool_t *pool, const pj_str_t *hname, return hdr; } +/* Parse multiple header lines */ +PJ_DEF(pj_status_t) pjsip_parse_headers( pj_pool_t *pool, char *input, + pj_size_t size, pjsip_hdr *hlist, + unsigned options) +{ + enum { STOP_ON_ERROR = 1 }; + pj_scanner scanner; + pjsip_parse_ctx ctx; + pj_str_t hname; + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, input, size, PJ_SCAN_AUTOSKIP_WS_HEADER, + &on_syntax_error); + + pj_bzero(&ctx, sizeof(ctx)); + ctx.scanner = &scanner; + ctx.pool = pool; + +retry_parse: + PJ_TRY + { + /* Parse headers. */ + do { + pjsip_parse_hdr_func * handler; + pjsip_hdr *hdr = NULL; + + /* Init hname just in case parsing fails. + * Ref: PROTOS #2412 + */ + hname.slen = 0; + + /* Get hname. */ + pj_scan_get( &scanner, &pconst.pjsip_TOKEN_SPEC, &hname); + if (pj_scan_get_char( &scanner ) != ':') { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + /* Find handler. */ + handler = find_handler(&hname); + + /* Call the handler if found. + * If no handler is found, then treat the header as generic + * hname/hvalue pair. + */ + if (handler) { + hdr = (*handler)(&ctx); + } else { + hdr = parse_hdr_generic_string(&ctx); + hdr->name = hdr->sname = hname; + } + + /* Single parse of header line can produce multiple headers. + * For example, if one Contact: header contains Contact list + * separated by comma, then these Contacts will be split into + * different Contact headers. + * So here we must insert list instead of just insert one header. + */ + if (hdr) + pj_list_insert_nodes_before(hlist, hdr); + + /* Parse until EOF or an empty line is found. */ + } while (!pj_scan_is_eof(&scanner) && !IS_NEWLINE(*scanner.curptr)); + + /* If empty line is found, eat it. */ + if (!pj_scan_is_eof(&scanner)) { + if (IS_NEWLINE(*scanner.curptr)) { + pj_scan_get_newline(&scanner); + } + } + } + PJ_CATCH_ANY + { + PJ_LOG(4,(THIS_FILE, "Error parsing header: '%.*s' line %d col %d", + (int)hname.slen, hname.ptr, scanner.line, + pj_scan_get_col(&scanner))); + + /* Exception was thrown during parsing. */ + if ((options & STOP_ON_ERROR) == STOP_ON_ERROR) { + pj_scan_fini(&scanner); + return PJSIP_EINVALIDHDR; + } + + /* Skip until newline, and parse next header. */ + if (!pj_scan_is_eof(&scanner)) { + /* Skip until next line. + * Watch for header continuation. + */ + do { + pj_scan_skip_line(&scanner); + } while (IS_SPACE(*scanner.curptr)); + } + + /* Restore flag. Flag may be set in int_parse_sip_url() */ + scanner.skip_ws = PJ_SCAN_AUTOSKIP_WS_HEADER; + + /* Continue parse next header, if any. */ + if (!pj_scan_is_eof(&scanner) && !IS_NEWLINE(*scanner.curptr)) { + goto retry_parse; + } + + } + PJ_END; + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 8b695adff..d390009ef 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -645,7 +645,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsua_call *call; int call_id = -1; int sip_err_code; - pjmedia_sdp_session *offer, *answer; + pjmedia_sdp_session *offer=NULL, *answer; pj_status_t status; /* Don't want to handle anything but INVITE */ @@ -765,13 +765,14 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) /* Parse SDP from incoming request */ if (rdata->msg_info.msg->body) { - status = pjmedia_sdp_parse(rdata->tp_info.pool, - (char*)rdata->msg_info.msg->body->data, - rdata->msg_info.msg->body->len, &offer); - if (status == PJ_SUCCESS) { - /* Validate */ - status = pjmedia_sdp_validate(offer); - } + pjsip_rdata_sdp_info *sdp_info; + + sdp_info = pjsip_rdata_get_sdp_info(rdata); + offer = sdp_info->sdp; + + status = sdp_info->sdp_err; + if (status==PJ_SUCCESS && sdp_info->sdp==NULL) + status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE); if (status != PJ_SUCCESS) { const pj_str_t reason = pj_str("Bad SDP"); diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 3d13f2eb7..3150d977a 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -142,6 +142,8 @@ PJ_DEF(void) pjsua_msg_data_init(pjsua_msg_data *msg_data) { pj_bzero(msg_data, sizeof(*msg_data)); pj_list_init(&msg_data->hdr_list); + pjsip_media_type_init(&msg_data->multipart_ctype, NULL, NULL); + pj_list_init(&msg_data->multipart_parts); } PJ_DEF(void) pjsua_transport_config_default(pjsua_transport_config *cfg) @@ -2225,6 +2227,37 @@ void pjsua_process_msg_data(pjsip_tx_data *tdata, &msg_data->msg_body); tdata->msg->body = body; } + + /* Multipart */ + if (!pj_list_empty(&msg_data->multipart_parts) && + msg_data->multipart_ctype.type.slen) + { + pjsip_msg_body *bodies; + pjsip_multipart_part *part; + pj_str_t *boundary = NULL; + + bodies = pjsip_multipart_create(tdata->pool, + &msg_data->multipart_ctype, + boundary); + part = msg_data->multipart_parts.next; + while (part != &msg_data->multipart_parts) { + pjsip_multipart_part *part_copy; + + part_copy = pjsip_multipart_clone_part(tdata->pool, part); + pjsip_multipart_add_part(tdata->pool, bodies, part_copy); + part = part->next; + } + + if (tdata->msg->body) { + part = pjsip_multipart_create_part(tdata->pool); + part->body = tdata->msg->body; + pjsip_multipart_add_part(tdata->pool, bodies, part); + + tdata->msg->body = NULL; + } + + tdata->msg->body = bodies; + } } diff --git a/pjsip/src/test/multipart_test.c b/pjsip/src/test/multipart_test.c new file mode 100644 index 000000000..bb56b8fe7 --- /dev/null +++ b/pjsip/src/test/multipart_test.c @@ -0,0 +1,264 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include +#include + +#define THIS_FILE "" + +/* + * multipart tests + */ +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 struct test_t +{ + char *ctype; + char *csubtype; + char *boundary; + const char *msg; + verify_ptr verify; +} p_tests[] = +{ + { + /* Content-type */ + "multipart", "mixed", "12345", + + /* Body: */ + "This is the prolog, which should be ignored.\r\n" + "--12345\r\n" + "Content-Type: my/text\r\n" + "\r\n" + "Header and body\r\n" + "--12345 \t\r\n" + "Content-Type: hello/world\r\n" + "Content-Length: 0\r\n" + "\r\n" + "--12345\r\n" + "\r\n" + "Body only\r\n" + "--12345\r\n" + "Content-Type: multipart/mixed;boundary=6789\r\n" + "\r\n" + "Prolog of the subbody, should be ignored\r\n" + "--6789\r\n" + "\r\n" + "Subbody\r\n" + "--6789--\r\n" + "Epilogue of the subbody, should be ignored\r\n" + "--12345--\r\n" + "This is epilogue, which should be ignored too", + + &verify1 + } +}; + +static void init_media_type(pjsip_media_type *mt, + char *type, char *subtype, char *boundary) +{ + static pjsip_param prm; + + pjsip_media_type_init(mt, NULL, NULL); + if (type) mt->type = pj_str(type); + if (subtype) mt->subtype = pj_str(subtype); + if (boundary) { + pj_list_init(&prm); + prm.name = pj_str("boundary"); + prm.value = pj_str(boundary); + pj_list_push_back(&mt->param, &prm); + } +} + +static int verify_part(pjsip_multipart_part *part, + char *h_content_type, + char *h_content_subtype, + char *boundary, + int h_content_length, + const char *body) +{ + pjsip_ctype_hdr *ctype_hdr = NULL; + pjsip_clen_hdr *clen_hdr = NULL; + pjsip_hdr *hdr; + pj_str_t the_body; + + hdr = part->hdr.next; + while (hdr != &part->hdr) { + if (hdr->type == PJSIP_H_CONTENT_TYPE) + ctype_hdr = (pjsip_ctype_hdr*)hdr; + else if (hdr->type == PJSIP_H_CONTENT_LENGTH) + clen_hdr = (pjsip_clen_hdr*)hdr; + hdr = hdr->next; + } + + if (h_content_type) { + pjsip_media_type mt; + + if (ctype_hdr == NULL) + return -10; + + init_media_type(&mt, h_content_type, h_content_subtype, boundary); + + if (pjsip_media_type_cmp(&ctype_hdr->media, &mt) != 0) + return -20; + + } else { + if (ctype_hdr) + return -30; + } + + if (h_content_length >= 0) { + if (clen_hdr == NULL) + return -50; + if (clen_hdr->len != h_content_length) + return -60; + } else { + if (clen_hdr) + return -70; + } + + the_body.ptr = (char*)part->body->data; + the_body.slen = part->body->len; + + if (pj_strcmp2(&the_body, body) != 0) + return -90; + + return 0; +} + +static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body) +{ + pjsip_media_type mt; + pjsip_multipart_part *part; + int rc; + + /* Check content-type: "multipart/mixed;boundary=12345" */ + init_media_type(&mt, "multipart", "mixed", "12345"); + if (pjsip_media_type_cmp(&body->content_type, &mt) != 0) + return -200; + + /* First part: + "Content-Type: my/text\r\n" + "\r\n" + "Header and body\r\n" + */ + part = pjsip_multipart_get_first_part(body); + if (!part) + return -210; + if (verify_part(part, "my", "text", NULL, -1, "Header and body")) + return -220; + + /* Next part: + "Content-Type: hello/world\r\n" + "Content-Length: 0\r\n" + "\r\n" + */ + part = pjsip_multipart_get_next_part(body, part); + if (!part) + return -230; + if ((rc=verify_part(part, "hello", "world", NULL, 0, ""))!=0) { + PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc)); + return -240; + } + + /* Next part: + "\r\n" + "Body only\r\n" + */ + part = pjsip_multipart_get_next_part(body, part); + if (!part) + return -260; + if (verify_part(part, NULL, NULL, NULL, -1, "Body only")) + return -270; + + /* Next part: + "Content-Type: multipart/mixed;boundary=6789\r\n" + "\r\n" + "Prolog of the subbody, should be ignored\r\n" + "--6789\r\n" + "\r\n" + "Subbody\r\n" + "--6789--\r\n" + "Epilogue of the subbody, should be ignored\r\n" + + */ + part = pjsip_multipart_get_next_part(body, part); + if (!part) + return -280; + if ((rc=verify_part(part, "multipart", "mixed", "6789", -1, + "Prolog of the subbody, should be ignored\r\n" + "--6789\r\n" + "\r\n" + "Subbody\r\n" + "--6789--\r\n" + "Epilogue of the subbody, should be ignored"))!=0) { + PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc)); + return -290; + } + + return 0; +} + +static int parse_test(void) +{ + unsigned i; + + for (i=0; i