From b29a0e08a39abaa8539255738a55632d54a75dd8 Mon Sep 17 00:00:00 2001 From: Jose Lopes Date: Fri, 8 Apr 2022 11:34:11 +0100 Subject: [PATCH] res_pjsip_header_funcs: Add functions PJSIP_RESPONSE_HEADER and PJSIP_RESPONSE_HEADERS These new functions allow retrieving information from headers on 200 OK INVITE response. ASTERISK-29999 Change-Id: I264a610a9333359297a0825feb29a1bb4f4ad144 --- .../res_pjsip_header_funcs.txt | 5 + res/res_pjsip_header_funcs.c | 254 +++++++++++++++++- 2 files changed, 247 insertions(+), 12 deletions(-) create mode 100644 doc/CHANGES-staging/res_pjsip_header_funcs.txt diff --git a/doc/CHANGES-staging/res_pjsip_header_funcs.txt b/doc/CHANGES-staging/res_pjsip_header_funcs.txt new file mode 100644 index 0000000000..88946e4808 --- /dev/null +++ b/doc/CHANGES-staging/res_pjsip_header_funcs.txt @@ -0,0 +1,5 @@ +Subject: res_pjsip_header_funcs + +Add function PJSIP_RESPONSE_HEADERS() to get list of header names from 200 response, in the same way as PJSIP_HEADERS() from the request. + +Add function PJSIP_RESPONSE_HEADER() to read header from 200 response, in the same way as PJSIP_HEADER() from the request. diff --git a/res/res_pjsip_header_funcs.c b/res/res_pjsip_header_funcs.c index fa6db34a3f..cca2b78636 100644 --- a/res/res_pjsip_header_funcs.c +++ b/res/res_pjsip_header_funcs.c @@ -4,6 +4,7 @@ * Copyright (C) 2013, Fairview 5 Engineering, LLC * * George Joseph + * José Lopes * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -166,6 +167,89 @@ PJSIP_HEADER + + + Gets headers of 200 response from an outbound PJSIP channel. + + + + + + Returns instance number + of response header name. + + + + + + The name of the response header. + A * can be appended to the name + to iterate over all response headers beginning with + name. + + + + If there's more than 1 header with the same name, this specifies which header + to read. If not specified, defaults to 1 meaning + the first matching header. + + + + + + PJSIP_RESPONSE_HEADER allows you to read specific SIP headers of 200 response + from the outbound PJSIP channel. + Examples: + + exten => 1,1,Set(somevar=${PJSIP_RESPONSE_HEADER(read,From)}) + + + exten => 1,1,Set(via2=${PJSIP_RESPONSE_HEADER(read,Via,2)}) + + + exten => 1,1,Set(xhdr=${PJSIP_RESPONSE_HEADER(read,X-*,1)}) + + + If you call PJSIP_RESPONSE_HEADER in a normal dialplan context you'll be + operating on the caller's (incoming) channel which + may not be what you want. To operate on the callee's (outgoing) + channel call PJSIP_RESPONSE_HEADER in a pre-connect handler. + + + [handler] + exten => readheader,1,NoOp(PJSIP_RESPONSE_HEADER(read,X-MyHeader)) + [somecontext] + exten => 1,1,Dial(PJSIP/${EXTEN},,U(handler^readheader^1)) + + + + PJSIP_RESPONSE_HEADERS + PJSIP_HEADER + + + + + Gets the list of SIP header names from the 200 response of INVITE message. + + + + If specified, only the headers matching the given prefix are returned. + + + + Returns a comma-separated list of header names (without values) from the 200 + response of INVITE message. Multiple headers with the same name are included in the + list only once. + For example, ${PJSIP_RESPONSE_HEADERS(Co)} might return + Contact,Content-Length,Content-Type. As a practical example, + you may use ${PJSIP_RESPONSE_HEADERS(X-)} to enumerate optional + extended headers. + + + PJSIP_RESPONSE_HEADER + PJSIP_HEADERS + + ***/ @@ -180,6 +264,10 @@ AST_LIST_HEAD_NOLOCK(hdr_list, hdr_list_entry); static const struct ast_datastore_info header_datastore = { .type = "header_datastore", }; +/*! \brief Datastore for saving response headers */ +static const struct ast_datastore_info response_header_datastore = { + .type = "response_header_datastore", +}; /*! \brief Data structure used for ast_sip_push_task_wait_serializer */ struct header_data { @@ -189,6 +277,7 @@ struct header_data { char *buf; int header_number; size_t len; + const struct ast_datastore_info *header_datastore; }; /*! @@ -243,6 +332,42 @@ static int incoming_request(struct ast_sip_session *session, pjsip_rx_data * rda return 0; } +/*! + * \internal + * \brief Session supplement callback on an incoming INVITE response + * + * Retrieve the response_header_datastore from the session or create one if it doesn't exist. + * Create and initialize the list if needed. + * Insert the headers. + */ +static void incoming_response(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, response_header_datastore.type), ao2_cleanup); + pjsip_status_line status = rdata->msg_info.msg->line.status; + + /* Skip responses different of 200 OK, when 2xx is received. */ + if (session->inv_session->state != PJSIP_INV_STATE_CONNECTING || status.code!=200) { + return; + } + + if (!datastore) { + if (!(datastore = + ast_sip_session_alloc_datastore(&response_header_datastore, response_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; + } + AST_LIST_HEAD_INIT_NOLOCK((struct hdr_list *) datastore->data); + } + insert_headers(pool, (struct hdr_list *) datastore->data, rdata->msg_info.msg); + + return; +} + /*! * \internal * \brief Search list for nth occurrence of specific header. @@ -270,7 +395,7 @@ static pjsip_hdr *find_header(struct hdr_list *list, const char *header_name, /*! * \internal - * \brief Implements PJSIP_HEADERS by searching for the requested header prefix. + * \brief Implements PJSIP_HEADERS/PJSIP_RESPONSE_HEADERS by searching for the requested header prefix. * * Retrieve the header_datastore. * Search for the all matching headers. @@ -292,7 +417,7 @@ static int read_headers(void *obj) struct hdr_list *list; RAII_VAR(struct ast_datastore *, datastore, - ast_sip_session_get_datastore(data->channel->session, header_datastore.type), + ast_sip_session_get_datastore(data->channel->session, data->header_datastore->type), ao2_cleanup); if (!datastore || !datastore->data) { @@ -354,10 +479,9 @@ static int read_headers(void *obj) return 0; } - /*! * \internal - * \brief Implements PJSIP_HEADER 'read' by searching the for the requested header. + * \brief Implements PJSIP_HEADER/PJSIP_RESPONSE_HEADER 'read' by searching the for the requested header. * * Retrieve the header_datastore. * Search for the nth matching header. @@ -378,7 +502,7 @@ static int read_header(void *obj) struct hdr_list *list; int i = 1; RAII_VAR(struct ast_datastore *, datastore, - ast_sip_session_get_datastore(data->channel->session, header_datastore.type), + ast_sip_session_get_datastore(data->channel->session, data->header_datastore->type), ao2_cleanup); if (!datastore || !datastore->data) { @@ -459,11 +583,11 @@ static int add_header(void *obj) struct hdr_list *list; RAII_VAR(struct ast_datastore *, datastore, - ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup); + ast_sip_session_get_datastore(session, data->header_datastore->type), ao2_cleanup); if (!datastore) { - if (!(datastore = ast_sip_session_alloc_datastore(&header_datastore, - header_datastore.type)) + if (!(datastore = ast_sip_session_alloc_datastore(data->header_datastore, + data->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"); @@ -502,7 +626,7 @@ 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), + ast_sip_session_get_datastore(data->channel->session, data->header_datastore->type), ao2_cleanup); if (!datastore || !datastore->data) { @@ -539,7 +663,7 @@ static int remove_header(void *obj) 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), + ast_sip_session_get_datastore(data->channel->session, data->header_datastore->type), ao2_cleanup); if (!datastore || !datastore->data) { @@ -597,13 +721,47 @@ static int func_read_headers(struct ast_channel *chan, const char *function, cha header_data.header_value = NULL; header_data.buf = buf; header_data.len = len; + header_data.header_datastore = &header_datastore; return ast_sip_push_task_wait_serializer(channel->session->serializer, read_headers, &header_data); } /*! - * \brief Implements function 'read' callback. + * \brief Read list of unique SIP response headers + */ +static int func_response_read_headers(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; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(header_pattern); + ); + AST_STANDARD_APP_ARGS(args, data); + + if (!chan || 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.header_pattern)) { + ast_log(AST_LOG_ERROR, "This function requires a pattern.\n"); + return -1; + } + + header_data.channel = channel; + header_data.header_name = args.header_pattern; + header_data.header_value = NULL; + header_data.buf = buf; + header_data.len = len; + header_data.header_datastore = &response_header_datastore; + + return ast_sip_push_task_wait_serializer(channel->session->serializer, read_headers, &header_data); + +} + +/*! + * \brief Implements PJSIP_HEADER function 'read' callback. * * Valid actions are 'read' and 'remove'. */ @@ -645,6 +803,7 @@ static int func_read_header(struct ast_channel *chan, const char *function, char header_data.header_value = NULL; header_data.buf = buf; header_data.len = len; + header_data.header_datastore = &header_datastore; if (!strcasecmp(args.action, "read")) { return ast_sip_push_task_wait_serializer(channel->session->serializer, read_header, &header_data); @@ -660,7 +819,62 @@ static int func_read_header(struct ast_channel *chan, const char *function, char } /*! - * \brief Implements function 'write' callback. + * \brief Implements PJSIP_RESPONSE_HEADER function 'read' callback. + * + * Valid actions are 'read' + */ +static int func_response_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; + header_data.header_datastore = &response_header_datastore; + + if (!strcasecmp(args.action, "read")) { + return ast_sip_push_task_wait_serializer(channel->session->serializer, read_header, &header_data); + } else { + ast_log(AST_LOG_ERROR, + "Unknown action '%s' is not valid, must be 'read'.\n", + args.action); + return -1; + } +} + +/*! + * \brief Implements PJSIP_HEADER function 'write' callback. * * Valid actions are 'add', 'update' and 'remove'. */ @@ -703,6 +917,7 @@ static int func_write_header(struct ast_channel *chan, const char *cmd, char *da header_data.header_value = value; header_data.buf = NULL; header_data.len = 0; + header_data.header_datastore = &header_datastore; if (!strcasecmp(args.action, "add")) { return ast_sip_push_task_wait_serializer(channel->session->serializer, @@ -732,6 +947,16 @@ static struct ast_custom_function pjsip_headers_function = { .read = func_read_headers }; +static struct ast_custom_function pjsip_response_header_function = { + .name = "PJSIP_RESPONSE_HEADER", + .read = func_response_read_header +}; + +static struct ast_custom_function pjsip_response_headers_function = { + .name = "PJSIP_RESPONSE_HEADERS", + .read = func_response_read_headers +}; + /*! * \internal * \brief Session supplement callback for outgoing INVITE requests @@ -768,6 +993,7 @@ static struct ast_sip_session_supplement header_funcs_supplement = { .priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL - 1000, .incoming_request = incoming_request, .outgoing_request = outgoing_request, + .incoming_response = incoming_response, }; static int load_module(void) @@ -775,6 +1001,8 @@ static int load_module(void) ast_sip_session_register_supplement(&header_funcs_supplement); ast_custom_function_register(&pjsip_header_function); ast_custom_function_register(&pjsip_headers_function); + ast_custom_function_register(&pjsip_response_header_function); + ast_custom_function_register(&pjsip_response_headers_function); return AST_MODULE_LOAD_SUCCESS; } @@ -783,6 +1011,8 @@ static int unload_module(void) { ast_custom_function_unregister(&pjsip_header_function); ast_custom_function_unregister(&pjsip_headers_function); + ast_custom_function_unregister(&pjsip_response_header_function); + ast_custom_function_unregister(&pjsip_response_headers_function); ast_sip_session_unregister_supplement(&header_funcs_supplement); return 0; }