summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2010-08-01 09:48:51 +0000
committerBenny Prijono <bennylp@teluu.com>2010-08-01 09:48:51 +0000
commit059d687249f0f95e0b30785c418e1aa47555615a (patch)
tree792e10acf82954faca0a2b7fe937064a1ecb81d8
parent1426b8301e4d99837bd70ce73b350d03fafbfd45 (diff)
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: http://svn.pjsip.org/repos/pjproject/trunk@3243 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r--build.symbian/pjsip.mmp1
-rw-r--r--pjsip-apps/src/pjsua/pjsua_app.c40
-rw-r--r--pjsip/build/Makefile4
-rw-r--r--pjsip/include/pjsip-ua/sip_inv.h43
-rw-r--r--pjsip/include/pjsip.h1
-rw-r--r--pjsip/include/pjsip/sip_multipart.h179
-rw-r--r--pjsip/include/pjsip/sip_parser.h22
-rw-r--r--pjsip/include/pjsua-lib/pjsua.h18
-rw-r--r--pjsip/src/pjsip-ua/sip_inv.c181
-rw-r--r--pjsip/src/pjsip/sip_multipart.c644
-rw-r--r--pjsip/src/pjsip/sip_parser.c150
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c17
-rw-r--r--pjsip/src/pjsua-lib/pjsua_core.c33
-rw-r--r--pjsip/src/test/multipart_test.c264
-rw-r--r--pjsip/src/test/test.c4
-rw-r--r--pjsip/src/test/test.h2
-rw-r--r--tests/pjsua/inc_sip.py19
-rw-r--r--tests/pjsua/mod_sendto.py2
-rw-r--r--tests/pjsua/scripts-sendto/251_multipart_ok_simple.py38
-rw-r--r--tests/pjsua/scripts-sendto/252_multipart_ok_clutter.py47
-rw-r--r--tests/pjsua/scripts-sendto/260_multipart_err_no_sdp.py38
21 files changed, 1626 insertions, 121 deletions
diff --git a/build.symbian/pjsip.mmp b/build.symbian/pjsip.mmp
index 6269b47d..1fd7440c 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 c3d6ca08..f51a30cd 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<digits.slen; ++i) {
- pjsua_msg_data msg_data;
char body[80];
pjsua_msg_data_init(&msg_data);
diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile
index d825db83..4a7c9a22 100644
--- a/pjsip/build/Makefile
+++ b/pjsip/build/Makefile
@@ -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 \
diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h
index 122a148a..309cded4 100644
--- a/pjsip/include/pjsip-ua/sip_inv.h
+++ b/pjsip/include/pjsip-ua/sip_inv.h
@@ -382,6 +382,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
* occurences of events in invite sessions.
@@ -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
diff --git a/pjsip/include/pjsip.h b/pjsip/include/pjsip.h
index 4f74d2a4..7108577f 100644
--- a/pjsip/include/pjsip.h
+++ b/pjsip/include/pjsip.h
@@ -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 */
diff --git a/pjsip/include/pjsip/sip_multipart.h b/pjsip/include/pjsip/sip_multipart.h
new file mode 100644
index 00000000..12a27bad
--- /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 <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__ */
diff --git a/pjsip/include/pjsip/sip_parser.h b/pjsip/include/pjsip/sip_parser.h
index 872b87cc..8634d3b1 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 add306b9..7c770e05 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 b522b3bd..ac136825 100644
--- a/pjsip/src/pjsip-ua/sip_inv.c
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -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) {
+ sdp_info = pjsip_rdata_get_sdp_info(rdata);
+ } else {
+ sdp_info = NULL;
+ }
+
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;
-
- /* 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" */
+
+ /* 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 00000000..5d40ddb5
--- /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 <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;
+}
+
diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c
index ac8200cb..61559601 100644
--- a/pjsip/src/pjsip/sip_parser.c
+++ b/pjsip/src/pjsip/sip_parser.c
@@ -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;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index 8b695adf..d390009e 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 3d13f2eb..3150d977 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 00000000..bb56b8fe
--- /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 <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;
+}
+
diff --git a/pjsip/src/test/test.c b/pjsip/src/test/test.c
index df5523a7..01163249 100644
--- a/pjsip/src/test/test.c
+++ b/pjsip/src/test/test.c
@@ -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
diff --git a/pjsip/src/test/test.h b/pjsip/src/test/test.h
index 6c695725..18d28b75 100644
--- a/pjsip/src/test/test.h
+++ b/pjsip/src/test/test.h
@@ -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);
diff --git a/tests/pjsua/inc_sip.py b/tests/pjsua/inc_sip.py
index d64ae87f..c27c72f3 100644
--- a/tests/pjsua/inc_sip.py
+++ b/tests/pjsua/inc_sip.py
@@ -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
diff --git a/tests/pjsua/mod_sendto.py b/tests/pjsua/mod_sendto.py
index a2f45212..50dc0fe8 100644
--- a/tests/pjsua/mod_sendto.py
+++ b/tests/pjsua/mod_sendto.py
@@ -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")
diff --git a/tests/pjsua/scripts-sendto/251_multipart_ok_simple.py b/tests/pjsua/scripts-sendto/251_multipart_ok_simple.py
new file mode 100644
index 00000000..42163199
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/251_multipart_ok_simple.py
@@ -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)
+
diff --git a/tests/pjsua/scripts-sendto/252_multipart_ok_clutter.py b/tests/pjsua/scripts-sendto/252_multipart_ok_clutter.py
new file mode 100644
index 00000000..65038488
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/252_multipart_ok_clutter.py
@@ -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)
+
diff --git a/tests/pjsua/scripts-sendto/260_multipart_err_no_sdp.py b/tests/pjsua/scripts-sendto/260_multipart_err_no_sdp.py
new file mode 100644
index 00000000..7827ec11
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/260_multipart_err_no_sdp.py
@@ -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)
+