Implemented core multipart support and support in the invite session (re #1070)

- incoming multipart message will be handled automatically
 - for testing, enable HAVE_MULTIPART_TEST in pjsua_app.c


git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@3243 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Benny Prijono 2010-08-01 09:48:51 +00:00
parent ad56eb8ee9
commit 1c1d734e05
21 changed files with 1626 additions and 121 deletions

View File

@ -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

View File

@ -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<digits.slen; ++i) {
pjsua_msg_data msg_data;
char body[80];
pjsua_msg_data_init(&msg_data);

View File

@ -36,7 +36,7 @@ export _CXXFLAGS:= $(_CFLAGS) $(CC_CXXFLAGS) $(OS_CXXFLAGS) $(M_CXXFLAGS) \
#
export PJSIP_SRCDIR = ../src/pjsip
export PJSIP_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
sip_config.o \
sip_config.o sip_multipart.o \
sip_errno.o sip_msg.o sip_parser.o sip_tel_uri.o sip_uri.o \
sip_endpoint.o sip_util.o sip_util_proxy.o \
sip_resolve.o sip_transport.o sip_transport_loop.o \
@ -86,7 +86,7 @@ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
#
export TEST_SRCDIR = ../src/test
export TEST_OBJS += dlg_core_test.o dns_test.o msg_err_test.o \
msg_logger.o msg_test.o regc_test.o \
msg_logger.o msg_test.o multipart_test.o regc_test.o \
test.o transport_loop_test.o transport_tcp_test.o \
transport_test.o transport_udp_test.o \
tsx_basic_test.o tsx_bench.o tsx_uac_test.o \

View File

@ -381,6 +381,34 @@ 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
* mechanism supports multipart message body.
*/
typedef struct pjsip_rdata_sdp_info
{
/**
* Pointer and length of the text body in the incoming message. If
* the pointer is NULL, it means the message does not contain SDP
* body.
*/
pj_str_t body;
/**
* This will contain non-zero if an invalid SDP body is found in the
* message.
*/
pj_status_t sdp_err;
/**
* A parsed and validated SDP body.
*/
pjmedia_sdp_session *sdp;
} pjsip_rdata_sdp_info;
/**
* Initialize the invite usage module and register it to the endpoint.
* The callback argument contains pointer to functions to be called on
@ -874,6 +902,21 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_body(pj_pool_t *pool,
pjmedia_sdp_session *sdp,
pjsip_msg_body **p_body);
/**
* 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.
*
* @return The SDP info.
*/
PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata);
PJ_END_DECL

View File

@ -28,6 +28,7 @@
#include <pjsip/sip_uri.h>
#include <pjsip/sip_tel_uri.h>
#include <pjsip/sip_msg.h>
#include <pjsip/sip_multipart.h>
#include <pjsip/sip_parser.h>
/* Core */

View File

@ -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 <pjsip/sip_msg.h>
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__ */

View File

@ -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);
/**

View File

@ -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;
};

View File

@ -23,6 +23,7 @@
#include <pjsip/sip_module.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_event.h>
#include <pjsip/sip_multipart.h>
#include <pjsip/sip_transaction.h>
#include <pjmedia/sdp.h>
#include <pjmedia/sdp_neg.h>
@ -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.

View File

@ -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 <pjsip/sip_multipart.h>
#include <pjsip/sip_parser.h>
#include <pjlib-util/scanner.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/errno.h>
#include <pj/except.h>
#include <pj/guid.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
#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<endptr-1 && *(curptr+1)=='-') {
/* Found the closing delimiter */
curptr += 2;
break;
}
/* Optional whitespace after delimiter */
while (curptr!=endptr && IS_SPACE(*curptr)) ++curptr;
/* Mandatory CRLF */
if (*curptr=='\r') ++curptr;
if (*curptr!='\n') {
/* Expecting a newline here */
return NULL;
}
++curptr;
/* We now in the start of the body */
start_body = curptr;
/* Find the next delimiter */
{
pj_str_t subbody;
subbody.ptr = curptr; subbody.slen = endptr - curptr;
curptr = pj_strstr(&subbody, &delim);
if (!curptr) {
/* We're really expecting end delimiter to be found. */
return NULL;
}
}
end_body = curptr;
/* The newline preceeding the delimiter is conceptually part of
* the delimiter, so trim it from the body.
*/
if (*(end_body-1) == '\n')
--end_body;
if (*(end_body-1) == '\r')
--end_body;
/* Now that we have determined the part's boundary, parse it
* to get the header and body part of the part.
*/
part = parse_multipart_part(pool, start_body, end_body - start_body,
ctype);
if (part) {
pjsip_multipart_add_part(pool, body, part);
}
}
return body;
}

View File

@ -20,6 +20,7 @@
#include <pjsip/sip_parser.h>
#include <pjsip/sip_uri.h>
#include <pjsip/sip_msg.h>
#include <pjsip/sip_multipart.h>
#include <pjsip/sip_auth_parser.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_transport.h> /* rdata structure */
@ -34,6 +35,8 @@
#include <pj/ctype.h>
#include <pj/assert.h>
#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, &param->name, &param->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;
}

View File

@ -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");

View File

@ -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;
}
}

View File

@ -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 <pjsip.h>
#include <pjlib.h>
#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<PJ_ARRAY_SIZE(p_tests); ++i) {
pj_pool_t *pool;
pjsip_media_type ctype;
pjsip_msg_body *body;
pj_str_t str;
int rc;
pool = pjsip_endpt_create_pool(endpt, NULL, 512, 512);
init_media_type(&ctype, p_tests[i].ctype, p_tests[i].csubtype,
p_tests[i].boundary);
pj_strdup2_with_null(pool, &str, p_tests[i].msg);
body = pjsip_multipart_parse(pool, str.ptr, str.slen, &ctype, 0);
if (!body)
return -100;
if (p_tests[i].verify) {
rc = p_tests[i].verify(pool, body);
} else {
rc = 0;
}
pj_pool_release(pool);
if (rc)
return rc;
}
return 0;
}
int multipart_test(void)
{
int rc;
rc = parse_test();
if (rc)
return rc;
return rc;
}

View File

@ -301,6 +301,10 @@ int test_main(void)
DO_TEST(msg_err_test());
#endif
#if INCLUDE_MULTIPART_TEST
DO_TEST(multipart_test());
#endif
#if INCLUDE_TXDATA_TEST
DO_TEST(txdata_test());
#endif

View File

@ -56,6 +56,7 @@ extern pjsip_endpoint *endpt;
#define INCLUDE_URI_TEST INCLUDE_MESSAGING_GROUP
#define INCLUDE_MSG_TEST INCLUDE_MESSAGING_GROUP
#define INCLUDE_MULTIPART_TEST INCLUDE_MESSAGING_GROUP
#define INCLUDE_TXDATA_TEST INCLUDE_MESSAGING_GROUP
#define INCLUDE_TSX_BENCH INCLUDE_MESSAGING_GROUP
#define INCLUDE_UDP_TEST INCLUDE_TRANSPORT_GROUP
@ -71,6 +72,7 @@ extern pjsip_endpoint *endpt;
int uri_test(void);
int msg_test(void);
int msg_err_test(void);
int multipart_test(void);
int txdata_test(void);
int tsx_bench(void);
int transport_udp_test(void);

View File

@ -107,7 +107,7 @@ class Dialog:
msg = msg.replace("$BRANCH", branch)
return msg
def create_req(self, method, sdp, branch="", extra_headers=""):
def create_req(self, method, sdp, branch="", extra_headers="", body=""):
if branch=="":
self.cseq = self.cseq + 1
msg = req_templ
@ -119,10 +119,14 @@ class Dialog:
if sdp!="":
msg = msg.replace("$CONTENT_LENGTH", str(len(sdp)))
msg = msg + "Content-Type: application/sdp\r\n"
msg = msg + "\r\n"
msg = msg + sdp
elif body!="":
msg = msg.replace("$CONTENT_LENGTH", str(len(body)))
msg = msg + "\r\n"
msg = msg + body
else:
msg = msg.replace("$CONTENT_LENGTH", "0")
msg = msg + "\r\n"
msg = msg + sdp
return self.update_fields(msg)
def create_response(self, request, code, reason, to_tag=""):
@ -138,9 +142,9 @@ class Dialog:
response = response + line + "\r\n"
return response
def create_invite(self, sdp, extra_headers=""):
def create_invite(self, sdp, extra_headers="", body=""):
self.inv_branch = str(random.random())
return self.create_req("INVITE", sdp, branch=self.inv_branch, extra_headers=extra_headers)
return self.create_req("INVITE", sdp, branch=self.inv_branch, extra_headers=extra_headers, body=body)
def create_ack(self, sdp="", extra_headers=""):
return self.create_req("ACK", sdp, extra_headers=extra_headers, branch=self.inv_branch)
@ -252,10 +256,12 @@ class SendtoCfg:
resp_include = []
# List of RE patterns that must NOT exist in response
resp_exclude = []
# Full (non-SDP) body
body = ""
# Constructor
def __init__(self, name, pjsua_args, sdp, resp_code,
resp_inc=[], resp_exc=[], use_tcp=False,
extra_headers="", complete_msg="",
extra_headers="", body="", complete_msg="",
enable_buffer = False):
self.complete_msg = complete_msg
self.sdp = sdp
@ -264,6 +270,7 @@ class SendtoCfg:
self.resp_exclude = resp_exc
self.use_tcp = use_tcp
self.extra_headers = extra_headers
self.body = body
self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
self.inst_param.enable_buffer = enable_buffer

View File

@ -21,7 +21,7 @@ def test_func(t):
if len(cfg.complete_msg) != 0:
req = dlg.update_fields(cfg.complete_msg)
else:
req = dlg.create_invite(cfg.sdp, cfg.extra_headers)
req = dlg.create_invite(cfg.sdp, cfg.extra_headers, cfg.body)
resp = dlg.send_request_wait(req, 10)
if resp=="":
raise TestError("Timed-out waiting for response")

View File

@ -0,0 +1,38 @@
# $Id$
import inc_sip as sip
import inc_sdp as sdp
body = \
"""
--12345
Content-Type: application/sdp
v=0
o=- 0 0 IN IP4 127.0.0.1
s=pjmedia
c=IN IP4 127.0.0.1
t=0 0
m=audio 4000 RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
--12345
Content-Type: text/plain
Hi there this is definitely not SDP
--12345--
"""
args = "--null-audio --auto-answer 200 --max-calls 1"
extra_headers = "Content-Type: multipart/mixed; boundary=12345"
include = ["v=0", "m=audio"]
exclude = []
sendto_cfg = sip.SendtoCfg( "Valid multipart/mixed body containing SDP",
pjsua_args=args, sdp="", resp_code=200,
extra_headers=extra_headers, body=body,
resp_inc=include, resp_exc=exclude)

View File

@ -0,0 +1,47 @@
# $Id$
import inc_sip as sip
import inc_sdp as sdp
body = \
"""
This is the preamble. It is to be ignored, though it
is a handy place for composition agents to include an
explanatory note to non-MIME conformant readers.
--123:45
Content-Type: text/plain
The first part is definitely not SDP
--123:45
This is implicitly typed plain US-ASCII text.
It does NOT end with a linebreak.
--123:45
Content-Type: application/sdp
v=0
o=- 0 0 IN IP4 127.0.0.1
s=pjmedia
c=IN IP4 127.0.0.1
t=0 0
m=audio 4000 RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
--123:45--
This is the epilogue. It is also to be ignored.
"""
args = "--null-audio --auto-answer 200 --max-calls 1"
extra_headers = "Content-Type: multipart/mixed; boundary=\"123:45\""
include = ["v=0", "m=audio"]
exclude = []
sendto_cfg = sip.SendtoCfg( "Valid but cluttered multipart/mixed body containing SDP",
pjsua_args=args, sdp="", resp_code=200,
extra_headers=extra_headers, body=body,
resp_inc=include, resp_exc=exclude)

View File

@ -0,0 +1,38 @@
# $Id$
import inc_sip as sip
import inc_sdp as sdp
body = \
"""
--12345
Content-Type: application/notsdp
v=0
o=- 0 0 IN IP4 127.0.0.1
s=pjmedia
c=IN IP4 127.0.0.1
t=0 0
m=audio 4000 RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
--12345
Content-Type: text/plain
Hi there this is definitely not SDP
--12345--
"""
args = "--null-audio --auto-answer 200 --max-calls 1"
extra_headers = "Content-Type: multipart/mixed; boundary=12345"
include = []
exclude = []
sendto_cfg = sip.SendtoCfg( "Multipart/mixed body without SDP",
pjsua_args=args, sdp="", resp_code=400,
extra_headers=extra_headers, body=body,
resp_inc=include, resp_exc=exclude)