mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-11-03 20:38:59 +00:00 
			
		
		
		
	Added notes about when you can read or write headers. Specifically about being able to read on the inbound channel and write on an outbound channel. ASTERISK-26063 #close Reported by: Private Name Tested by: Rusty Newton Change-Id: Ibeb64af17d1f6451028b3c29855a3f151a01d8c5
		
			
				
	
	
		
			633 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			633 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Asterisk -- An open source telephony toolkit.
 | 
						|
 *
 | 
						|
 * Copyright (C) 2013, Fairview 5 Engineering, LLC
 | 
						|
 *
 | 
						|
 * George Joseph <george.joseph@fairview5.com>
 | 
						|
 *
 | 
						|
 * See http://www.asterisk.org for more information about
 | 
						|
 * the Asterisk project. Please do not directly contact
 | 
						|
 * any of the maintainers of this project for assistance;
 | 
						|
 * the project provides a web site, mailing lists and IRC
 | 
						|
 * channels for your use.
 | 
						|
 *
 | 
						|
 * This program is free software, distributed under the terms of
 | 
						|
 * the GNU General Public License Version 2. See the LICENSE file
 | 
						|
 * at the top of the source tree.
 | 
						|
 */
 | 
						|
 | 
						|
/*** MODULEINFO
 | 
						|
	<depend>pjproject</depend>
 | 
						|
	<depend>res_pjsip</depend>
 | 
						|
	<depend>res_pjsip_session</depend>
 | 
						|
	<support_level>core</support_level>
 | 
						|
 ***/
 | 
						|
 | 
						|
#include "asterisk.h"
 | 
						|
 | 
						|
#include <pjsip.h>
 | 
						|
#include <pjsip_ua.h>
 | 
						|
 | 
						|
#include "asterisk/res_pjsip.h"
 | 
						|
#include "asterisk/res_pjsip_session.h"
 | 
						|
#include "asterisk/channel.h"
 | 
						|
#include "asterisk/pbx.h"
 | 
						|
#include "asterisk/app.h"
 | 
						|
#include "asterisk/module.h"
 | 
						|
#include "asterisk/utils.h"
 | 
						|
 | 
						|
/*** DOCUMENTATION
 | 
						|
	<function name="PJSIP_HEADER" language="en_US">
 | 
						|
		<synopsis>
 | 
						|
			Gets headers from an inbound PJSIP channel. Adds, updates or removes the
 | 
						|
			specified SIP header from an outbound PJSIP channel.
 | 
						|
		</synopsis>
 | 
						|
		<syntax>
 | 
						|
			<parameter name="action" required="true">
 | 
						|
				<enumlist>
 | 
						|
					<enum name="read"><para>Returns instance <replaceable>number</replaceable>
 | 
						|
					of header <replaceable>name</replaceable>.</para></enum>
 | 
						|
 | 
						|
					<enum name="add"><para>Adds a new header <replaceable>name</replaceable>
 | 
						|
					to this session.</para></enum>
 | 
						|
 | 
						|
					<enum name="update"><para>Updates instance <replaceable>number</replaceable>
 | 
						|
					of header <replaceable>name</replaceable> to a new value.
 | 
						|
					The header must already exist.</para></enum>
 | 
						|
 | 
						|
					<enum name="remove"><para>Removes all instances of previously added headers
 | 
						|
					whose names match <replaceable>name</replaceable>. A <literal>*</literal>
 | 
						|
					may be appended to <replaceable>name</replaceable> to remove all headers
 | 
						|
					<emphasis>beginning with</emphasis> <replaceable>name</replaceable>.
 | 
						|
					<replaceable>name</replaceable> may be set to a single <literal>*</literal>
 | 
						|
					to clear <emphasis>all</emphasis> previously added headers. In all cases,
 | 
						|
					the number of headers actually removed is returned.</para></enum>
 | 
						|
				</enumlist>
 | 
						|
			</parameter>
 | 
						|
 | 
						|
			<parameter name="name" required="true"><para>The name of the header.</para></parameter>
 | 
						|
 | 
						|
			<parameter name="number" required="false">
 | 
						|
				<para>If there's more than 1 header with the same name, this specifies which header
 | 
						|
				to read or update.  If not specified, defaults to <literal>1</literal> meaning
 | 
						|
				the first matching header.  Not valid for <literal>add</literal> or
 | 
						|
				<literal>remove</literal>.</para>
 | 
						|
			</parameter>
 | 
						|
 | 
						|
		</syntax>
 | 
						|
		<description>
 | 
						|
			<para>PJSIP_HEADER allows you to read specific SIP headers from the inbound
 | 
						|
			PJSIP channel as well as write(add, update, remove) headers on the outbound
 | 
						|
			channel. One exception is that you can read headers that you have already
 | 
						|
			added on the outbound channel.</para>
 | 
						|
			<para>Examples:</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Set 'somevar' to the value of the 'From' header.</para>
 | 
						|
			<para>exten => 1,1,Set(somevar=${PJSIP_HEADER(read,From)})</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Set 'via2' to the value of the 2nd 'Via' header.</para>
 | 
						|
			<para>exten => 1,1,Set(via2=${PJSIP_HEADER(read,Via,2)})</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Add an 'X-Myheader' header with the value of 'myvalue'.</para>
 | 
						|
			<para>exten => 1,1,Set(PJSIP_HEADER(add,X-MyHeader)=myvalue)</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Add an 'X-Myheader' header with an empty value.</para>
 | 
						|
			<para>exten => 1,1,Set(PJSIP_HEADER(add,X-MyHeader)=)</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Update the value of the header named 'X-Myheader' to 'newvalue'.</para>
 | 
						|
			<para>; 'X-Myheader' must already exist or the call will fail.</para>
 | 
						|
			<para>exten => 1,1,Set(PJSIP_HEADER(update,X-MyHeader)=newvalue)</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Remove all headers whose names exactly match 'X-MyHeader'.</para>
 | 
						|
			<para>exten => 1,1,Set(PJSIP_HEADER(remove,X-MyHeader)=)</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Remove all headers that begin with 'X-My'.</para>
 | 
						|
			<para>exten => 1,1,Set(PJSIP_HEADER(remove,X-My*)=)</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Remove all previously added headers.</para>
 | 
						|
			<para>exten => 1,1,Set(PJSIP_HEADER(remove,*)=)</para>
 | 
						|
			<para>;</para>
 | 
						|
 | 
						|
			<note><para>The <literal>remove</literal> action can be called by reading
 | 
						|
			<emphasis>or</emphasis> writing PJSIP_HEADER.</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Display the number of headers removed</para>
 | 
						|
			<para>exten => 1,1,Verbose( Removed ${PJSIP_HEADER(remove,X-MyHeader)} headers)</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Set a variable to the number of headers removed</para>
 | 
						|
			<para>exten => 1,1,Set(count=${PJSIP_HEADER(remove,X-MyHeader)})</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>; Just remove them ignoring any count</para>
 | 
						|
			<para>exten => 1,1,Set(=${PJSIP_HEADER(remove,X-MyHeader)})</para>
 | 
						|
			<para>exten => 1,1,Set(PJSIP_HEADER(remove,X-MyHeader)=)</para>
 | 
						|
			<para>;</para>
 | 
						|
			</note>
 | 
						|
 | 
						|
			<note><para>If you call PJSIP_HEADER in a normal dialplan context you'll be
 | 
						|
			operating on the <emphasis>caller's (incoming)</emphasis> channel which
 | 
						|
			may not be what you want. To operate on the <emphasis>callee's (outgoing)</emphasis>
 | 
						|
			channel call PJSIP_HEADER in a pre-dial handler. </para>
 | 
						|
			<para>Example:</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>[handler]</para>
 | 
						|
			<para>exten => addheader,1,Set(PJSIP_HEADER(add,X-MyHeader)=myvalue)</para>
 | 
						|
			<para>exten => addheader,2,Set(PJSIP_HEADER(add,X-MyHeader2)=myvalue2)</para>
 | 
						|
			<para>;</para>
 | 
						|
			<para>[somecontext]</para>
 | 
						|
			<para>exten => 1,1,Dial(PJSIP/${EXTEN},,b(handler^addheader^1))</para>
 | 
						|
			<para>;</para>
 | 
						|
			</note>
 | 
						|
		</description>
 | 
						|
	</function>
 | 
						|
 ***/
 | 
						|
 | 
						|
/*! \brief Linked list for accumulating headers */
 | 
						|
struct hdr_list_entry {
 | 
						|
	pjsip_hdr *hdr;
 | 
						|
	AST_LIST_ENTRY(hdr_list_entry) nextptr;
 | 
						|
};
 | 
						|
AST_LIST_HEAD(hdr_list, hdr_list_entry);
 | 
						|
 | 
						|
/*! \brief Destructor for hdr_list */
 | 
						|
static void hdr_list_destroy(void *obj)
 | 
						|
{
 | 
						|
	AST_LIST_HEAD_DESTROY((struct hdr_list *) obj);
 | 
						|
}
 | 
						|
 | 
						|
/*! \brief Datastore for saving headers */
 | 
						|
static const struct ast_datastore_info header_datastore = {
 | 
						|
	.type = "header_datastore",
 | 
						|
	.destroy = hdr_list_destroy,
 | 
						|
};
 | 
						|
 | 
						|
/*! \brief Data structure used for ast_sip_push_task_synchronous  */
 | 
						|
struct header_data {
 | 
						|
	struct ast_sip_channel_pvt *channel;
 | 
						|
	char *header_name;
 | 
						|
	const char *header_value;
 | 
						|
	char *buf;
 | 
						|
	int header_number;
 | 
						|
	size_t len;
 | 
						|
};
 | 
						|
 | 
						|
/*!
 | 
						|
 * \internal
 | 
						|
 * \brief Insert the header pointers into the linked list.
 | 
						|
 *
 | 
						|
 * For each header in the message, allocate a list entry,
 | 
						|
 * clone the header, then insert the entry.
 | 
						|
 */
 | 
						|
static int insert_headers(pj_pool_t * pool, struct hdr_list *list, pjsip_msg * msg)
 | 
						|
{
 | 
						|
	pjsip_hdr *hdr = msg->hdr.next;
 | 
						|
	struct hdr_list_entry *le;
 | 
						|
 | 
						|
	while (hdr && hdr != &msg->hdr) {
 | 
						|
		le = pj_pool_zalloc(pool, sizeof(struct hdr_list_entry));
 | 
						|
		le->hdr = pjsip_hdr_clone(pool, hdr);
 | 
						|
		AST_LIST_INSERT_TAIL(list, le, nextptr);
 | 
						|
		hdr = hdr->next;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*!
 | 
						|
 * \internal
 | 
						|
 * \brief Session supplement callback on an incoming INVITE request
 | 
						|
 *
 | 
						|
 * Retrieve the header_datastore from the session or create one if it doesn't exist.
 | 
						|
 * Create and initialize the list if needed.
 | 
						|
 * Insert the headers.
 | 
						|
 */
 | 
						|
static int incoming_request(struct ast_sip_session *session, pjsip_rx_data * rdata)
 | 
						|
{
 | 
						|
	pj_pool_t *pool = session->inv_session->dlg->pool;
 | 
						|
	RAII_VAR(struct ast_datastore *, datastore,
 | 
						|
			 ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup);
 | 
						|
 | 
						|
	if (!datastore) {
 | 
						|
		if (!(datastore =
 | 
						|
			  ast_sip_session_alloc_datastore(&header_datastore, header_datastore.type))
 | 
						|
			||
 | 
						|
			!(datastore->data = pj_pool_alloc(pool, sizeof(struct hdr_list))) ||
 | 
						|
			ast_sip_session_add_datastore(session, datastore)) {
 | 
						|
			ast_log(AST_LOG_ERROR, "Unable to create datastore for header functions.\n");
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
		AST_LIST_HEAD_INIT((struct hdr_list *) datastore->data);
 | 
						|
	}
 | 
						|
	insert_headers(pool, (struct hdr_list *) datastore->data, rdata->msg_info.msg);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*!
 | 
						|
 * \internal
 | 
						|
 * \brief Search list for nth occurrence of specific header.
 | 
						|
 */
 | 
						|
static pjsip_hdr *find_header(struct hdr_list *list, const char *header_name,
 | 
						|
							  int header_number)
 | 
						|
{
 | 
						|
	struct hdr_list_entry *le;
 | 
						|
	pjsip_hdr *hdr = NULL;
 | 
						|
	int i = 1;
 | 
						|
 | 
						|
	if (!list || ast_strlen_zero(header_name) || header_number < 1) {
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	AST_LIST_TRAVERSE(list, le, nextptr) {
 | 
						|
		if (pj_stricmp2(&le->hdr->name, header_name) == 0 && i++ == header_number) {
 | 
						|
			hdr = le->hdr;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return hdr;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*!
 | 
						|
 * \internal
 | 
						|
 * \brief Implements PJSIP_HEADER 'read' by searching the for the requested header.
 | 
						|
 *
 | 
						|
 * Retrieve the header_datastore.
 | 
						|
 * Search for the nth matching header.
 | 
						|
 * Validate the pjsip_hdr found.
 | 
						|
 * Parse pjsip_hdr into a name and value.
 | 
						|
 * Return the value.
 | 
						|
 */
 | 
						|
static int read_header(void *obj)
 | 
						|
{
 | 
						|
	struct header_data *data = obj;
 | 
						|
	pjsip_hdr *hdr = NULL;
 | 
						|
	char *pj_hdr_string;
 | 
						|
	size_t pj_hdr_string_len;
 | 
						|
	char *p;
 | 
						|
	size_t plen;
 | 
						|
	RAII_VAR(struct ast_datastore *, datastore,
 | 
						|
			 ast_sip_session_get_datastore(data->channel->session, header_datastore.type),
 | 
						|
			 ao2_cleanup);
 | 
						|
 | 
						|
	if (!datastore || !datastore->data) {
 | 
						|
		ast_debug(1, "There was no datastore from which to read headers.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	hdr = find_header((struct hdr_list *) datastore->data, data->header_name,
 | 
						|
					  data->header_number);
 | 
						|
 | 
						|
	if (!hdr) {
 | 
						|
		ast_debug(1, "There was no header named %s.\n", data->header_name);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	pj_hdr_string = ast_alloca(data->len);
 | 
						|
	pj_hdr_string_len = pjsip_hdr_print_on(hdr, pj_hdr_string, data->len);
 | 
						|
	pj_hdr_string[pj_hdr_string_len] = '\0';
 | 
						|
 | 
						|
	p = strchr(pj_hdr_string, ':');
 | 
						|
	if (!p) {
 | 
						|
		ast_log(AST_LOG_ERROR,
 | 
						|
				"A malformed header was returned from pjsip_hdr_print_on.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	++p;
 | 
						|
	p = ast_strip(p);
 | 
						|
	plen = strlen(p);
 | 
						|
	if (plen + 1 > data->len) {
 | 
						|
		ast_log(AST_LOG_ERROR,
 | 
						|
				"Buffer isn't big enough to hold header value.  %zu > %zu\n", plen + 1,
 | 
						|
				data->len);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	ast_copy_string(data->buf, p, data->len);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*!
 | 
						|
 * \internal
 | 
						|
 * \brief Implements PJSIP_HEADER 'add' by inserting the specified header into thge list.
 | 
						|
 *
 | 
						|
 * Retrieve the header_datastore from the session or create one if it doesn't exist.
 | 
						|
 * Create and initialize the list if needed.
 | 
						|
 * Create the pj_strs for name and value.
 | 
						|
 * Create pjsip_msg and hdr_list_entry.
 | 
						|
 * Add the entry to the list.
 | 
						|
 */
 | 
						|
static int add_header(void *obj)
 | 
						|
{
 | 
						|
	struct header_data *data = obj;
 | 
						|
	struct ast_sip_session *session = data->channel->session;
 | 
						|
	pj_pool_t *pool = session->inv_session->dlg->pool;
 | 
						|
	pj_str_t pj_header_name;
 | 
						|
	pj_str_t pj_header_value;
 | 
						|
	struct hdr_list_entry *le;
 | 
						|
	struct hdr_list *list;
 | 
						|
 | 
						|
	RAII_VAR(struct ast_datastore *, datastore,
 | 
						|
			 ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup);
 | 
						|
 | 
						|
	if (!datastore) {
 | 
						|
		if (!(datastore = ast_sip_session_alloc_datastore(&header_datastore,
 | 
						|
														  header_datastore.type))
 | 
						|
			|| !(datastore->data = pj_pool_alloc(pool, sizeof(struct hdr_list)))
 | 
						|
			|| ast_sip_session_add_datastore(session, datastore)) {
 | 
						|
			ast_log(AST_LOG_ERROR, "Unable to create datastore for header functions.\n");
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		AST_LIST_HEAD_INIT((struct hdr_list *) datastore->data);
 | 
						|
	}
 | 
						|
 | 
						|
	ast_debug(1, "Adding header %s with value %s\n", data->header_name,
 | 
						|
			  data->header_value);
 | 
						|
 | 
						|
	pj_cstr(&pj_header_name, data->header_name);
 | 
						|
	pj_cstr(&pj_header_value, data->header_value);
 | 
						|
	le = pj_pool_zalloc(pool, sizeof(struct hdr_list_entry));
 | 
						|
	le->hdr = (pjsip_hdr *) pjsip_generic_string_hdr_create(pool, &pj_header_name,
 | 
						|
															&pj_header_value);
 | 
						|
	list = datastore->data;
 | 
						|
 | 
						|
	AST_LIST_INSERT_TAIL(list, le, nextptr);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*!
 | 
						|
 * \internal
 | 
						|
 * \brief Implements PJSIP_HEADER 'update' by finding the specified header and updating it.
 | 
						|
 *
 | 
						|
 * Retrieve the header_datastore from the session or create one if it doesn't exist.
 | 
						|
 * Create and initialize the list if needed.
 | 
						|
 * Create the pj_strs for name and value.
 | 
						|
 * Create pjsip_msg and hdr_list_entry.
 | 
						|
 * Add the entry to the list.
 | 
						|
 */
 | 
						|
static int update_header(void *obj)
 | 
						|
{
 | 
						|
	struct header_data *data = obj;
 | 
						|
	pjsip_hdr *hdr = NULL;
 | 
						|
	RAII_VAR(struct ast_datastore *, datastore,
 | 
						|
			 ast_sip_session_get_datastore(data->channel->session, header_datastore.type),
 | 
						|
			 ao2_cleanup);
 | 
						|
 | 
						|
	if (!datastore || !datastore->data) {
 | 
						|
		ast_log(AST_LOG_ERROR, "No headers had been previously added to this session.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	hdr = find_header((struct hdr_list *) datastore->data, data->header_name,
 | 
						|
					  data->header_number);
 | 
						|
 | 
						|
	if (!hdr) {
 | 
						|
		ast_log(AST_LOG_ERROR, "There was no header named %s.\n", data->header_name);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	pj_strcpy2(&((pjsip_generic_string_hdr *) hdr)->hvalue, data->header_value);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*!
 | 
						|
 * \internal
 | 
						|
 * \brief Implements PJSIP_HEADER 'remove' by finding the specified header and removing it.
 | 
						|
 *
 | 
						|
 * Retrieve the header_datastore from the session.  Fail if it doesn't exist.
 | 
						|
 * If the header_name is exactly '*', the entire list is simply destroyed.
 | 
						|
 * Otherwise search the list for the matching header name which may be a partial name.
 | 
						|
 */
 | 
						|
static int remove_header(void *obj)
 | 
						|
{
 | 
						|
	struct header_data *data = obj;
 | 
						|
	size_t len = strlen(data->header_name);
 | 
						|
	struct hdr_list *list;
 | 
						|
	struct hdr_list_entry *le;
 | 
						|
	int removed_count = 0;
 | 
						|
	RAII_VAR(struct ast_datastore *, datastore,
 | 
						|
			 ast_sip_session_get_datastore(data->channel->session, header_datastore.type),
 | 
						|
			 ao2_cleanup);
 | 
						|
 | 
						|
	if (!datastore || !datastore->data) {
 | 
						|
		ast_log(AST_LOG_ERROR, "No headers had been previously added to this session.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	list = datastore->data;
 | 
						|
	AST_LIST_TRAVERSE_SAFE_BEGIN(list, le, nextptr) {
 | 
						|
		if (data->header_name[len - 1] == '*') {
 | 
						|
			if (pj_strnicmp2(&le->hdr->name, data->header_name, len - 1) == 0) {
 | 
						|
				AST_LIST_REMOVE_CURRENT(nextptr);
 | 
						|
				removed_count++;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if (pj_stricmp2(&le->hdr->name, data->header_name) == 0) {
 | 
						|
				AST_LIST_REMOVE_CURRENT(nextptr);
 | 
						|
				removed_count++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	AST_LIST_TRAVERSE_SAFE_END;
 | 
						|
 | 
						|
	if (data->buf && data->len) {
 | 
						|
		snprintf(data->buf, data->len, "%d", removed_count);
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*!
 | 
						|
 * \brief Implements function 'read' callback.
 | 
						|
 *
 | 
						|
 * Valid actions are 'read' and 'remove'.
 | 
						|
 */
 | 
						|
static int func_read_header(struct ast_channel *chan, const char *function, char *data,
 | 
						|
							char *buf, size_t len)
 | 
						|
{
 | 
						|
	struct ast_sip_channel_pvt *channel = chan ? ast_channel_tech_pvt(chan) : NULL;
 | 
						|
	struct header_data header_data;
 | 
						|
	int number;
 | 
						|
	AST_DECLARE_APP_ARGS(args,
 | 
						|
						 AST_APP_ARG(action);
 | 
						|
						 AST_APP_ARG(header_name); AST_APP_ARG(header_number););
 | 
						|
	AST_STANDARD_APP_ARGS(args, data);
 | 
						|
 | 
						|
	if (!channel || strncmp(ast_channel_name(chan), "PJSIP/", 6)) {
 | 
						|
		ast_log(LOG_ERROR, "This function requires a PJSIP channel.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (ast_strlen_zero(args.action)) {
 | 
						|
		ast_log(AST_LOG_ERROR, "This function requires an action.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	if (ast_strlen_zero(args.header_name)) {
 | 
						|
		ast_log(AST_LOG_ERROR, "This function requires a header name.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	if (!args.header_number) {
 | 
						|
		number = 1;
 | 
						|
	} else {
 | 
						|
		sscanf(args.header_number, "%30d", &number);
 | 
						|
		if (number < 1) {
 | 
						|
			number = 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	header_data.channel = channel;
 | 
						|
	header_data.header_name = args.header_name;
 | 
						|
	header_data.header_number = number;
 | 
						|
	header_data.header_value = NULL;
 | 
						|
	header_data.buf = buf;
 | 
						|
	header_data.len = len;
 | 
						|
 | 
						|
	if (stricmp(args.action, "read") == 0) {
 | 
						|
		return ast_sip_push_task_synchronous(channel->session->serializer, read_header,
 | 
						|
											 &header_data);
 | 
						|
	} else if (stricmp(args.action, "remove") == 0) {
 | 
						|
		return ast_sip_push_task_synchronous(channel->session->serializer, remove_header,
 | 
						|
											 &header_data);
 | 
						|
	} else {
 | 
						|
		ast_log(AST_LOG_ERROR,
 | 
						|
				"Unknown action \'%s\' is not valid,  Must be \'read\' or \'remove\'.\n",
 | 
						|
				args.action);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*!
 | 
						|
 * \brief Implements function 'write' callback.
 | 
						|
 *
 | 
						|
 * Valid actions are 'add', 'update' and 'remove'.
 | 
						|
 */
 | 
						|
static int func_write_header(struct ast_channel *chan, const char *cmd, char *data,
 | 
						|
							 const char *value)
 | 
						|
{
 | 
						|
	struct ast_sip_channel_pvt *channel = chan ? ast_channel_tech_pvt(chan) : NULL;
 | 
						|
	struct header_data header_data;
 | 
						|
	int header_number;
 | 
						|
	AST_DECLARE_APP_ARGS(args,
 | 
						|
						 AST_APP_ARG(action);
 | 
						|
						 AST_APP_ARG(header_name); AST_APP_ARG(header_number););
 | 
						|
	AST_STANDARD_APP_ARGS(args, data);
 | 
						|
 | 
						|
	if (!channel || strncmp(ast_channel_name(chan), "PJSIP/", 6)) {
 | 
						|
		ast_log(LOG_ERROR, "This function requires a PJSIP channel.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (ast_strlen_zero(args.action)) {
 | 
						|
		ast_log(AST_LOG_ERROR, "This function requires an action.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	if (ast_strlen_zero(args.header_name)) {
 | 
						|
		ast_log(AST_LOG_ERROR, "This function requires a header name.\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	if (!args.header_number) {
 | 
						|
		header_number = 1;
 | 
						|
	} else {
 | 
						|
		sscanf(args.header_number, "%30d", &header_number);
 | 
						|
		if (header_number < 1) {
 | 
						|
			header_number = 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	header_data.channel = channel;
 | 
						|
	header_data.header_name = args.header_name;
 | 
						|
	header_data.header_number = header_number;
 | 
						|
	header_data.header_value = value;
 | 
						|
	header_data.buf = NULL;
 | 
						|
	header_data.len = 0;
 | 
						|
 | 
						|
	if (stricmp(args.action, "add") == 0) {
 | 
						|
		return ast_sip_push_task_synchronous(channel->session->serializer, add_header,
 | 
						|
											 &header_data);
 | 
						|
	} else if (stricmp(args.action, "update") == 0) {
 | 
						|
		return ast_sip_push_task_synchronous(channel->session->serializer, update_header,
 | 
						|
											 &header_data);
 | 
						|
	} else if (stricmp(args.action, "remove") == 0) {
 | 
						|
		return ast_sip_push_task_synchronous(channel->session->serializer, remove_header,
 | 
						|
											 &header_data);
 | 
						|
	} else {
 | 
						|
		ast_log(AST_LOG_ERROR,
 | 
						|
				"Unknown action \'%s\' is not valid,  Must be \'add\', \'update\', or \'remove\'.\n",
 | 
						|
				args.action);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static struct ast_custom_function pjsip_header_function = {
 | 
						|
	.name = "PJSIP_HEADER",
 | 
						|
	.read = func_read_header,
 | 
						|
	.write = func_write_header,
 | 
						|
};
 | 
						|
 | 
						|
/*!
 | 
						|
 * \internal
 | 
						|
 * \brief Session supplement callback for outgoing INVITE requests
 | 
						|
 *
 | 
						|
 * Retrieve the header_datastore from the session.
 | 
						|
 * Add each header in the list to the outgoing message.
 | 
						|
 *
 | 
						|
 * These pjsip_hdr structures will have been created by add_header.
 | 
						|
 * Because outgoing_request may be called more than once with the same header
 | 
						|
 * list (as in the case of an authentication exchange), each pjsip_hdr structure
 | 
						|
 * MUST be newly cloned for each outgoing message.
 | 
						|
 */
 | 
						|
static void outgoing_request(struct ast_sip_session *session, pjsip_tx_data * tdata)
 | 
						|
{
 | 
						|
	pj_pool_t *pool = session->inv_session->dlg->pool;
 | 
						|
	struct hdr_list *list;
 | 
						|
	struct hdr_list_entry *le;
 | 
						|
	RAII_VAR(struct ast_datastore *, datastore,
 | 
						|
			 ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup);
 | 
						|
 | 
						|
	if (!datastore || !datastore->data ||
 | 
						|
		(session->inv_session->state >= PJSIP_INV_STATE_CONFIRMED)) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	list = datastore->data;
 | 
						|
	AST_LIST_TRAVERSE(list, le, nextptr) {
 | 
						|
		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) pjsip_hdr_clone(pool, le->hdr));
 | 
						|
	}
 | 
						|
	ast_sip_session_remove_datastore(session, datastore->uid);
 | 
						|
}
 | 
						|
 | 
						|
static struct ast_sip_session_supplement header_funcs_supplement = {
 | 
						|
	.method = "INVITE",
 | 
						|
	.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL - 1000,
 | 
						|
	.incoming_request = incoming_request,
 | 
						|
	.outgoing_request = outgoing_request,
 | 
						|
};
 | 
						|
 | 
						|
static int load_module(void)
 | 
						|
{
 | 
						|
	CHECK_PJSIP_SESSION_MODULE_LOADED();
 | 
						|
 | 
						|
	ast_sip_session_register_supplement(&header_funcs_supplement);
 | 
						|
	ast_custom_function_register(&pjsip_header_function);
 | 
						|
 | 
						|
	return AST_MODULE_LOAD_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int unload_module(void)
 | 
						|
{
 | 
						|
	ast_custom_function_unregister(&pjsip_header_function);
 | 
						|
	ast_sip_session_unregister_supplement(&header_funcs_supplement);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Header Functions",
 | 
						|
	.support_level = AST_MODULE_SUPPORT_CORE,
 | 
						|
	.load = load_module,
 | 
						|
	.unload = unload_module,
 | 
						|
	.load_pri = AST_MODPRI_APP_DEPEND,
 | 
						|
);
 |