/* * This file is part of the Sofia-SIP package * * Copyright (C) 2005 Nokia Corporation. * * Contact: Pekka Pessi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /**@CFILE sip_basic.c * @brief Basic SIP headers. * * Implementation of header classes for basic SIP headers, like request and * status lines, payload, @CallID, @CSeq, @Contact, @ContentLength, @Date, * @Expires, @From, @Route, @RecordRoute, @To, and @Via. * * @author Pekka Pessi . * * @date Created: Tue Jun 13 02:57:51 2000 ppessi */ #include "config.h" /* Avoid casting sip_t to msg_pub_t and sip_header_t to msg_header_t */ #define MSG_PUB_T struct sip_s #define MSG_HDR_T union sip_header_u #include #include #include "sofia-sip/sip_parser.h" #include #include #include #include #include #include #include #include #include #include /* ====================================================================== */ /**@SIP_HEADER sip_request Request Line * * The request line is first line in a SIP request message. Its syntax defined * in @RFC3261 as follows: * * @code * Request-Line = Method SP Request-URI SP SIP-Version CRLF * Request-URI = SIP-URI / SIPS-URI / absoluteURI * absoluteURI = scheme ":" ( hier-part / opaque-part ) * hier-part = ( net-path / abs-path ) [ "?" query ] * net-path = "//" authority [ abs-path ] * abs-path = "/" path-segments * opaque-part = uric-no-slash *uric * uric = reserved / unreserved / escaped * uric-no-slash = unreserved / escaped / ";" / "?" / ":" / "@" * / "&" / "=" / "+" / "$" / "," * path-segments = segment *( "/" segment ) * segment = *pchar *( ";" param ) * param = *pchar * pchar = unreserved / escaped / * ":" / "@" / "&" / "=" / "+" / "$" / "," * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) * authority = srvr / reg-name * srvr = [ [ userinfo "@" ] hostport ] * reg-name = 1*( unreserved / escaped / "$" / "," * / ";" / ":" / "@" / "&" / "=" / "+" ) * query = *uric * SIP-Version = "SIP" "/" 1*DIGIT "." 1*DIGIT * @endcode * * The parsed request-line is stored in #sip_request_t structure. */ /**@ingroup sip_request * @typedef typedef struct sip_request_s sip_request_t; * * The structure #sip_request_t contains representation of SIP request line. * * The #sip_request_t is defined as follows: * @code * typedef struct sip_request_s { * sip_common_t rq_common[1]; // Common fragment info * sip_unknown_t *rq_next; // Link to next (dummy) * sip_method_t rq_method; // Method enum * char const *rq_method_name; // Method name * url_t rq_url[1]; // RequestURI * char const *rq_version; // Protocol version * } sip_request_t; * @endcode */ #define sip_request_insert msg_request_insert static msg_xtra_f sip_request_dup_xtra; static msg_dup_f sip_request_dup_one; #define sip_request_update NULL msg_hclass_t sip_request_class[] = SIP_HEADER_CLASS(request, NULL, "", rq_common, single_critical, request); /**Parse @ref sip_request "request line" from a a SIP message. */ issize_t sip_request_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_request_t *rq = (sip_request_t *)h; char *uri, *version; if (msg_firstline_d(s, &uri, &version) < 0 || !uri || !version || (rq->rq_method = sip_method_d(&s, &rq->rq_method_name)) < 0 || *s || url_d(rq->rq_url, uri) < 0 || sip_version_d(&version, &rq->rq_version) < 0 || *version) return -1; return 0; } /**Encode @ref sip_request "request line" of a a SIP message. */ issize_t sip_request_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { sip_request_t const *rq = (sip_request_t *)h; return snprintf(b, bsiz, "%s " URL_FORMAT_STRING " %s" CRLF, rq->rq_method_name, URL_PRINT_ARGS(rq->rq_url), rq->rq_version); } isize_t sip_request_dup_xtra(sip_header_t const *h, isize_t offset) { sip_request_t const *rq = (sip_request_t *)h; offset += url_xtra(rq->rq_url); if (!rq->rq_method) offset += MSG_STRING_SIZE(rq->rq_method_name); offset += sip_version_xtra(rq->rq_version); return offset; } /** Duplicate one request header. */ char *sip_request_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_request_t *rq = (sip_request_t *)dst; sip_request_t const *o = (sip_request_t *)src; char *end = b + xtra; URL_DUP(b, end, rq->rq_url, o->rq_url); if (!(rq->rq_method = o->rq_method)) MSG_STRING_DUP(b, rq->rq_method_name, o->rq_method_name); else rq->rq_method_name = o->rq_method_name; sip_version_dup(&b, &rq->rq_version, o->rq_version); assert(b <= end); return b; } /**@ingroup sip_request * * Create a @ref sip_request "request line" object. * * Create a request line object with * method enum @a method, method name @a name, request URI @a uri, and * protocol version @a version. The memory for the header object is * allocated from the memory home @a home. * * @param home memory home used to allocate #sip_request_t object * @param method method enum * @param name method name (required if method is not well-known) * @param uri request URI * @param version version string (defaults to "SIP/2.0" if NULL) * * @par Example * The following code fragment creates an OPTIONS request object: * @code * sip_request_t *rq; * rq = sip_request_create(home, SIP_METHOD_OPTIONS, requestURI, NULL); * @endcode * @note * If you provide an non-NULL @a version string, it is not copied. The * version string @b MUST remain constant. */ sip_request_t *sip_request_create(su_home_t *home, sip_method_t method, char const *name, url_string_t const *uri, char const *version) { size_t xtra; sip_request_t *rq; if (method) name = sip_method_name(method, name); if (!name) return NULL; if (!method) method = sip_method_code(name); xtra = url_xtra(uri->us_url) + (method ? 0 : strlen(name) + 1); rq = (sip_request_t *)sip_header_alloc(home, sip_request_class, xtra); if (rq) { char *b = (char *)(rq + 1), *end = b + xtra; rq->rq_method = method; rq->rq_method_name = name; if (!method) MSG_STRING_DUP(b, rq->rq_method_name, name); URL_DUP(b, end, rq->rq_url, uri->us_url); rq->rq_version = version ? version : SIP_VERSION_CURRENT; assert(b == end); } return rq; } /* ====================================================================== */ /**@SIP_HEADER sip_status Status Line * * The status line is first line in a response message. It is defined in * @RFC3261 as follows: * * @code * Status-Line = SIP-Version SP Status-Code SP Reason-Phrase CRLF * Status-Code = Informational * / Redirection * / Success * / Client-Error * / Server-Error * / Global-Failure * / extension-code * extension-code = 3DIGIT * Reason-Phrase = *(reserved / unreserved / escaped * / UTF8-NONASCII / UTF8-CONT / SP / HTAB) * @endcode * * The parsed status line is stored in #sip_status_t structure. */ /**@ingroup sip_status * @typedef typedef struct sip_status_s sip_status_t; * * The structure #sip_status_t contains representation of SIP * @ref sip_status "status line". * * The #sip_status_t is defined as follows: * @code * typedef struct sip_status_s { * sip_common_t st_common[1]; // Common fragment info * sip_unknown_t *st_next; // Link to next (dummy) * char const *st_version; // Protocol version * int st_status; // Status code * char const *st_phrase; // Status phrase * } sip_status_t; * @endcode */ static msg_xtra_f sip_status_dup_xtra; static msg_dup_f sip_status_dup_one; #define sip_status_insert msg_status_insert #define sip_status_update NULL msg_hclass_t sip_status_class[] = SIP_HEADER_CLASS(status, NULL, "", st_common, single_critical, status); /** Parse status line */ issize_t sip_status_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_status_t *st = (sip_status_t *)h; char *status, *phrase; unsigned long code; if (msg_firstline_d(s, &status, &phrase) < 0 || sip_version_d(&s, &st->st_version) < 0 || *s || (code = strtoul(status, &status, 10)) >= INT_MAX || *status) return -1; st->st_status = code; st->st_phrase = phrase; return 0; } issize_t sip_status_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { sip_status_t const *st = (sip_status_t *)h; int status; assert(sip_is_status(h)); status = st->st_status; if (status > 999 || status < 100) status = 0; return snprintf(b, bsiz, "%s %03u %s" CRLF, st->st_version, status, st->st_phrase); } /** Extra size of a #sip_status_t object. */ isize_t sip_status_dup_xtra(sip_header_t const *h, isize_t offset) { sip_status_t const *st = (sip_status_t *)h; offset += sip_version_xtra(st->st_version); offset += MSG_STRING_SIZE(st->st_phrase); return offset; } /** Duplicate one status header. */ char *sip_status_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_status_t *st = (sip_status_t *)dst; sip_status_t const *o = (sip_status_t *)src; char *end = b + xtra; sip_version_dup(&b, &st->st_version, o->st_version); st->st_status = o->st_status; MSG_STRING_DUP(b, st->st_phrase, o->st_phrase); assert(b <= end); (void)end; return b; } /**@ingroup sip_status * * Create a @ref sip_status "status line" object. * * @param home memory home used to allocate #sip_status_t object * @param status status code (in range 100 - 699) * @param phrase status phrase (may be NULL) * @param version version string (defaults to "SIP/2.0" if NULL) * * @note * If you provide an non-NULL @a version string, it is not copied. The * string @b MUST remain constant. * * @return * A pointer to newly created @ref sip_status "status line" * structure when successful, or NULL upon an error. */ sip_status_t *sip_status_create(su_home_t *home, unsigned status, char const *phrase, char const *version) { sip_status_t *st; if (status < 100 || status > 699) return NULL; if (phrase == NULL && (phrase = sip_status_phrase(status)) == NULL) phrase = ""; if ((st = (sip_status_t *)sip_header_alloc(home, sip_status_class, 0))) { st->st_status = status; st->st_phrase = phrase; st->st_version = version ? version : SIP_VERSION_CURRENT; } return st; } /* ====================================================================== */ /**@SIP_HEADER sip_payload Message Body * * The payload structure contains the optional message body. The message * body stored in the #sip_payload_t structure has no internal structure, * but it is accessed as a byte array. Use @ref sdp_parser "SDP parser" to * parse SDP content, for instance. * * The message body is stored in a #sip_payload_t structure. */ /**@ingroup sip_payload * @typedef typedef struct sip_payload_s sip_payload_t; * * The structure #sip_payload_t contains representation of SIP message payload. * * The #sip_payload_t is defined as follows: * @code * typedef struct sip_payload_s { * msg_common_t pl_common[1]; // Common fragment info * msg_header_t *pl_next; // Next payload (if multipart message) * char *pl_data; // Data - may contain NUL * unsigned pl_len; // Length of message payload * } sip_payload_t; * @endcode */ #define sip_payload_d msg_payload_d #define sip_payload_e msg_payload_e #define sip_payload_dup_xtra msg_payload_dup_xtra #define sip_payload_dup_one msg_payload_dup_one #define sip_payload_update NULL msg_hclass_t sip_payload_class[] = SIP_HEADER_CLASS(payload, NULL, "", pl_common, single, payload); /**@ingroup sip_payload * * Create a @ref sip_payload "SIP payload" structure. * * Create a new SIP payload structure. it * copies the given data to the the payload data, and NUL terminates it (it * allocates one extra byte for NUL). If a NULL pointer is given as @a data, * sip_payload_create() allocates and zeroes a data buffer of @a len bytes. * * @param home memory home * @param data payload data * @param len payload length * * @return A pointer to newly created * payload structure, if successful, and NULL upon an error. */ sip_payload_t *sip_payload_create(su_home_t *home, void const *data, isize_t len) { msg_hclass_t *hc = sip_payload_class; sip_header_t *h = sip_header_alloc(home, hc, len + 1); sip_payload_t *pl = (sip_payload_t *)h; if (pl) { char *b = sip_header_data(h); if (data) { memcpy(b, data, len); b[len] = 0; } else { memset(b, 0, len + 1); } h->sh_data = pl->pl_data = b; h->sh_len = pl->pl_len = len; } return pl; } /* ====================================================================== */ /**@SIP_HEADER sip_separator Separator Line * * An empty line separates message headers from the message body (payload). * In order to avoid modifying messages with integrity protection, the * separator line has its own header structure which is included in the * #sip_t structure. * * The parsed separator line is stored in #sip_separator_t structure. */ /**@ingroup sip_separator * @typedef typedef struct sip_separator_s sip_separator_t; * * The structure #sip_separator_t contains representation of separator line * between message headers and body. * * The #sip_separator_t is defined as follows: * @code * typedef struct sip_separator_s { * msg_common_t sep_common[1]; // Common fragment info * msg_header_t *sep_next; // Pointer to next header * char sep_data[4]; // NUL-terminated separator * } sip_separator_t; * @endcode */ #define sip_separator_d msg_separator_d #define sip_separator_e msg_separator_e #define sip_separator_insert msg_separator_insert msg_hclass_t sip_separator_class[] = SIP_HEADER_CLASS(separator, NULL, "", sep_common, single, any); /**@ingroup sip_separator * * Create a @ref sip_separator "SIP separator line" structure. */ sip_separator_t *sip_separator_create(su_home_t *home) { sip_separator_t *sep = (sip_separator_t *) sip_header_alloc(home, sip_separator_class, 0); if (sep) strcpy(sep->sep_data, CRLF); return sep; } /* ====================================================================== */ /**@SIP_HEADER sip_unknown Unknown Headers * * The unknown headers are handled with #sip_unknown_t structure. The * unknown header name is stored in @a un_name field and the header field * following the colon is stored in @a un_value field. * * @note It is possible to speed up parsing process by creating a parser * which does understand only a minimum number of headers. If such a parser * is used, some well-known headers are regarded as unknown and put into * list of unknown headers. */ /**@ingroup sip_unknown * @typedef typedef struct sip_unknown_s sip_unknown_t; * * The structure #sip_unknown_t contains representation of unknown headers. * * The #sip_unknown_t is defined as follows: * @code * typedef struct msg_unknown_s { * msg_common_t un_common[1]; // Common fragment info * msg_unknown_t *un_next; // Link to next unknown header * char const *un_name; // Header name * char const *un_value; // Header field value * } sip_unknown_t; * @endcode */ #define sip_unknown_dup_xtra msg_unknown_dup_xtra #define sip_unknown_dup_one msg_unknown_dup_one #define sip_unknown_update NULL msg_hclass_t sip_unknown_class[] = SIP_HEADER_CLASS(unknown, "", "", un_common, append, unknown); issize_t sip_unknown_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { return msg_unknown_d(home, h, s, slen); } issize_t sip_unknown_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { return msg_unknown_e(b, bsiz, h, flags); } /* ====================================================================== */ /**@SIP_HEADER sip_error Erroneous Headers * * The erroneous headers are stored in #sip_error_t structure. * * @note Other headers (like duplicate @ContentLength headers) may be put * into the list of erroneous headers (@c sip->sip_error). If the list of * erroneous headers is processed, the header type must be validated first * by calling sip_is_error() (or by other relevant tests). */ /**@ingroup sip_error * @typedef typedef msg_error_t sip_error_t; * The structure #sip_error_t contains representation of error headers. * * The #sip_error_t is defined as follows: * @code * typedef struct msg_error_s { * msg_common_t er_common[1]; // Common fragment info * msg_error_t *er_next; // Link to next header * char const *er_name; // Name of bad header (if any) * } sip_error_t; * @endcode */ msg_hclass_t sip_error_class[] = SIP_HEADER_CLASS(error, NULL, "", er_common, append, any); issize_t sip_error_d(su_home_t *home, msg_header_t *h, char *s, isize_t slen) { return 0; } issize_t sip_error_e(char b[], isize_t bsiz, msg_header_t const *h, int flags) { /* There is no way to encode an erroneous header */ return 0; } /* ====================================================================== */ /* * addr = ("To" | "t" | "From" | "f") ":" * ( name-addr | addr-spec ) *( ";" addr-params ) * name-addr = [ display-name ] "<" addr-spec ">" * addr-spec = SIP-URL | URI * display-name = *token | quoted-string */ /**Parse @e name-addr. * * Parses ( name-addr | addr-spec ) construct on @Contact, @From, * @To, and other compatible headers. It splits the argument string in * four parts: * * @par * @e [display-name] @e addr-spec @e [parameters] @e [comment] @e [ss] * * @param home pointer to memory home * @param inout_s pointer to pointer to string to be parsed * @param return_display value-result parameter for @e display-name * @param return_url value-result parameter for @e addr-spec * @param return_params value-result paramater for @e parameters * @param return_comment value-result parameter for @e comment * * @note After succesful call to the function @c sip_name_addr_d(), *ss * contains pointer to the first character not beloging to @e name-addr, * most probably a comma. If that character is a separator, the last parameter * may not be NUL (zero) terminated. So, after examining value of @a **ss, * the calling function @b MUST set it to NUL. * * @retval 0 if successful * @retval -1 upon an error * * @sa @From, @To, @Contact */ issize_t sip_name_addr_d(su_home_t *home, char **inout_s, char const **return_display, url_t *return_url, msg_param_t const **return_params, char const **return_comment) { char c, *s = *inout_s; char *display = NULL, *addr_spec = NULL; size_t n; if (*s == '\0') /* Empty string */ return -1; if (return_display && *s == '"') { /* Quoted string */ if (msg_quoted_d(&s, &display) == -1) return -1; /* Now, we should have a '<' in s[0] */ if (s[0] != '<') return -1; s++[0] = '\0'; /* NUL terminate quoted string... */ n = strcspn(s, ">"); addr_spec = s; s += n; if (*s) *s++ = '\0'; else return -1; } else { if (return_display) n = span_token_lws(s); else n = 0; if (s[n] == '<') { /* OK, we got a display name */ display = s; s += n + 1; /* NUL terminate display name */ while (n > 0 && IS_LWS(display[n - 1])) n--; if (n > 0) display[n] = '\0'; else display = ""; n = strcspn(s, ">"); addr_spec = s; s += n; if (*s) *s++ = '\0'; else return -1; } else { /* addr-spec only */ addr_spec = s; /**@sa * Discussion about comma, semicolon and question mark in * @RFC3261 section 20.10. */ if (return_params) n = strcspn(s, " \t,;?"); /* DO NOT accept ,;? in URL */ else /* P-Asserted-Identity and friends */ n = strcspn(s, " ,"); /* DO NOT accept , in URL */ s += n; if (IS_LWS(*s)) *s++ = '\0'; } } skip_lws(&s); if (return_display) *return_display = display; /* Now, url may still not be NUL terminated, e.g., if * it is like "Contact: url:foo,sip:bar,sip:zunk" */ c = *s; *s = '\0'; /* terminate temporarily */ /* Do not accept an empty URL */ if (addr_spec[0] == '\0') return -1; if (url_d(return_url, addr_spec) == -1) return -1; *s = c; /* return terminator */ *inout_s = s; if (c == ';' && return_params) if (msg_params_d(home, inout_s, return_params) == -1) return -1; if (**inout_s == '(' && return_comment) if (msg_comment_d(inout_s, return_comment) == -1) return -1; return 0; } /**Encode @e name-addr and parameter list. * * Encodes @e name-addr headers, like @From, @To, @CallInfo, @ErrorInfo, * @Route, and @RecordRoute. * * @param b buffer to store the encoding result * @param bsiz size of the buffer @a b * @param flags encoding flags * @param display display name encoded before the @a url (may be NULL) * @param brackets if true, use always brackets around @a url * @param url pointer to URL structure * @param params pointer to parameter list (may be NULL) * @param comment comment string encoded after others (may be NULL) * * @return * Returns number of characters in encoding, excluding the * final NUL. * * @note * The encoding result may be incomplete if the buffer size is not large * enough to store the whole encoding result. */ issize_t sip_name_addr_e(char b[], isize_t bsiz, int flags, char const *display, int brackets, url_t const url[], msg_param_t const params[], char const *comment) { int const compact = MSG_IS_COMPACT(flags); char const *u; char *b0 = b, *end = b + bsiz; brackets = brackets || display || (url && (url->url_params || url->url_headers || ((u = url->url_user) && u[strcspn(u, ";,?")]) || ((u = url->url_password) && u[strcspn(u, ",")]))); if (display && display[0]) { MSG_STRING_E(b, end, display); if (!compact) MSG_CHAR_E(b, end, ' '); } if (url) { if (brackets) MSG_CHAR_E(b, end, '<'); URL_E(b, end, url); if (brackets) MSG_CHAR_E(b, end, '>'); } MSG_PARAMS_E(b, end, params, flags); if (comment) { if (!compact) MSG_CHAR_E(b, end, ' '); MSG_CHAR_E(b, end, '('); MSG_STRING_E(b, end, comment); MSG_CHAR_E(b, end, ')'); } MSG_TERM_E(b, end); return b - b0; } /** Calculate the extra size needed to duplicate a name-addr-params construct. * * @param display display name (may be NULL) * @param addr pointer to URL structure * @param params pointer to parameter list (may be NULL) * @param offset base offset * * @retval Size of duplicated name-addr-params construct, including base offset. * * @NEW_1_12_7. */ isize_t sip_name_addr_xtra(char const *display, url_t const *addr, msg_param_t const params[], isize_t offset) { SIP_PARAMS_SIZE(offset, params); offset += SIP_STRING_SIZE(display); offset += url_xtra(addr); return offset; } /**Duplicate a name-addr-params construct. * * @param d_display value-result parameter for copied @e name (may be NULL) * @param display display name (may be NULL) * @param d_addr value-result parameter for copied @e address * @param addr pointer to URL address structure * @param d_params value-result parameter for copied parameters (may be NULL) * @param params pointer to parameter list (may be NULL) * @param b pointer to memory pool * @param xtra size of the memory pool * * @retval End of the memory area used. * * @NEW_1_12_7. */ char *sip_name_addr_dup(char const **d_display, char const *display, url_t *d_addr, url_t const *addr, msg_param_t const **d_params, msg_param_t const params[], char *b, isize_t xtra) { char *end = b + xtra; if (d_params) b = msg_params_dup(d_params, params, b, xtra); URL_DUP(b, end, d_addr, addr); if (d_display) MSG_STRING_DUP(b, *d_display, display); assert(b <= end); return b; } /** Parse @To or @From headers */ static issize_t sip_addr_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_addr_t *a = (sip_addr_t *)h; char const *comment = NULL; if (sip_name_addr_d(home, &s, &a->a_display, a->a_url, &a->a_params, &comment) == -1 || *s /* XXX - something extra? */) return -1; a->a_tag = msg_params_find(a->a_params, "tag="); return 0; } static int sip_addr_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { sip_addr_t const *a = (sip_addr_t const *)h; return sip_name_addr_e(b, bsiz, flags, a->a_display, MSG_IS_CANONIC(flags), a->a_url, a->a_params, NULL); } /** * Extra dup size of a sip_addr_t object. * * This function calculates extra size required when duplicating a * sip_addr_t object. * * @param a pointer to a sip_addr_t object * * @return * Size of strings related to sip_addr_t object. */ static isize_t sip_addr_dup_xtra(sip_header_t const *h, isize_t offset) { sip_addr_t const *a = (sip_addr_t const *)h; return sip_name_addr_xtra(a->a_display, a->a_url, a->a_params, offset); } /**@internal * Duplicate one sip_addr_t object. */ static char *sip_addr_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_addr_t *a = (sip_addr_t *)dst; sip_addr_t const *o = (sip_addr_t *)src; return sip_name_addr_dup(&a->a_display, o->a_display, a->a_url, o->a_url, &a->a_params, o->a_params, b, xtra); } /** Update parameters in sip_addr_t object */ static int sip_addr_update(msg_common_t *h, char const *name, isize_t namelen, char const *value) { sip_addr_t *a = (sip_addr_t *)h; if (name == NULL) { a->a_tag = NULL; } else if (namelen == strlen("tag") && su_casenmatch(name, "tag", namelen)) { a->a_tag = value; } return 0; } /** Create an address header object from URL */ static sip_addr_t * sip_addr_make_url(su_home_t *home, msg_hclass_t *hc, url_string_t const *us) { size_t n; sip_header_t *h; n = url_xtra(us->us_url); h = sip_header_alloc(home, hc, n); if (h) { sip_addr_t *a = (sip_to_t *)h; char *s2 = sip_header_data(h); if ((size_t)url_dup(s2, n, a->a_url, us->us_url) == n) return a; su_free(home, h); } return NULL; } /** Add a tag to address structure. */ static int sip_addr_tag(su_home_t *home, sip_addr_t *a, char const *tag) { if (a && tag) { msg_param_t value = strchr(tag, '='); if (value) value = strchr(value, '=') + 1; else value = tag; if (a->a_tag) { if (su_casematch(a->a_tag, value)) return 0; else return -1; } if (tag == value) tag = su_sprintf(home, "tag=%s", tag); else tag = su_strdup(home, tag); if (tag) if (msg_header_replace_param(home, a->a_common, tag) >= 0) return 0; } return -1; } /* ====================================================================== */ /**@SIP_HEADER sip_call_id Call-ID Header * * The @b Call-ID header uniquely identifies a particular invitation or all * registrations of a particular client. It is defined in @RFC3261 as * follows: * * @code * Call-ID = ( "Call-ID" / "i" ) HCOLON callid * callid = word [ "@" word ] * word = 1*(alphanum / "-" / "." / "!" / "%" / "*" / * "_" / "+" / "`" / "'" / "~" / "(" / ")" / "<" / ">" / * ":" / "\" / DQUOTE / "/" / "[" / "]" / "?" / "{" / "}" ) * @endcode * * The parsed Call-ID Header is stored in #sip_call_id_t structure. */ /**@ingroup sip_call_id * @typedef typedef struct sip_call_id_s sip_call_id_t; * * The structure #sip_call_id_t contains representation of SIP @CallID * header. * * The #sip_call_id_t is defined as follows: * @code * typedef struct sip_call_id_s { * sip_common_t i_common[1]; // Common fragment info * sip_call_id_t *i_next; // Link to next (dummy) * char const *i_id; // ID value * uint32_t i_hash; // Hash value (always nonzero) * } sip_call_id_t; * @endcode */ static msg_xtra_f sip_call_id_dup_xtra; static msg_dup_f sip_call_id_dup_one; #define sip_call_id_update NULL msg_hclass_t sip_call_id_class[] = SIP_HEADER_CLASS(call_id, "Call-ID", "i", i_common, single, call_id); issize_t sip_call_id_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_call_id_t *i = (sip_call_id_t *)h; i->i_id = s; /* XXX - why not sip_word_at_word_d(&s); */ i->i_hash = msg_hash_string(s); return 0; } issize_t sip_call_id_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { sip_call_id_t const *i = (sip_call_id_t *)h; size_t n = strlen(i->i_id); if (bsiz > n) strcpy(b, i->i_id); return (issize_t)n; } /** Extra size of a #sip_call_id_t object. */ isize_t sip_call_id_dup_xtra(sip_header_t const *h, isize_t offset) { sip_call_id_t const *i = (sip_call_id_t *)h; return offset + MSG_STRING_SIZE(i->i_id); } /**Duplicate a sip_call_id object. * * Duplicate (copy deeply) a single #sip_call_id_t header object. * * @param dst pointer to newly allocated header object * @param src pointer to a header object to be duplicated * @param b memory buffer used to copy external references * @param xtra number bytes in buffer @a b * * @return Pointer to the new copy of #sip_call_id_t object, or @c NULL * upon an error. */ char *sip_call_id_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_call_id_t *i = (sip_call_id_t *)dst; sip_call_id_t const *o = (sip_call_id_t *)src; char *end = b + xtra; MSG_STRING_DUP(b, i->i_id, o->i_id); if (!(i->i_hash = o->i_hash)) i->i_hash = msg_hash_string(i->i_id); assert(b <= end); (void)end; return b; } /**@ingroup sip_call_id * * Create a @CallID header object. * * Create a Call-ID header object with a new unique value. It uses * su_guid_generate() function to generate the value. If the local host name * @a domain is specified, it is prepended to the generated value instead of * local MAC address. * @param home memory home * @param domain local domain name * * @return A pointer to newly created @CallID header object when * successful or NULL upon an error. * * @sa su_guid_generate(), su_guid_sprintf() */ sip_call_id_t *sip_call_id_create(su_home_t *home, char const *domain) { sip_call_id_t *i; size_t xtra = su_guid_strlen + 1 + (domain ? strlen(domain) + 1 : 0); i = (sip_call_id_t *)sip_header_alloc(home, sip_call_id_class, xtra); if (i) { char *b; su_guid_t guid[1]; i->i_id = b = (char *)(i + 1); su_guid_generate(guid); /* * Guid looks like "NNNNNNNN-NNNN-NNNN-NNNN-XXXXXXXXXXXX" * where NNNNNNNN-NNNN-NNNN-NNNN is timestamp and XX is MAC address * (but we use usually random ID for MAC because we do not have * guid generator available for all processes within node) */ su_guid_sprintf(b, su_guid_strlen + 1, guid); /* If we have a domain name don't include MAC address at the end of guid */ if (domain) { b[8 + 5 + 5 + 5] = '@'; strcpy(b + 8 + 5 + 5 + 5 + 1, domain); } i->i_hash = msg_hash_string(i->i_id); } return i; } /* ====================================================================== */ /**@SIP_HEADER sip_cseq CSeq Header * * The CSeq header (command sequence) uniquely identifies transactions * within a dialog. It is defined in @RFC3261 as follows: * * @code * CSeq = "CSeq" HCOLON 1*DIGIT LWS Method * Method = INVITEm / ACKm / OPTIONSm / BYEm * / CANCELm / REGISTERm * / extension-method * extension-method = token * @endcode * * The parsed CSeq header is stored in #sip_cseq_t structure. */ /**@ingroup sip_cseq * @typedef typedef struct sip_cseq_s sip_cseq_t; * * The structure #sip_cseq_t contains representation of SIP @CSeq header. * * The #sip_cseq_t is defined as follows: * @code * typedef struct sip_cseq_s { * sip_common_t cs_common[1]; // Common fragment info * sip_error_t *cs_next; // Link to next (dummy) * uint32_t cs_seq; // Sequence number * sip_method_t cs_method; // Method enum * char const *cs_method_name; // Method name * } sip_cseq_t; * @endcode */ static msg_xtra_f sip_cseq_dup_xtra; static msg_dup_f sip_cseq_dup_one; #define sip_cseq_update NULL msg_hclass_t sip_cseq_class[] = SIP_HEADER_CLASS(cseq, "CSeq", "", cs_common, single, cseq); issize_t sip_cseq_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_cseq_t *cs = (sip_cseq_t *)h; if (msg_uint32_d(&s, &cs->cs_seq) < 0) return -1; if (*s) { if ((cs->cs_method = sip_method_d(&s, &cs->cs_method_name)) >= 0) return 0; } return -1; } issize_t sip_cseq_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { sip_cseq_t const *cs = (sip_cseq_t *)h; assert(sip_is_cseq(h)); return snprintf(b, bsiz, "%u %s", cs->cs_seq, cs->cs_method_name); } isize_t sip_cseq_dup_xtra(sip_header_t const *h, isize_t offset) { sip_cseq_t const *cs = (sip_cseq_t *)h; if (!cs->cs_method) return offset + MSG_STRING_SIZE(cs->cs_method_name); else return offset; } char *sip_cseq_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_cseq_t *cs = (sip_cseq_t *)dst; sip_cseq_t const *o = (sip_cseq_t *)src; char *end = b + xtra; if (!(cs->cs_method = o->cs_method)) MSG_STRING_DUP(b, cs->cs_method_name, o->cs_method_name); else cs->cs_method_name = o->cs_method_name; cs->cs_seq = o->cs_seq; assert(b <= end); (void)end; return b; } /**@ingroup sip_cseq * * Create a @CSeq header object. * * Create a @CSeq header object with the * sequence number @a seq, method enum @a method and method name @a * method_name. The memory for the header object is allocated from the * memory home @a home. * * @param home memory home * @param seq sequence number * @param method method enum * @param method_name method name (required if method is not well-known) * * @par Example * The following code fragment creates a cseq object for OPTIONS request: * @code * sip_cseq_t *cseq; * cseq = sip_cseq_create(home, agent->seq++, SIP_METHOD_OPTIONS); * @endcode * * @return * A pointer to newly created @CSeq * header object when successful or NULL upon an error. */ sip_cseq_t *sip_cseq_create(su_home_t *home, uint32_t seq, unsigned method, char const *method_name) { size_t xtra; sip_cseq_t *cs; if (method) method_name = sip_method_name((sip_method_t)method, method_name); if (method_name == NULL) return NULL; xtra = (method ? 0 : (strlen(method_name) + 1)); cs = (sip_cseq_t *)sip_header_alloc(home, sip_cseq_class, xtra); if (cs) { cs->cs_seq = seq; cs->cs_method = (sip_method_t)method; if (!method) method_name = strcpy((char *)(cs + 1), method_name); cs->cs_method_name = method_name; } return cs; } /* ====================================================================== */ /**@SIP_HEADER sip_contact Contact Header * * The Contact header contain a list of URLs used to redirect future * requests. Its syntax is defined in @RFC3261 as follows: * * @code * Contact = ("Contact" / "m" ) HCOLON * ( STAR / (contact-param *(COMMA contact-param))) * contact-param = (name-addr / addr-spec) *(SEMI contact-params) * name-addr = [ display-name ] LAQUOT addr-spec RAQUOT * addr-spec = SIP-URI / SIPS-URI / absoluteURI * display-name = *(token LWS)/ quoted-string * contact-params = c-p-q / c-p-expires * / contact-extension * c-p-q = "q" EQUAL qvalue * c-p-expires = "expires" EQUAL delta-seconds * contact-extension = generic-param * delta-seconds = 1*DIGIT * @endcode * * @note * The @RFC2543 syntax allowed . We accept it, but don't encode it. * * Each parsed Contact header field is stored in #sip_contact_t structure. */ /**@ingroup sip_contact * @typedef typedef struct sip_contact_s sip_contact_t; * * The structure #sip_contact_t contains representation of SIP @Contact * header. * * The #sip_contact_t is defined as follows: * @code * typedef struct sip_contact_s { * sip_common_t m_common[1]; // Common fragment info * sip_contact_t *m_next; // Link to next * char const *m_display; // Display name * url_t m_url[1]; // SIP URL * msg_param_t const *m_params; // List of contact-params * char const *m_comment; // Comment * * char const *m_q; // Priority * char const *m_expires; // Expiration time * } sip_contact_t; * @endcode * * @note The field @ref sip_contact_s::m_comment "m_comment" is * deprecated: it is parsed but not included in encoding. */ static msg_xtra_f sip_contact_dup_xtra; static msg_dup_f sip_contact_dup_one; static msg_update_f sip_contact_update; /** @showinitializer */ msg_hclass_t sip_contact_class[] = /* * Cut through the fog of macros * SIP_HEADER_CLASS(contact, "Contact", "m", m_params, append, contact); * and show here how the msg_hclass_t is initialized */ {{ /* hc_hash: */ sip_contact_hash, /* hc_parse: */ sip_contact_d, /* hc_print: */ sip_contact_e, /* hc_dxtra: */ sip_contact_dup_xtra, /* hc_dup_one: */ sip_contact_dup_one, /* hc_update: */ sip_contact_update, /* hc_name: */ "Contact", /* hc_len: */ sizeof("Contact") - 1, /* hc_short: */ "m", /* hc_size: */ MSG_ALIGN(sizeof(sip_contact_t), sizeof(void*)), /* hc_params: */ offsetof(sip_contact_t, m_params), /* hc_kind: */ msg_kind_append, /* hc_critical: */ 0 }}; issize_t sip_contact_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_contact_t *m; assert(h); for(;;) { m = (sip_contact_t *)h; while (*s == ',') /* Ignore empty entries (comma-whitespace) */ *s = '\0', s += span_lws(s + 1) + 1; if (sip_name_addr_d(home, &s, &m->m_display, m->m_url, &m->m_params, &m->m_comment) == -1) return -1; msg_parse_next_field_without_recursion(); } } issize_t sip_contact_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { sip_contact_t const *m = (sip_contact_t *)h; int always_lt_gt = MSG_IS_CANONIC(flags) && m->m_url->url_type != url_any; assert(sip_is_contact(h)); return sip_name_addr_e(b, bsiz, flags, m->m_display, always_lt_gt, m->m_url, m->m_params, NULL /* m->m_comment */); } isize_t sip_contact_dup_xtra(sip_header_t const *h, isize_t offset) { sip_contact_t const *m = (sip_contact_t *)h; return sip_name_addr_xtra(m->m_display, m->m_url, m->m_params, offset) + MSG_STRING_SIZE(m->m_comment); } char *sip_contact_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_contact_t *m = (sip_contact_t *)dst; sip_contact_t const *o = (sip_contact_t *)src; b = sip_name_addr_dup(&m->m_display, o->m_display, m->m_url, o->m_url, &m->m_params, o->m_params, b, xtra); MSG_STRING_DUP(b, m->m_comment, o->m_comment); return b; } /** Update parameter in #sip_contact_t */ static int sip_contact_update(msg_common_t *h, char const *name, isize_t namelen, char const *value) { sip_contact_t *m = (sip_contact_t *)h; if (name == NULL) { m->m_q = NULL; m->m_expires = NULL; } else if (namelen == 1 && su_casenmatch(name, "q", 1)) { /* XXX - check for invalid value? */ m->m_q = value; } else if (namelen == strlen("expires") && su_casenmatch(name, "expires", namelen)) { m->m_expires = value; } return 0; } /**@ingroup sip_contact * * Add a parameter to a @Contact header object * * Add a parameter to a @Contact * object. It does not copy the contents of the string @c param. * * @note This function @b does @b not duplicate @p param. * * @param home memory home * @param m #sip_contact_t object * @param param parameter string * * @return 0 when successful, and -1 upon an error. * * @deprecated Use msg_header_replace_param() directly. */ int sip_contact_add_param(su_home_t *home, sip_contact_t *m, char const *param) { return msg_header_replace_param(home, m->m_common, param); } /* ====================================================================== */ /**@SIP_HEADER sip_content_length Content-Length Header * * The Content-Length header indicates the size of the message-body in * decimal number of octets. Its syntax is defined in @RFC3261 as * follows: * * @code * Content-Length = ( "Content-Length" / "l" ) HCOLON 1*DIGIT * @endcode * * The parsed Content-Length header is stored in #sip_content_length_t * structure. */ /**@ingroup sip_content_length * @typedef typedef struct sip_content_length_s sip_content_length_t; * * The structure #sip_content_length_t contains representation of SIP * @ContentLength header. * * The #sip_content_length_t is defined as follows: * @code * typedef struct sip_content_length_s { * sip_common_t l_common[1]; // Common fragment info * sip_error_t *l_next; // Dummy link to next * uint32_t l_length; // Message body length in bytes * } sip_content_length_t; * @endcode */ msg_hclass_t sip_content_length_class[] = SIP_HEADER_CLASS(content_length, "Content-Length", "l", l_common, single_critical, any); issize_t sip_content_length_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_content_length_t *l = (sip_content_length_t *)h; issize_t retval = msg_uint32_d(&s, &l->l_length); if (*s) retval = -1; return retval; } issize_t sip_content_length_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { sip_content_length_t const *l = (sip_content_length_t const *)h; assert(sip_is_content_length(h)); return snprintf(b, bsiz, "%lu", (unsigned long)l->l_length); } /**@ingroup sip_content_length * * Create a @ContentLength header object. * * Create a @ContentLength * header object with the value @a n. The memory for the header is * allocated from the memory home @a home. * * @param home memory home * @param n payload size in bytes * * @return * A pointer to newly created @ContentLength header object when successful * or NULL upon an error. */ sip_content_length_t *sip_content_length_create(su_home_t *home, uint32_t n) { sip_content_length_t *l = (sip_content_length_t *) sip_header_alloc(home, sip_content_length_class, 0); if (l) l->l_length = n; return l; } /* ====================================================================== */ /**@SIP_HEADER sip_date Date Header * * The Date header field reflects the time when the request or response was * first sent. Its syntax is defined in @RFC3261 and @RFC2616 section 14.18 as * follows: * * @code * Date = "Date" HCOLON SIP-date * SIP-date = rfc1123-date * rfc1123-date = wkday "," SP date1 SP time SP "GMT" * date1 = 2DIGIT SP month SP 4DIGIT * ; day month year (e.g., 02 Jun 1982) * time = 2DIGIT ":" 2DIGIT ":" 2DIGIT * ; 00:00:00 - 23:59:59 * wkday = "Mon" / "Tue" / "Wed" * / "Thu" / "Fri" / "Sat" / "Sun" * month = "Jan" / "Feb" / "Mar" / "Apr" * / "May" / "Jun" / "Jul" / "Aug" * / "Sep" / "Oct" / "Nov" / "Dec" * @endcode * * The parsed Date header is stored in #sip_date_t structure. */ /**@ingroup sip_date * @typedef typedef struct sip_date_s sip_date_t; * * The structure #sip_date_t contains representation of SIP @Date header. * * The #sip_date_t is defined as follows: * @code * typedef struct sip_date_s { * sip_common_t d_common[1]; // Common fragment info * sip_date_t *d_next; // Link to next (dummy) * sip_time_t d_time; // Seconds since Jan 1, 1900 * } sip_date_t; * @endcode */ msg_hclass_t sip_date_class[] = SIP_HEADER_CLASS(date, "Date", "", d_common, single, any); issize_t sip_date_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_date_t *date = (sip_date_t *)h; if (msg_date_d((char const **)&s, &date->d_time) < 0 || *s) return -1; else return 0; } issize_t sip_date_e(char b[], isize_t bsiz, sip_header_t const *h, int f) { sip_date_t const *date = (sip_date_t *)h; return msg_date_e(b, bsiz, date->d_time); } /**@ingroup sip_date * @brief Create an @Date header object. * * Create a @Date header object with * the date @a date. If @date is 0, current time (as returned by sip_now()) * is used. * * @param home memory home * @param date date expressed as seconds since Mon, 01 Jan 1900 00:00:00 * * @return * A pointer to newly created @Date header object when successful, or NULL * upon an error. */ sip_date_t *sip_date_create(su_home_t *home, sip_time_t date) { sip_date_t *d = (sip_date_t *)sip_header_alloc(home, sip_date_class, 0); if (d) { if (date == 0) date = sip_now(); d->d_time = date; } return d; } /* ====================================================================== */ /**@SIP_HEADER sip_expires Expires Header * * The Expires header field gives the date and time after which the message * content expires. Its syntax is defined in @RFC3261 as follows: * * @code * Expires = "Expires" HCOLON delta-seconds * @endcode * * Note that the first SIP revision (@RFC2543) also allowed absolute time in * Expires. * * The parsed Expires header is stored in #sip_expires_t structure. */ /**@ingroup sip_expires * @typedef typedef struct sip_expires_s sip_expires_t; * * The structure #sip_expires_t contains representation of SIP @Expires * header. * * The #sip_expires_t is defined as follows: * @code * typedef struct sip_expires_s { * sip_common_t ex_common[1]; // Common fragment info * sip_error_t *ex_next; // Link to next (dummy) * sip_time_t ex_date; // Seconds since Jan 1, 1900 * sip_time_t ex_delta; // ...or delta seconds * } sip_expires_t; * @endcode */ msg_hclass_t sip_expires_class[] = SIP_HEADER_CLASS(expires, "Expires", "", ex_common, single, any); issize_t sip_expires_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_expires_t *expires = (sip_expires_t *)h; if (msg_date_delta_d((char const **)&s, &expires->ex_date, &expires->ex_delta) < 0 || *s) return -1; else return 0; } issize_t sip_expires_e(char b[], isize_t bsiz, sip_header_t const *h, int f) { sip_expires_t const *expires = (sip_expires_t *)h; if (expires->ex_date) return msg_date_e(b, bsiz, expires->ex_date + expires->ex_delta); else return msg_delta_e(b, bsiz, expires->ex_delta); } /**@ingroup sip_expires * @brief Create an @Expires header object. * * Create an @Expires header object with the expiration time @a delta. * * @param home memory home used to allocate #sip_expires_t structure * @param delta relative expiration time in seconds * * @return * A pointer to newly created @Expires header object when successful or NULL * upon an error. */ sip_expires_t *sip_expires_create(su_home_t *home, sip_time_t delta) { sip_expires_t *ex = (sip_expires_t *) sip_header_alloc(home, sip_expires_class, 0); if (ex) ex->ex_delta = delta; return ex; } /* ====================================================================== */ /**@SIP_HEADER sip_from From Header * * The From header indicates the initiator of the request. It is defined in * @RFC3261 as follows: * * @code * From = ( "From" / "f" ) HCOLON from-spec * from-spec = ( name-addr / addr-spec ) * *( SEMI from-param ) * from-param = tag-param / generic-param * tag-param = "tag" EQUAL token * @endcode * * * The parsed From header is stored in #sip_from_t structure. */ /**@ingroup sip_from * @typedef typedef struct sip_addr_s sip_from_t; * * The structure #sip_from_t contains representation of @From header. * * The #sip_from_t is defined as follows: * @code * typedef struct sip_addr_s { * sip_common_t a_common[1]; // Common fragment info * sip_error_t *a_next; // Link to next * char const *a_display; // Display name * url_t a_url[1]; // URL * msg_param_t const *a_params; // List of from-param * char const *a_comment; // Comment * char const *a_tag; // Tag parameter * } sip_from_t; * @endcode * */ msg_hclass_t sip_from_class[] = SIP_HEADER_CLASS(from, "From", "f", a_params, single, addr); issize_t sip_from_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { return sip_addr_d(home, h, s, slen); } issize_t sip_from_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { assert(sip_is_from(h)); return sip_addr_e(b, bsiz, h, flags); } /**@ingroup sip_from * * Create a @From header object with URL. * * @param home memory home used to allocate #sip_from_t structure * @param s pointer to the URL or a string * * @return * A pointer to newly created @From header object when successful or NULL * upon an error. */ sip_from_t * sip_from_create(su_home_t *home, url_string_t const *s) { return sip_addr_make_url(home, sip_from_class, s); } /**@ingroup sip_from * * Add a parameter to an #sip_from_t object. * * @param home memory home * @param from a pointer to #sip_from_t object * @param param parameter string * * @retval 0 when successful * @retval -1 upon an error * * @deprecated Use msg_header_replace_param() directly. */ int sip_from_add_param(su_home_t *home, sip_from_t *from, char const *param) { return msg_header_replace_param(home, from->a_common, param); } /**@ingroup sip_from * * Add a tag to a @From header. If @a tag is * identical with the existing one, nothing will be done. An error is * returned, if the header already contains a different tag. The @a tag can * be provided either as a single token ("deadbeer") or as in parameter form * ("tag=deadbeer"). In both cases the tag is duplicated using the memory * home @a home. * * @param home memory home used to allocate new tag * @param from @From header to modify * @param tag tag token or parameter to be added * * @retval 0 when successful * @retval -1 upon an error. */ int sip_from_tag(su_home_t *home, sip_from_t *from, char const *tag) { return sip_addr_tag(home, from, tag); } int sip_to_tag(su_home_t *home, sip_to_t *to, char const *tag) { return sip_addr_tag(home, to, tag); } /* ====================================================================== */ /**@SIP_HEADER sip_max_forwards Max-Forwards Header * * The Max-Forwards header is used to limit the number of proxies or * gateways that can forward the request. The Max-Forwards syntax is * defined in @RFC3261 as follows: * * @code * Max-Forwards = "Max-Forwards" HCOLON 1*DIGIT * @endcode * * * The parsed Max-Forwards header is stored in #sip_max_forwards_t structure. */ /**@ingroup sip_max_forwards * @typedef typedef struct sip_max_forwards_s sip_max_forwards_t; * * The structure #sip_max_forwards_t contains representation of SIP * @MaxForwards header. * * The #sip_max_forwards_t is defined as follows: * @code * typedef struct sip_max_forwards_s { * sip_common_t mf_common[1]; // Common fragment info * sip_error_t *mf_next; // Link to next (dummy) * unsigned long mf_count; // Digits * } sip_max_forwards_t; * @endcode */ msg_hclass_t sip_max_forwards_class[] = SIP_HEADER_CLASS(max_forwards, "Max-Forwards", "", mf_common, single, any); issize_t sip_max_forwards_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { return sip_numeric_d(home, h, s, slen); } issize_t sip_max_forwards_e(char b[], isize_t bsiz, sip_header_t const *h, int f) { assert(sip_is_max_forwards(h)); return sip_numeric_e(b, bsiz, h, f); } /* ====================================================================== */ /**@SIP_HEADER sip_min_expires Min-Expires Header * * The Min-Expires header is used to limit the number of proxies or * gateways that can forward the request. The Min-Expires syntax is * defined in @RFC3261 as follows: * * @code * Min-Expires = "Min-Expires" HCOLON delta-seconds * @endcode * * The parsed Min-Expires header is stored in #sip_min_expires_t structure. */ /**@ingroup sip_min_expires * @typedef typedef struct sip_min_expires_s sip_min_expires_t; * * The structure #sip_min_expires_t contains representation of SIP * @MinExpires header. * * The #sip_min_expires_t is defined as follows: * @code * typedef struct sip_min_expires_s { * sip_common_t me_common[1]; // Common fragment info * sip_error_t *me_next; // Link to next (dummy) * unsigned long me_delta; // Seconds * } sip_min_expires_t; * @endcode */ msg_hclass_t sip_min_expires_class[] = SIP_HEADER_CLASS(min_expires, "Min-Expires", "", me_common, single, any); issize_t sip_min_expires_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { return sip_numeric_d(home, h, s, slen); } issize_t sip_min_expires_e(char b[], isize_t bsiz, sip_header_t const *h, int f) { assert(sip_is_min_expires(h)); return sip_numeric_e(b, bsiz, h, f); } /* ====================================================================== */ /**@SIP_HEADER sip_retry_after Retry-After Header * * The Retry-After response-header field @RFC3261 section 20.33 can be used to * indicate how long the service is expected to be unavailable or when the * called party anticipates being available again. Its syntax is defined in * @RFC3261 as follows: * * @code * Retry-After = "Retry-After" HCOLON delta-seconds * [ comment ] *( SEMI retry-param ) * retry-param = ("duration" EQUAL delta-seconds) * / generic-param * @endcode * * The parsed Retry-After header is stored in #sip_retry_after_t structure. */ /**@ingroup sip_retry_after * @typedef struct sip_retry_after_s sip_retry_after_t; * * The structure #sip_retry_after_t contains representation of an * @RetryAfter header. * * The #sip_retry_after_t is defined as follows: * @code * typedef struct sip_retry_after_s { * sip_common_t af_common[1]; // Common fragment info * sip_error_t *af_next; // Link to next (dummy) * sip_time_t af_delta; // Seconds to before retry * char const *af_comment; // Comment string * msg_param_t const *af_params; // List of parameters * char const *af_duration; // Duration parameter * } sip_retry_after_t; * @endcode */ static msg_xtra_f sip_retry_after_dup_xtra; static msg_dup_f sip_retry_after_dup_one; static msg_update_f sip_retry_after_update; msg_hclass_t sip_retry_after_class[] = SIP_HEADER_CLASS(retry_after, "Retry-After", "", af_params, single, retry_after); issize_t sip_retry_after_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_retry_after_t *af = (sip_retry_after_t *)h; if ((msg_delta_d((char const **)&s, &af->af_delta) < 0) || (*s == '(' && msg_comment_d(&s, &af->af_comment) == -1) || (*s == ';' && msg_params_d(home, &s, &af->af_params) == -1) || (*s != '\0')) { if (af->af_params) su_free(home, (void *)af->af_params), af->af_params = NULL; return -1; } if (af->af_params) msg_header_update_params(h->sh_common, 0); return 0; } issize_t sip_retry_after_e(char b[], isize_t bsiz, sip_header_t const *h, int f) { sip_retry_after_t const *af = (sip_retry_after_t *)h; int const compact = MSG_IS_COMPACT(f); char *b0 = b, *end = b + bsiz; b += snprintf(b, bsiz, "%lu", af->af_delta); if (af->af_comment) { if (!compact) MSG_CHAR_E(b, end, ' '); MSG_CHAR_E(b, end, '('); MSG_STRING_E(b, end, af->af_comment); MSG_CHAR_E(b, end, ')'); if (!compact && af->af_params && af->af_params[0]) MSG_CHAR_E(b, end, ' '); } if (af->af_params) MSG_PARAMS_E(b, end, af->af_params, f); MSG_TERM_E(b, end); return b - b0; } isize_t sip_retry_after_dup_xtra(sip_header_t const *h, isize_t offset) { sip_retry_after_t const *af = (sip_retry_after_t *)h; MSG_PARAMS_SIZE(offset, af->af_params); offset += MSG_STRING_SIZE(af->af_comment); return offset; } char *sip_retry_after_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_retry_after_t *af = (sip_retry_after_t *)dst; sip_retry_after_t const *o = (sip_retry_after_t *)src; char *end = b + xtra; b = msg_params_dup(&af->af_params, o->af_params, b, xtra); MSG_STRING_DUP(b, af->af_comment, o->af_comment); af->af_delta = o->af_delta; assert(b <= end); (void)end; return b; } static int sip_retry_after_update(msg_common_t *h, char const *name, isize_t namelen, char const *value) { sip_retry_after_t *af = (sip_retry_after_t *)h; if (name == NULL) { af->af_duration = NULL; } else if (namelen == strlen("duration") && su_casenmatch(name, "duration", namelen)) { af->af_duration = value; } return 0; } /* ====================================================================== */ /**Parse a @Route or a @RecordRoute header. * * @retval 0 when successful, * @retval -1 upon an error. */ issize_t sip_any_route_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_route_t *r; assert(h); for (;;) { r = (sip_route_t *)h; while (*s == ',') { /* Ignore empty entries (comma-whitespace) */ *s = '\0', s += span_lws(s + 1) + 1; } if (sip_name_addr_d(home, &s, &r->r_display, r->r_url, &r->r_params, NULL) < 0) { return -1; } msg_parse_next_field_without_recursion(); } } issize_t sip_any_route_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { sip_route_t const *r = (sip_route_t *)h; return sip_name_addr_e(b, bsiz, flags, r->r_display, 1, r->r_url, r->r_params, NULL); } isize_t sip_any_route_dup_xtra(sip_header_t const *h, isize_t offset) { sip_route_t const *r = (sip_route_t *)h; return sip_name_addr_xtra(r->r_display, r->r_url, r->r_params, offset); } char *sip_any_route_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_route_t *r = (sip_route_t *)dst; sip_route_t const *o = (sip_route_t *)src; return sip_name_addr_dup(&r->r_display, o->r_display, r->r_url, o->r_url, &r->r_params, o->r_params, b, xtra); } #define sip_any_route_update NULL /** Create a route. * * Create a route or record-route entry * from two URLs; first one provides the URL, second maddr parameter and * port. * * @param home memory home * @param rq_url route URL * @param maddr optional route address and port * */ static sip_route_t *sip_any_route_create(su_home_t *home, msg_hclass_t *hc, url_t const *rq_url, url_t const *maddr) { sip_header_t *h; sip_route_t *rr; url_t url[1]; size_t xtra, n, n_url, n_params, n_addr; char *b, *param; *url = *rq_url; if (maddr) { url->url_port = maddr->url_port; url->url_params = NULL; } n_url = url_xtra(url); n_params = maddr && maddr->url_params ? strlen(maddr->url_params) : 0; if (maddr && (!maddr->url_params || !url_param(maddr->url_params, "maddr", NULL, 0))) n_addr = (n_params != 0) + strlen("maddr=") + strlen(maddr->url_host); else n_addr = 0; xtra = n_url + n_params + n_addr + (n_params || n_addr); h = sip_header_alloc(home, hc, xtra); if ((rr = (sip_record_route_t *)h)) { b = sip_header_data(h); n = url_dup(b, n_url, rr->r_url, url); assert(n == n_url); if (n_params || n_addr) { param = b + n_url; if (n_params) { rr->r_url->url_params = strcpy(param, maddr->url_params); param += n_params; } if (n_addr) { if (n_params) *param++ = ';'; strcpy(param, "maddr="), param += strlen("maddr="); strcpy(param, maddr->url_host), param += strlen(maddr->url_host); } assert(b + xtra == param + 1); } } return rr; } /* ====================================================================== */ /**@SIP_HEADER sip_route Route Header * * The Route headers is used to store the route set of a transaction. * The Route header is defined in @RFC3261 as follows: * * @code * Route = "Route" HCOLON route-param *(COMMA route-param) * route-param = name-addr *( SEMI rr-param ) * @endcode * * The parsed Route header is stored in #sip_route_t structure. */ /**@ingroup sip_route * @typedef typedef struct sip_route_s sip_route_t; * * The structure #sip_route_t contains representation of SIP @Route header. * * The #sip_route_t is defined as follows: * @code * typedef struct sip_route_s { * sip_common_t r_common[1]; // Common fragment info * sip_route_t *r_next; // Link to next @Route * char const *r_display; // Display name * url_t r_url[1]; // @Route URL * msg_param_t const *r_params; // List of route parameters * } sip_route_t; * @endcode */ msg_hclass_t sip_route_class[] = SIP_HEADER_CLASS(route, "Route", "", r_params, append, any_route); issize_t sip_route_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { return sip_any_route_d(home, h, s, slen); } issize_t sip_route_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { assert(sip_is_route(h)); return sip_any_route_e(b, bsiz, h, flags); } /**@ingroup sip_route * @brief Create a @Route header object. * * Creates a route entry from two URLs; first one provides the URL, second * maddr parameter and port. * * @param home memory home * @param url route URL * @param maddr optional route address and port * * @return * Returns a pointer to newly created @Route header object when successful, * or NULL upon an error. */ sip_route_t *sip_route_create(su_home_t *home, url_t const *url, url_t const *maddr) { return sip_any_route_create(home, sip_route_class, url, maddr); } /* ====================================================================== */ /**@SIP_HEADER sip_record_route Record-Route Header * * The Record-Route headers are used to establish a route for transactions * belonging to a session. The Record-Route header is defined in @RFC3261 * as follows: * * @code * Record-Route = "Record-Route" HCOLON rec-route *(COMMA rec-route) * rec-route = name-addr *( SEMI rr-param ) * rr-param = generic-param * @endcode * * The parsed Record-Route header is stored in #sip_record_route_t structure. */ /**@ingroup sip_record_route * @typedef typedef struct sip_record_route_s sip_record_route_t; * * The structure #sip_record_route_t contains representation of SIP * @RecordRoute header. * * The #sip_record_route_t is defined as follows: * @code * typedef struct sip_route_s { * sip_common_t r_common[1]; // Common fragment info * sip_record_route_t *r_next; // Link to next * char const *r_display; // Display name * url_t r_url[1]; // @RecordRoute URL * msg_param_t const *r_params; // List of route parameters * } sip_record_route_t; * @endcode */ msg_hclass_t sip_record_route_class[] = SIP_HEADER_CLASS(record_route, "Record-Route", "", r_params, prepend, any_route); issize_t sip_record_route_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { return sip_any_route_d(home, h, s, slen); } issize_t sip_record_route_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { assert(sip_is_record_route(h)); return sip_any_route_e(b, bsiz, h, flags); } /** @ingroup sip_record_route * * Create a record-route. * * Create a record-route entry from two URLs; first one provides the URL, * second maddr parameter and port. * * @param home memory home * @param rq_url route URL * @param maddr optional route address and port * * @return * A pointer to newly created @RecordRoute header object when successful or * NULL upon an error. */ sip_record_route_t *sip_record_route_create(su_home_t *home, url_t const *rq_url, url_t const *maddr) { return sip_any_route_create(home, sip_record_route_class, rq_url, maddr); } /* ====================================================================== */ /**@SIP_HEADER sip_to To Header * * The To header field specifies the "logical" recipient of the * request. It is defined in @RFC3261 as follows: * * @code * To = ( "To" / "t" ) HCOLON ( name-addr * / addr-spec ) *( SEMI to-param ) * to-param = tag-param / generic-param * @endcode * * The parsed To header is stored in #sip_to_t structure. */ /**@ingroup sip_to * @typedef typedef struct sip_addr_s sip_to_t; * * The structure #sip_to_t contains representation of @To header. * * The #sip_to_t is defined as follows: * @code * typedef struct { * sip_common_t a_common[1]; // Common fragment info * sip_error_t *a_next; // Link to next (dummy) * char const *a_display; // Display name * url_t a_url[1]; // URL * msg_param_t const *a_params; // List of to-params * char const *a_comment; // Comment * char const *a_tag; // Tag parameter * } sip_to_t; * @endcode * */ msg_hclass_t sip_to_class[] = SIP_HEADER_CLASS(to, "To", "t", a_params, single, addr); issize_t sip_to_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { return sip_addr_d(home, h, s, slen); } issize_t sip_to_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { assert(sip_is_to(h)); return sip_addr_e(b, bsiz, h, flags); } /**@ingroup sip_to * * Create a @To header object with URL. * * @param home memory home * @param url URL (string or pointer to url_t) * * @return * A pointer to newly created @To header object when successful or NULL upon * an error. */ sip_to_t * sip_to_create(su_home_t *home, url_string_t const *url) { return sip_addr_make_url(home, sip_to_class, url); } /**@ingroup sip_to * * Add a parameter to a #sip_to_t object. * * @note This function @b does @b not duplicate @p param. * * @param home memory home * @param to #sip_to_t structure * @param param parameter string * * @retval 0 when successful * @retval -1 upon an error * * @deprecated Use msg_header_replace_param() directly. */ int sip_to_add_param(su_home_t *home, sip_to_t *to, char const *param) { return msg_header_replace_param(home, to->a_common, param); } /* ====================================================================== */ /**@SIP_HEADER sip_via Via Header * * The Via header indicates the path taken by the request so far. Via * headers can be used to prevent request looping and ensure replies take * the same path as the requests. The Via syntax is defined in @RFC3261 * as follows: * * @code * Via = ( "Via" / "v" ) HCOLON via-parm *(COMMA via-parm) * via-parm = sent-protocol LWS sent-by *( SEMI via-params ) * via-params = via-ttl / via-maddr * / via-received / via-branch * / via-extension * via-ttl = "ttl" EQUAL ttl * via-maddr = "maddr" EQUAL host * via-received = "received" EQUAL (IPv4address / IPv6address) * via-branch = "branch" EQUAL token * via-extension = generic-param * sent-protocol = protocol-name SLASH protocol-version * SLASH transport * protocol-name = "SIP" / token * protocol-version = token * transport = "UDP" / "TCP" / "TLS" / "SCTP" * / other-transport * sent-by = host [ COLON port ] * ttl = 1*3DIGIT ; 0 to 255 * @endcode * * @note * The @RFC2543 syntax allowed . We accept it, but don't encode it. * * In addition to the parameters defined in @RFC3261, @RFC3486 defines a * parameter "comp": * @code * via-compression = "comp" EQUAL ("sigcomp" / other-compression) * via-params /= via-compression * @endcode * * @RFC3581 defines a parameter "rport": * @code * response-port = "rport" [EQUAL 1*DIGIT] * via-params /= response-port * @endcode * * The parsed Via header is stored in #sip_via_t structure. */ /**@ingroup sip_via * @typedef typedef struct sip_via_s sip_via_t; * * The structure #sip_via_t contains representation of SIP @Via header. * * The #sip_via_t is defined as follows: * @code * typedef struct sip_via_s { * sip_common_t v_common[1]; // Common fragment info * sip_via_t *v_next; // Link to next @Via header * char const *v_protocol; // Application and transport protocol * char const *v_host; // Hostname * char const *v_port; // Port number * msg_param_t const *v_params; // List of via-params * char const *v_comment; // Comment * * char const *v_ttl; // "ttl" parameter * char const *v_maddr; // "maddr" parameter * char const *v_received; // "received" parameter * char const *v_branch; // "branch" parameter * char const *v_comp; // "comp" parameter * char const *v_rport; // "rport" parameter * } sip_via_t; * @endcode */ static msg_xtra_f sip_via_dup_xtra; static msg_dup_f sip_via_dup_one; static msg_update_f sip_via_update; msg_hclass_t sip_via_class[] = SIP_HEADER_CLASS(via, "Via", "v", v_params, prepend, via); issize_t sip_via_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_via_t *v; assert(h); for (;;) { v = (sip_via_t *)h; while (*s == ',') /* Ignore empty entries (comma-whitespace) */ *s = '\0', s += span_lws(s + 1) + 1; /* sent-protocol sent-by *( ";" via-params ) [ comment ] */ /* Parse protocol */ if (sip_transport_d(&s, &v->v_protocol) == -1) return -1; /* Host (and port) */ if (msg_hostport_d(&s, &v->v_host, &v->v_port) == -1) return -1; /* Parameters */ if (*s == ';' && msg_params_d(home, &s, &v->v_params) == -1) return -1; /* Comment */ if (*s == '(' && msg_comment_d(&s, &v->v_comment) == -1) return -1; msg_parse_next_field_without_recursion(); } } issize_t sip_via_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { char *b0 = b, *end = b + bsiz; sip_via_t const *v = (sip_via_t *)h; assert(sip_is_via(h)); MSG_STRING_E(b, end, v->v_protocol); MSG_CHAR_E(b, end, ' '); MSG_STRING_E(b, end, v->v_host); if (v->v_port) { MSG_CHAR_E(b, end, ':'); MSG_STRING_E(b, end, v->v_port); } MSG_PARAMS_E(b, end, v->v_params, flags); #if 0 /* Comment is deprecated in @RFC3265 - accept it, but do not send */ if (v->v_comment) { if (!MSG_IS_COMPACT(flags)) MSG_CHAR_E(b, end, ' '); MSG_CHAR_E(b, end, '('); MSG_STRING_E(b, end, v->v_comment); MSG_CHAR_E(b, end, ')'); } #endif MSG_TERM_E(b, end); return b - b0; } isize_t sip_via_dup_xtra(sip_header_t const *h, isize_t offset) { sip_via_t const *v = (sip_via_t *)h; MSG_PARAMS_SIZE(offset, v->v_params); offset += sip_transport_xtra(v->v_protocol); offset += MSG_STRING_SIZE(v->v_host); offset += MSG_STRING_SIZE(v->v_port); offset += MSG_STRING_SIZE(v->v_comment); return offset; } /** Duplicate one #sip_via_t object */ char *sip_via_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_via_t *v = (sip_via_t *)dst; sip_via_t const *o = (sip_via_t *)src; char *end = b + xtra; b = msg_params_dup(&v->v_params, o->v_params, b, xtra); sip_transport_dup(&b, &v->v_protocol, o->v_protocol); MSG_STRING_DUP(b, v->v_host, o->v_host); MSG_STRING_DUP(b, v->v_port, o->v_port); MSG_STRING_DUP(b, v->v_comment, o->v_comment); assert(b <= end); (void)end; return b; } static int sip_via_update(msg_common_t *h, char const *name, isize_t namelen, char const *value) { sip_via_t *v = (sip_via_t *)h; if (name == NULL) { v->v_ttl = NULL; v->v_maddr = NULL; v->v_received = NULL; v->v_branch = NULL; v->v_rport = NULL; v->v_comp = NULL; } #define MATCH(s) (namelen == strlen(#s) && su_casenmatch(name, #s, strlen(#s))) else if (MATCH(ttl)) { v->v_ttl = value; } else if (MATCH(maddr)) { v->v_maddr = value; } else if (MATCH(received)) { v->v_received = value; } else if (MATCH(branch)) { v->v_branch = value; } else if (MATCH(rport)) { v->v_rport = value; } else if (MATCH(comp)) { v->v_comp = value; } #undef MATCH return 0; } /**@ingroup sip_via * * Add a parameter to a @Via object. * * @note This function @b does @b not duplicate @p param. * * @param home memory home * @param v #sip_via_t object * @param param parameter string * * @retval 0 when successful * @retval -1 upon an error. * * @deprecated Use msg_header_replace_param() directly. */ int sip_via_add_param(su_home_t *home, sip_via_t *v, char const *param) { return msg_header_replace_param(home, v->v_common, param); } /**@ingroup sip_via * * Create a @Via object. * * Create a new @Via header object with * given parameters. If @a transport is NULL, the default transport * "SIP/2.0/UDP" is used. A NULL-terminated list of parameters can be * specified after transport. * * @param home memory home * @param host host name * @param port protocol port number * @param transport transport protocol (default is "SIP/2.0/UDP") * @param ... NULL-terminated list of parameters * * @return * A pointer to newly created * @Via header object when successful or NULL upon an error. */ sip_via_t *sip_via_create(su_home_t *home, char const *host, char const *port, char const *transport, /* char const *params */ ...) { sip_via_t *v, via[1] = {{{{ NULL }}}}; va_list params; via->v_common->h_class = sip_via_class; if (transport) via->v_protocol = transport; else via->v_protocol = sip_transport_udp; via->v_host = host; via->v_port = port; v = (sip_via_t *)msg_header_dup_as(home, sip_via_class, (sip_header_t *)via); if (v) { char const *param; va_start(params, transport); for (param = va_arg(params, char const *); param; param = va_arg(params, char const *)) { if ((param = su_strdup(home, param))) { if (msg_header_replace_param(home, v->v_common, param) < 0) break; } } va_end(params); } return v; } /**@ingroup sip_via * * Get port number corresponding to a @Via line. * * If @a using_rport is non-null, try rport. * If *using_rport is non-zero, try rport even if is not UDP. * If is UDP, set *using_rport to zero. */ char const *sip_via_port(sip_via_t const *v, int *using_rport) { if (v == NULL) return NULL; if (using_rport) { char const *port; if (v->v_rport && !v->v_maddr /* multicast */) { if (v->v_protocol == sip_transport_udp || su_casematch(v->v_protocol, sip_transport_udp)) port = v->v_rport, *using_rport = 0; else if (*using_rport) port = v->v_rport; else port = NULL; if (port && port[0]) return port; } *using_rport = 0; /* No, we don't... */ } if (v->v_port) return v->v_port; if (sip_transport_has_tls(v->v_protocol)) return SIPS_DEFAULT_SERV; /* 5061 */ else return SIP_DEFAULT_SERV; /* 5060 */ } /**@SIP_HEADER sip_identity Identity Header * * The Identity header field specifies the "logical" recipient of the * request. It is defined in @RFC8224 with semantics shown below, * though for now it's parsed to a single 'value' field. * * @code * Identity = "Identity" HCOLON signed-identity-digest SEMI * ident-info *( SEMI ident-info-params ) * signed-identity-digest = 1*(base64-char / ".") * ident-info = "info" EQUAL ident-info-uri * ident-info-uri = LAQUOT absoluteURI RAQUOT * ident-info-params = ident-info-alg / ident-type / * ident-info-extension * ident-info-alg = "alg" EQUAL token * ident-type = "ppt" EQUAL token * ident-info-extension = generic-param * * base64-char = ALPHA / DIGIT / "/" / "+" * @endcode * * The parsed Identity header is stored in #sip_identity_t structure. */ /**@ingroup sip_identity * @typedef typedef struct sip_identity_s sip_identity_t; * * The structure #sip_identity_t contains representation of @Identity header. * * The #sip_identity_t is defined as follows: * @code * typedef struct { * sip_common_t id_common[1]; // Common fragment info * sip_identity_t *id_next; // Link to next Identity * char const *id_value; // Identity * char const *id_info; // Info param containing URL of the cert, with no '<','>' * char const *id_signed_identity_digest; // Digest * char const *id_info_alg; // Field containing alg of the cert * char const *id_info_ppt; // Field containing PASSporT Type * msg_param_t const *id_info_params; // Field containing extensions * } sip_identity_t; * @endcode * */ static msg_xtra_f sip_identity_dup_xtra; static msg_dup_f sip_identity_dup_one; static msg_update_f sip_identity_update; msg_hclass_t sip_identity_class[] = SIP_HEADER_CLASS(identity, "Identity", "", id_common, non_compact_append, identity); issize_t sip_identity_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen) { sip_identity_t *id = (sip_identity_t *)h; char const *p = NULL, *pp = NULL, *ppp = NULL, *ie = NULL; char *sid = NULL, *uri = NULL, *alg = NULL, *ppt = NULL, *ext = NULL; size_t len = 0; id->id_value = su_strdup(home, s); id->id_signed_identity_digest = s; id->id_info = NULL; sid = strchr(s, ';'); p = strstr(s, "info="); alg = strstr(s, "alg="); ppt = strstr(s, "ppt="); if (sid) { *sid = '\0'; } else { id->id_signed_identity_digest = NULL; } if (p) { ie = strchr(p, ';'); if (!ie) ie = strchr(p, '\0'); pp = strchr(p, '<'); ppp = strchr(p, '>'); // allow for a spaces between "info=" and opening '<' // extract URI from inside "<>" but allow empty - let the higher level app decide what to do about it if (ie && pp && ppp && (pp < ppp) && (ppp < ie)) { len = ppp - pp; uri = pp + 1; uri[len - 1] = '\0'; id->id_info = uri; } } if (alg) { alg += 4; id->id_info_alg = alg; alg = strchr(alg, ';'); if (alg) { *alg = '\0'; } } if (ppt) { ext = strchr(ppt, ';'); if (ext) { msg_param_t *params = su_alloc(home, sizeof(msg_param_t)); if (msg_params_d(home, &ext, ¶ms) >= 0) { id->id_info_params = params; } } ppt += 4; id->id_info_ppt = ppt; ppt = strchr(ppt, ';'); if (ppt) { *ppt = '\0'; } } return 0; } issize_t sip_identity_e(char b[], isize_t bsiz, sip_header_t const *h, int flags) { char* b0 = b, * end = b + bsiz; sip_identity_t const* id = (sip_identity_t*)h; if (id->id_signed_identity_digest) { MSG_STRING_E(b, end, id->id_signed_identity_digest); } if (id->id_info) { MSG_STRING_E(b, end, ";info=<"); MSG_STRING_E(b, end, id->id_info); MSG_STRING_E(b, end, ">"); } if (id->id_info_alg) { MSG_STRING_E(b, end, ";alg="); MSG_STRING_E(b, end, id->id_info_alg); } if (id->id_info_ppt) { MSG_STRING_E(b, end, ";ppt="); MSG_STRING_E(b, end, id->id_info_ppt); /* No need to put ';' as following MSG_PARAMS_E starts from ';' */ } if (id->id_info_params) { MSG_PARAMS_E(b, end, id->id_info_params, flags); } MSG_TERM_E(b, end); return b - b0; } isize_t sip_identity_dup_xtra(sip_header_t const *h, isize_t offset) { sip_identity_t const *id = (sip_identity_t *)h; offset += MSG_STRING_SIZE(id->id_value); offset += MSG_STRING_SIZE(id->id_signed_identity_digest); offset += MSG_STRING_SIZE(id->id_info); offset += MSG_STRING_SIZE(id->id_info_alg); offset += MSG_STRING_SIZE(id->id_info_ppt); MSG_PARAMS_SIZE(offset, id->id_info_params); return offset; } char *sip_identity_dup_one(sip_header_t *dst, sip_header_t const *src, char *b, isize_t xtra) { sip_identity_t* o_dst = dst->sh_identity; sip_identity_t const* o_src = src->sh_identity; char* end = b + xtra; b = msg_params_dup(&o_dst->id_info_params, o_src->id_info_params, b, xtra); o_dst->id_value = o_src->id_value; o_dst->id_signed_identity_digest = o_src->id_signed_identity_digest; o_dst->id_info = o_src->id_info; o_dst->id_info_alg = o_src->id_info_alg; o_dst->id_info_ppt = o_src->id_info_ppt; assert(b <= end); (void)end; return b; } static int sip_identity_update(msg_common_t *h, char const *name, isize_t namelen, char const *value) { sip_identity_t *id = (sip_identity_t *)h; if (name == NULL) { id->id_value = NULL; id->id_signed_identity_digest = NULL; id->id_info = NULL; id->id_info_alg = NULL; id->id_info_ppt = NULL; id->id_info_params = NULL; } #define MATCH(s) (namelen == strlen(#s) && su_casenmatch(name, #s, strlen(#s))) else if (MATCH(digest)) { id->id_signed_identity_digest = value; } else if (MATCH(uri)) { id->id_info = value; } else if (MATCH(alg)) { id->id_info_alg = value; } else if (MATCH(ppt)) { id->id_info_ppt = value; } else if (MATCH(value)) { id->id_value = value; } #undef MATCH return 0; }