Index: pjproject/trunk/build.symbian/pjsip.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjsip.mmp	(revision 3242)
+++ pjproject/trunk/build.symbian/pjsip.mmp	(revision 3243)
@@ -44,4 +44,5 @@
 SOURCE	sip_errno.c
 SOURCE	sip_msg.c
+SOURCE	sip_multipart.c
 SOURCE	sip_parser_wrap.cpp
 SOURCE	sip_resolve.c
Index: pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 3242)
+++ pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 3243)
@@ -27,4 +27,5 @@
 //#define STEREO_DEMO
 //#define TRANSPORT_ADAPTER_SAMPLE
+//#define HAVE_MULTIPART_TEST
 
 /* Ringtones		    US	       UK  */
@@ -2219,4 +2220,34 @@
 }
 
+#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
@@ -3433,4 +3464,5 @@
     pj_str_t tmp;
     struct input_result result;
+    pjsua_msg_data msg_data;
     pjsua_call_info call_info;
     pjsua_acc_info acc_info;
@@ -3501,5 +3533,7 @@
 	    }
 	    
-	    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;
 
@@ -3639,5 +3673,4 @@
 		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)))
@@ -3887,5 +3920,4 @@
 	    } 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 };
@@ -3942,5 +3974,4 @@
 		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 };
@@ -4084,5 +4115,4 @@
 		digits = pj_str(buf);
 		for (i=0; i<digits.slen; ++i) {
-		    pjsua_msg_data msg_data;
 		    char body[80];
 
Index: pjproject/trunk/pjsip/build/Makefile
===================================================================
--- pjproject/trunk/pjsip/build/Makefile	(revision 3242)
+++ pjproject/trunk/pjsip/build/Makefile	(revision 3243)
@@ -37,5 +37,5 @@
 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 \
@@ -87,5 +87,5 @@
 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 \
Index: pjproject/trunk/pjsip/include/pjsip-ua/sip_inv.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-ua/sip_inv.h	(revision 3242)
+++ pjproject/trunk/pjsip/include/pjsip-ua/sip_inv.h	(revision 3243)
@@ -383,4 +383,32 @@
 
 /**
+ * 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 
@@ -875,4 +903,19 @@
 					   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
Index: pjproject/trunk/pjsip/include/pjsip.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip.h	(revision 3242)
+++ pjproject/trunk/pjsip/include/pjsip.h	(revision 3243)
@@ -29,4 +29,5 @@
 #include <pjsip/sip_tel_uri.h>
 #include <pjsip/sip_msg.h>
+#include <pjsip/sip_multipart.h>
 #include <pjsip/sip_parser.h>
 
Index: pjproject/trunk/pjsip/include/pjsip/sip_multipart.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip/sip_multipart.h	(revision 3243)
+++ pjproject/trunk/pjsip/include/pjsip/sip_multipart.h	(revision 3243)
@@ -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__ */
Index: pjproject/trunk/pjsip/include/pjsip/sip_parser.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip/sip_parser.h	(revision 3242)
+++ pjproject/trunk/pjsip/include/pjsip/sip_parser.h	(revision 3243)
@@ -277,5 +277,5 @@
  *			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.
  */
@@ -288,19 +288,23 @@
  * 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.
- *
- * @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. 
+ * 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.
  *			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);
 
 
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 3242)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 3243)
@@ -1187,8 +1187,24 @@
 
     /**
-     * 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;
 };
 
Index: pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c	(revision 3242)
+++ pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c	(revision 3243)
@@ -24,4 +24,5 @@
 #include <pjsip/sip_endpoint.h>
 #include <pjsip/sip_event.h>
+#include <pjsip/sip_multipart.h>
 #include <pjsip/sip_transaction.h>
 #include <pjmedia/sdp.h>
@@ -746,4 +747,65 @@
 }
 
+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.
@@ -766,4 +828,5 @@
     pj_status_t status = PJ_SUCCESS;
     pjsip_hdr res_hdr_list;
+    pjsip_rdata_sdp_info *sdp_info;
 
     /* Init return arguments. */
@@ -822,15 +885,15 @@
      * 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);
@@ -849,11 +912,5 @@
 	}
 
-	/* 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;
@@ -865,5 +922,5 @@
 		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);
 
@@ -874,5 +931,5 @@
 	}
 
-	r_sdp = sdp;
+	r_sdp = sdp_info->sdp;
     }
 
@@ -1164,5 +1221,5 @@
     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;
 
@@ -1212,24 +1269,15 @@
     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);
 						
@@ -1375,6 +1423,6 @@
     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;
@@ -1528,4 +1576,5 @@
 }
 
+
 /*
  * Initiate SDP negotiation in the SDP negotiator.
@@ -1576,9 +1625,7 @@
 {
     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. */
@@ -1590,7 +1637,6 @@
     }
 
-    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;
@@ -1661,19 +1707,13 @@
     }
 
-    /* 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 */
@@ -1690,15 +1730,14 @@
 	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;
 	}
@@ -1708,5 +1747,5 @@
 	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);
 
 	}
@@ -1725,11 +1764,9 @@
 
 	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;
 	}
@@ -3856,4 +3893,5 @@
 	    pjsip_tx_data *tdata;
 	    pj_status_t status;
+	    pjsip_rdata_sdp_info *sdp_info;
 	    pjsip_status_code st_code;
 
@@ -3926,5 +3964,6 @@
 	     * 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 {
Index: pjproject/trunk/pjsip/src/pjsip/sip_multipart.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip/sip_multipart.c	(revision 3243)
+++ pjproject/trunk/pjsip/src/pjsip/sip_multipart.c	(revision 3243)
@@ -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;
+}
+
Index: pjproject/trunk/pjsip/src/pjsip/sip_parser.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip/sip_parser.c	(revision 3242)
+++ pjproject/trunk/pjsip/src/pjsip/sip_parser.c	(revision 3243)
@@ -21,4 +21,5 @@
 #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>
@@ -34,4 +35,6 @@
 #include <pj/ctype.h>
 #include <pj/assert.h>
+
+#define THIS_FILE	    "sip_parser.c"
 
 #define ALNUM
@@ -269,11 +272,4 @@
 }
 
-/* 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()
@@ -1053,13 +1049,25 @@
 	 */
 	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;
-
-	    body->data = scanner->curptr;
-	    body->len = scanner->end - scanner->curptr;
-	    body->print_body = &pjsip_print_text_body;
-	    body->clone_data = &pjsip_clone_text_data;
+	    /* 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;
+
+	    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;
@@ -1848,7 +1856,7 @@
     /* 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);
     }
 
@@ -2265,2 +2273,108 @@
 }
 
+/* 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;
+}
+
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 3242)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 3243)
@@ -646,5 +646,5 @@
     int call_id = -1;
     int sip_err_code;
-    pjmedia_sdp_session *offer, *answer;
+    pjmedia_sdp_session *offer=NULL, *answer;
     pj_status_t status;
 
@@ -766,11 +766,12 @@
     /* 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) {
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c	(revision 3242)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c	(revision 3243)
@@ -143,4 +143,6 @@
     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);
 }
 
@@ -2226,4 +2228,35 @@
 	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;
+    }
 }
 
Index: pjproject/trunk/pjsip/src/test/multipart_test.c
===================================================================
--- pjproject/trunk/pjsip/src/test/multipart_test.c	(revision 3243)
+++ pjproject/trunk/pjsip/src/test/multipart_test.c	(revision 3243)
@@ -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;
+}
+
Index: pjproject/trunk/pjsip/src/test/test.c
===================================================================
--- pjproject/trunk/pjsip/src/test/test.c	(revision 3242)
+++ pjproject/trunk/pjsip/src/test/test.c	(revision 3243)
@@ -302,4 +302,8 @@
 #endif
 
+#if INCLUDE_MULTIPART_TEST
+    DO_TEST(multipart_test());
+#endif
+
 #if INCLUDE_TXDATA_TEST
     DO_TEST(txdata_test());
Index: pjproject/trunk/pjsip/src/test/test.h
===================================================================
--- pjproject/trunk/pjsip/src/test/test.h	(revision 3242)
+++ pjproject/trunk/pjsip/src/test/test.h	(revision 3243)
@@ -57,4 +57,5 @@
 #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
@@ -72,4 +73,5 @@
 int msg_test(void);
 int msg_err_test(void);
+int multipart_test(void);
 int txdata_test(void);
 int tsx_bench(void);
Index: pjproject/trunk/tests/pjsua/inc_sip.py
===================================================================
--- pjproject/trunk/tests/pjsua/inc_sip.py	(revision 3242)
+++ pjproject/trunk/tests/pjsua/inc_sip.py	(revision 3243)
@@ -108,5 +108,5 @@
 		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
@@ -120,8 +120,12 @@
 			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)
 
@@ -139,7 +143,7 @@
 		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=""):
@@ -253,8 +257,10 @@
 	# 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
@@ -265,4 +271,5 @@
 		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 
Index: pjproject/trunk/tests/pjsua/mod_sendto.py
===================================================================
--- pjproject/trunk/tests/pjsua/mod_sendto.py	(revision 3242)
+++ pjproject/trunk/tests/pjsua/mod_sendto.py	(revision 3243)
@@ -22,5 +22,5 @@
 		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=="":
Index: pjproject/trunk/tests/pjsua/scripts-sendto/251_multipart_ok_simple.py
===================================================================
--- pjproject/trunk/tests/pjsua/scripts-sendto/251_multipart_ok_simple.py	(revision 3243)
+++ pjproject/trunk/tests/pjsua/scripts-sendto/251_multipart_ok_simple.py	(revision 3243)
@@ -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)
+
Index: pjproject/trunk/tests/pjsua/scripts-sendto/252_multipart_ok_clutter.py
===================================================================
--- pjproject/trunk/tests/pjsua/scripts-sendto/252_multipart_ok_clutter.py	(revision 3243)
+++ pjproject/trunk/tests/pjsua/scripts-sendto/252_multipart_ok_clutter.py	(revision 3243)
@@ -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)
+
Index: pjproject/trunk/tests/pjsua/scripts-sendto/260_multipart_err_no_sdp.py
===================================================================
--- pjproject/trunk/tests/pjsua/scripts-sendto/260_multipart_err_no_sdp.py	(revision 3243)
+++ pjproject/trunk/tests/pjsua/scripts-sendto/260_multipart_err_no_sdp.py	(revision 3243)
@@ -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)
+
