freeswitch/libs/sofia-sip/libsofia-sip-ua/url/url.c

2147 lines
51 KiB
C
Raw Blame History

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* 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 url.c
*
* Implementation of basic URL parsing and handling.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
*
* @date Created: Thu Jun 29 22:44:37 2000 ppessi
*/
#include "config.h"
#include <sofia-sip/su_alloc.h>
#include <sofia-sip/bnf.h>
#include <sofia-sip/hostdomain.h>
#include <sofia-sip/url.h>
#include <sofia-sip/string0.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#include <limits.h>
/**@def URL_PRINT_FORMAT
* Format string used when printing url with printf().
*
* The macro URL_PRINT_FORMAT is used in format string of printf() or
* similar printing functions. A URL can be printed like this:
* @code
* printf("%s received URL " URL_PRINT_FORMAT "\n",
* my_name, URL_PRINT_ARGS(url));
* @endcode
*/
/** @def URL_PRINT_ARGS(u)
* Argument list used when printing url with printf().
*
* The macro URL_PRINT_ARGS() is used to create a stdarg list for printf()
* or similar printing functions. Using it, a URL can be printed like this:
*
* @code
* printf("%s received URL " URL_PRINT_FORMAT "\n",
* my_name, URL_PRINT_ARGS(url));
* @endcode
*/
#define RESERVED ";/?:@&=+$,"
#define DELIMS "<>#%\""
#define UNWISE "{}|\\^[]`"
#define EXCLUDED RESERVED DELIMS UNWISE
#define UNRESERVED "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"abcdefghijklmnopqrstuvwxyz" \
"0123456789" \
"-_.!~*'()"
#define IS_EXCLUDED(u, m32, m64, m96) \
(u <= ' ' \
|| u >= '\177' \
|| (u < 64 ? (m32 & (1 << (63 - u))) \
: (u < 96 ? (m64 & (1 << (95 - u))) \
: /*u < 128*/ (m96 & (1 << (127 - u))))) != 0)
#define MASKS_WITH_RESERVED(reserved, m32, m64, m96) \
if (reserved == NULL) { \
m32 = 0xbe19003f, m64 = 0x8000001e, m96 = 0x8000001d; \
} else do { \
m32 = 0xb400000a, m64 = 0x0000001e, m96 = 0x8000001d; \
\
for (;reserved[0]; reserved++) { \
unsigned r = reserved[0]; \
RESERVE(r, m32, m64, m96); \
} \
} while (0)
#define RESERVE(reserved, m32, m64, m96) \
if (r < 32) \
; \
else if (r < 64) \
m32 |= 1U << (63 - r); \
else if (r < 96) \
m64 |= 1U << (95 - r); \
else if (r < 128) \
m96 |= 1U << (127 - r)
#define MASKS_WITH_ALLOWED(allowed, mask32, mask64, mask96) \
do { \
if (allowed) { \
for (;allowed[0]; allowed++) { \
unsigned a = allowed[0]; \
ALLOW(a, mask32, mask64, mask96); \
} \
} \
} while (0)
#define ALLOW(a, mask32, mask64, mask96) \
if (a < 32) \
; \
else if (a < 64) \
mask32 &= ~(1U << (63 - a)); \
else if (a < 96) \
mask64 &= ~(1U << (95 - a)); \
else if (a < 128) \
mask96 &= ~(1U << (127 - a))
#define NUL '\0'
#define NULNULNUL '\0', '\0', '\0'
#define RMASK1 0xbe19003f
#define RMASK2 0x8000001e
#define RMASK3 0x8000001d
#define RESERVED_MASK 0xbe19003f, 0x8000001e, 0x8000001d
#define URIC_MASK 0xb400000a, 0x0000001e, 0x8000001d
#define IS_EXCLUDED_MASK(u, m) IS_EXCLUDED(u, m)
/* Internal prototypes */
static char *url_canonize(char *d, char const *s, size_t n,
unsigned syn33,
char const allowed[]);
static char *url_canonize2(char *d, char const *s, size_t n,
unsigned syn33,
unsigned m32, unsigned m64, unsigned m96);
static int url_tel_cmp_numbers(char const *A, char const *B);
/**Test if string contains excluded or url-reserved characters.
*
*
*
* @param s string to be searched
*
* @retval 0 if no reserved characters were found.
* @retval l if a reserved character was found.
*/
int url_reserved_p(char const *s)
{
if (s)
while (*s) {
unsigned char u = *s++;
if (IS_EXCLUDED(u, RMASK1, RMASK2, RMASK3))
return 1;
}
return 0;
}
/** Calculate length of string when escaped with %-notation.
*
* Calculate the length of string @a s when the excluded or reserved
* characters in it have been escaped.
*
* @param s String with reserved URL characters. [IN
* @param reserved Optional array of reserved characters [IN]
*
* @return
* The number of characters in corresponding but escaped string.
*
* You can handle a part of URL with reserved characters like this:
*<2A>@code
* if (url_reserved_p(s)) {
* n = malloc(url_esclen(s, NULL) + 1);
* if (n) url_escape(n, s);
* } else {
* n = malloc(strlen(s) + 1);
* if (n) strcpy(n, s);
* }
* @endcode
*/
isize_t url_esclen(char const *s, char const reserved[])
{
size_t n;
unsigned mask32, mask64, mask96;
MASKS_WITH_RESERVED(reserved, mask32, mask64, mask96);
for (n = 0; s && *s; n++) {
unsigned char u = *s++;
if (IS_EXCLUDED(u, mask32, mask64, mask96))
n += 2;
}
return (isize_t)n;
}
/** Escape a string.
*
* The function url_escape() copies the string pointed by @a s to the array
* pointed by @a d, @b excluding the terminating \\0 character. All reserved
* characters in @a s are copied in hexadecimal format, for instance, @c
* "$%#" is copied as @c "%24%25%23". The destination array @a d must be
* large enough to receive the escaped copy.
*
* @param d Destination buffer [OUT]
* @param s String to be copied [IN]
* @param reserved Array of reserved characters [IN]
*
* @return Pointer to the destination array.
*/
char *url_escape(char *d, char const *s, char const reserved[])
{
char *retval = d;
unsigned mask32, mask64, mask96;
MASKS_WITH_RESERVED(reserved, mask32, mask64, mask96);
while (s && *s) {
unsigned char u = *s++;
if (IS_EXCLUDED(u, mask32, mask64, mask96)) {
# define URL_HEXIFY(u) ((u) + '0' + ((u) >= 10 ? 'A' - '0' - 10 : 0))
*d++ = '%';
*d++ = URL_HEXIFY(u >> 4);
*d++ = URL_HEXIFY(u & 15);
# undef URL_HEXIFY
}
else {
*d++ = u;
}
}
*d = '\0';
return retval;
}
/**Unescape url-escaped string fragment.
*
* Unescape @a n characters from string @a s to the buffer @a d, including
* the terminating \\0 character. All %-escaped triplets in @a s are
* unescaped, for instance, @c "%40%25%23" is copied as @c "@%#". The
* destination array @a d must be large enough to receive the escaped copy
* (@a n bytes is always enough).
*
* @param d destination buffer
* @param s string to be unescaped
* @param n maximum number of characters to unescape
*
* @return Length of unescaped string
*
* @NEW_1_12_4.
*/
size_t url_unescape_to(char *d, char const *s, size_t n)
{
size_t i = 0, j = 0;
if (s == NULL)
return 0;
i = j = strncspn(s, n, "%");
if (d && d != s)
memmove(d, s, i);
for (; i < n;) {
char c = s[i++];
if (c == '\0')
break;
if (c == '%' && i + 1 < n && IS_HEX(s[i]) && IS_HEX(s[i + 1])) {
#define UNHEX(a) (a - (a >= 'a' ? 'a' - 10 : (a >= 'A' ? 'A' - 10 : '0')))
c = (UNHEX(s[i]) << 4) | UNHEX(s[i + 1]);
#undef UNHEX
i += 2;
}
if (d)
d[j] = c;
j++;
}
return j;
}
/**Unescape url-escaped string.
*
* Unescape string @a s to the buffer @a d, including the terminating \\0
* character. All %-escaped triplets in @a s are unescaped, for instance, @c
* "%40%25%23" is copied as @c "@%#". The destination array @a d must be
* large enough to receive the escaped copy.
*
* @param d destination buffer
* @param s string to be copied
*
* @return Pointer to the destination buffer.
*/
char *url_unescape(char *d, char const *s)
{
size_t n = url_unescape_to(d, s, SIZE_MAX);
if (d)
d[n] = '\0';
return d;
}
/** Canonize a URL component */
static
char *url_canonize(char *d, char const *s, size_t n,
unsigned syn33,
char const allowed[])
{
unsigned mask32 = 0xbe19003f, mask64 = 0x8000001e, mask96 = 0x8000001d;
MASKS_WITH_ALLOWED(allowed, mask32, mask64, mask96);
return url_canonize2(d, s, n, syn33, mask32, mask64, mask96);
}
#define SYN33(c) (1U << (c - 33))
#define IS_SYN33(syn33, c) ((syn33 & (1U << (c - 33))) != 0)
/** Canonize a URL component (with precomputed mask) */
static
char *url_canonize2(char *d, char const * const s, size_t n,
unsigned syn33,
unsigned m32, unsigned m64, unsigned m96)
{
size_t i = 0;
if (d == s)
for (;s[i] && i < n; d++, i++)
if (s[i] == '%')
break;
for (;s[i] && i < n; d++, i++) {
unsigned char c = s[i], h1, h2;
if (c != '%') {
if (!IS_SYN33(syn33, c) && IS_EXCLUDED(c, m32, m64, m96))
return NULL;
*d = c;
continue;
}
h1 = s[i + 1], h2 = s[i + 2];
if (!IS_HEX(h1) || !IS_HEX(h2)) {
*d = '\0';
return NULL;
}
#define UNHEX(a) (a - (a >= 'a' ? 'a' - 10 : (a >= 'A' ? 'A' - 10 : '0')))
c = (UNHEX(h1) << 4) | UNHEX(h2);
if (!IS_EXCLUDED(c, m32, m64, m96)) {
/* Convert hex to normal character */
*d = c, i += 2;
continue;
}
/* Convert hex to uppercase */
if (h1 >= 'a' /* && h1 <= 'f' */)
h1 = h1 - 'a' + 'A';
if (h2 >= 'a' /* && h2 <= 'f' */)
h2 = h2 - 'a' + 'A';
d[0] = '%', d[1] = h1, d[2] = h2;
d +=2, i += 2;
#undef UNHEX
}
*d = '\0';
return d;
}
/** Canonize a URL component (with precomputed mask).
*
* This version does not flag error if *s contains character that should
* be escaped.
*/
static
char *url_canonize3(char *d, char const * const s, size_t n,
unsigned m32, unsigned m64, unsigned m96)
{
size_t i = 0;
if (d == s)
for (;s[i] && i < n; d++, i++)
if (s[i] == '%')
break;
for (;s[i] && i < n; d++, i++) {
unsigned char c = s[i], h1, h2;
if (c != '%') {
*d = c;
continue;
}
h1 = s[i + 1], h2 = s[i + 2];
if (!IS_HEX(h1) || !IS_HEX(h2)) {
*d = '\0';
return NULL;
}
#define UNHEX(a) (a - (a >= 'a' ? 'a' - 10 : (a >= 'A' ? 'A' - 10 : '0')))
c = (UNHEX(h1) << 4) | UNHEX(h2);
if (!IS_EXCLUDED(c, m32, m64, m96)) {
*d = c, i += 2;
continue;
}
/* Convert hex to uppercase */
if (h1 >= 'a' /* && h1 <= 'f' */)
h1 = h1 - 'a' + 'A';
if (h2 >= 'a' /* && h2 <= 'f' */)
h2 = h2 - 'a' + 'A';
d[0] = '%', d[1] = h1, d[2] = h2;
d +=2, i += 2;
#undef UNHEX
}
*d = '\0';
return d;
}
/** Get URL scheme. */
char const* url_scheme(enum url_type_e url_type)
{
switch (url_type) {
case url_any: return "*";
case url_sip: return "sip";
case url_sips: return "sips";
case url_tel: return "tel";
case url_fax: return "fax";
case url_modem: return "modem";
case url_http: return "http";
case url_https: return "https";
case url_ftp: return "ftp";
case url_file: return "file";
case url_rtsp: return "rtsp";
case url_rtspu: return "rtspu";
case url_mailto: return "mailto";
case url_im: return "im";
case url_pres: return "pres";
case url_cid: return "cid";
case url_msrp: return "msrp";
case url_msrps: return "msrps";
case url_wv: return "wv";
default:
assert(url_type == url_unknown);
return NULL;
}
}
su_inline
int url_type_is_opaque(enum url_type_e url_type)
{
return
url_type == url_invalid ||
url_type == url_tel ||
url_type == url_modem ||
url_type == url_fax ||
url_type == url_cid;
}
/** Init an url as given type */
void url_init(url_t *url, enum url_type_e type)
{
memset(url, 0, sizeof(*url));
url->url_type = type;
if (type > url_unknown) {
char const *scheme = url_scheme(url->url_type);
if (scheme)
url->url_scheme = scheme;
}
}
/** Get url type */
su_inline
enum url_type_e url_get_type(char const *scheme, size_t len)
{
#define test_scheme(s) \
if (len == strlen(#s) && !strncasecmp(scheme, #s, len)) return url_##s
switch (scheme[0]) {
case '*': if (strcmp(scheme, "*") == 0) return url_any;
case 'c': case 'C':
test_scheme(cid); break;
case 'f': case 'F':
test_scheme(ftp); test_scheme(file); test_scheme(fax); break;
case 'h': case 'H':
test_scheme(http); test_scheme(https); break;
case 'i': case 'I':
test_scheme(im); break;
case 'm': case 'M':
test_scheme(mailto); test_scheme(modem);
test_scheme(msrp); test_scheme(msrps); break;
case 'p': case 'P':
test_scheme(pres); break;
case 'r': case 'R':
test_scheme(rtsp); test_scheme(rtspu); break;
case 's': case 'S':
test_scheme(sip); test_scheme(sips); break;
case 't': case 'T':
test_scheme(tel); break;
case 'w': case 'W':
test_scheme(wv); break;
default: break;
}
#undef test_scheme
if (len != span_unreserved(scheme))
return url_invalid;
else
return url_unknown;
}
/**
* Decode a URL.
*
* This function decodes a (SIP) URL string to a url_t structure.
*
* @param url structure to store the parsing result
* @param s NUL-terminated string to be parsed
*
* @note The parsed string @a s will be modified when parsing it.
*
* @retval 0 if successful,
* @retval -1 otherwise.
*/
static
int _url_d(url_t *url, char *s)
{
size_t n, p;
char *s0, rest_c, *host, *user;
int have_authority = 1;
memset(url, 0, sizeof(*url));
if (strcmp(s, "*") == 0) {
url->url_type = url_any;
url->url_scheme = "*";
return 0;
}
s0 = s;
n = strcspn(s, ":/?#");
if (n && s[n] == ':') {
char *scheme;
url->url_scheme = scheme = s; s[n] = '\0'; s = s + n + 1;
if (!(scheme = url_canonize(scheme, scheme, SIZE_MAX, 0, "+")))
return -1;
n = scheme - url->url_scheme;
url->url_type = url_get_type(url->url_scheme, n);
have_authority = !url_type_is_opaque(url->url_type);
}
else {
url->url_type = url_unknown;
}
user = NULL, host = s;
if (url->url_type == url_sip || url->url_type == url_sips) {
/* SIP URL may have /;? in user part but no path */
/* user-unreserved = "&" / "=" / "+" / "$" / "," / ";" / "?" / "/" */
/* Some #*@#* phones include unescaped # there, too */
n = strcspn(s, "@/;?#");
p = strcspn(s + n, "@");
if (s[n + p] == '@') {
n += p;
user = s;
host = s + n + 1;
}
n += strcspn(s + n, "/;?#");
}
else if (have_authority) {
if (url->url_type == url_wv) {
/* WV URL may have / in user part */
n = strcspn(s, "@#?;");
if (s[n] == '@') {
user = s;
host = s + n + 1;
n += strcspn(s + n, ";?#");
}
}
else if (host[0] == '/' && host[1] != '/') {
/* foo:/bar or /bar - no authority, just path */
url->url_root = '/'; /* Absolute path */
host = NULL, n = 0;
}
else {
if (host[0] == '/' && host[1] == '/') {
/* We have authority, / / foo or foo */
host += 2; s += 2, url->url_root = '/';
n = strcspn(s, "/?#@[]");
}
else
n = strcspn(s, "@;/?#");
if (s[n] == '@')
user = host, host = user + n + 1;
n += strcspn(s + n, ";/?#"); /* Find path, query and/or fragment */
}
}
else /* !have_authority */ {
user = host, host = NULL;
if (url->url_type != url_invalid)
n = strcspn(s, "/;?#"); /* Find params, query and/or fragment */
else
n = strcspn(s, "#");
}
rest_c = s[n]; s[n] = 0; s = rest_c ? s + n + 1 : NULL;
if (user) {
if (host) host[-1] = '\0';
url->url_user = user;
if (url->url_type != url_unknown) {
n = strcspn(user, ":");
if (user[n]) {
user[n] = '\0';
url->url_password = user + n + 1;
}
}
}
if (host) {
url->url_host = host;
/* IPv6 (and in some cases, IPv4) addresses are quoted with [] */
if (host[0] == '[') {
n = strcspn(host, "]");
if (host[n] && (host[n + 1] == '\0' || host[n + 1] == ':'))
n++;
else
n = 0;
}
else {
n = strcspn(host, ":");
}
/* We allow empty host by default */
if (n == 0) switch (url->url_type) {
case url_sip:
case url_sips:
case url_im:
case url_pres:
return -1;
default:
break;
}
if (host[n] == ':') {
char *port = host + n + 1;
url->url_port = port;
switch (url->url_type) {
case url_any:
case url_sip:
case url_sips:
case url_http:
case url_https:
case url_ftp:
case url_file:
case url_rtsp:
case url_rtspu:
if (!url_canonize2(port, port, SIZE_MAX, 0, RESERVED_MASK))
return -1;
/* Check that port is really numeric or wildcard */
/* Port can be *digit, empty string or "*" */
while (*port >= '0' && *port <= '9')
port++;
if (port != url->url_port) {
if (port[0] != '\0')
return -1;
}
else if (port[0] == '\0')
/* empty string */;
else if (port[0] == '*' && port[1] == '\0')
/* wildcard */;
else
return -1;
}
host[n] = 0;
}
}
if (rest_c == '/') {
url->url_path = s; n = strcspn(s, "?#");
rest_c = s[n]; s[n] = 0; s = rest_c ? s + n + 1 : NULL;
}
if (rest_c == ';') {
url->url_params = s; n = strcspn(s, "?#");
rest_c = s[n]; s[n] = 0; s = rest_c ? s + n + 1 : NULL;
}
if (rest_c == '?') {
url->url_headers = s; n = strcspn(s, "#");
rest_c = s[n]; s[n] = 0; s = rest_c ? s + n + 1 : NULL;
}
if (rest_c == '#') {
url->url_fragment = s;
rest_c = '\0';
}
if (rest_c)
return -1;
return 0;
}
/* Unreserved things */
/**
* Decode a URL.
*
* This function decodes a URL string to a url_t structure.
*
* @param url structure to store the parsing result
* @param s NUL-terminated string to be parsed
*
* @note The parsed string @a s will be modified when parsing it.
*
* @retval 0 if successful,
* @retval -1 otherwise.
*/
int url_d(url_t *url, char *s)
{
if (url == NULL || _url_d(url, s) < 0)
return -1;
/* Canonize URL */
/* scheme is canonized by _url_d() */
if (url->url_type == url_sip || url->url_type == url_sips) {
# define SIP_USER_UNRESERVED "&=+$,;?/"
s = (char *)url->url_user;
if (s && !url_canonize(s, s, SIZE_MAX, 0, SIP_USER_UNRESERVED))
return -1;
/* Having different charset in user and password does not make sense */
/* but that is how it is defined in RFC 3261 */
# define SIP_PASS_UNRESERVED "&=+$,"
s = (char *)url->url_password;
if (s && !url_canonize(s, s, SIZE_MAX, 0, SIP_PASS_UNRESERVED))
return -1;
}
else {
# define USER_UNRESERVED "&=+$,;"
s = (char *)url->url_user;
if (s && !url_canonize(s, s, SIZE_MAX, 0, USER_UNRESERVED))
return -1;
# define PASS_UNRESERVED "&=+$,;:"
s = (char *)url->url_password;
if (s && !url_canonize(s, s, SIZE_MAX, 0, PASS_UNRESERVED))
return -1;
}
s = (char *)url->url_host;
if (s && !url_canonize2(s, s, SIZE_MAX, 0, RESERVED_MASK))
return -1;
/* port is canonized by _url_d() */
s = (char *)url->url_path;
if (s && !url_canonize(s, s, SIZE_MAX,
/* Allow all URI characters but ? */
/* Allow unescaped /;?@, - but do not convert */
SYN33('/') | SYN33(';') | SYN33('=') | SYN33('@') |
SYN33(','),
/* Convert escaped :&+$ to unescaped */
":&+$"))
return -1;
s = (char *)url->url_params;
if (s && !url_canonize(s, s, SIZE_MAX,
/* Allow all URI characters but ? */
/* Allow unescaped ;=@, - but do not convert */
SYN33(';') | SYN33('=') | SYN33('@') | SYN33(','),
/* Convert escaped /:&+$ to unescaped */
"/:&+$"))
return -1;
/* Unhex alphanumeric and unreserved URI characters */
s = (char *)url->url_headers;
if (s && !url_canonize3(s, s, SIZE_MAX, RESERVED_MASK))
return -1;
/* Allow all URI characters (including reserved ones) */
s = (char *)url->url_fragment;
if (s && !url_canonize2(s, s, SIZE_MAX, 0, URIC_MASK))
return -1;
return 0;
}
/** Encode an URL.
*
* The function url_e() combines a URL from substrings in url_t structure
* according the @ref url_syntax "URL syntax" presented above. The encoded
* @a url is stored in a @a buffer of @a n bytes.
*
* @param buffer memory area to store the encoded @a url.
* @param n size of @a buffer.
* @param url URL to be encoded.
*
* @return
* Return the number of bytes in the encoding.
*
* @note The function follows the convention set by C99 snprintf(). Even if
* the result does not fit into the @a buffer and it is truncated, the
* function returns the number of bytes in an untruncated encoding.
*/
issize_t url_e(char buffer[], isize_t n, url_t const *url)
{
size_t i;
char *b = buffer;
size_t m = n;
int do_copy = n > 0;
if (url == NULL)
return -1;
if (URL_STRING_P(url)) {
char const *u = (char *)url;
i = strlen(u);
if (!buffer)
return i;
if (i >= n) {
memcpy(buffer, u, n - 2);
buffer[n - 1] = '\0';
} else {
memcpy(buffer, u, i + 1);
}
return i;
}
if (url->url_type == url_any) {
if (b && m > 0) {
if (m > 1) strcpy(b, "*"); else b[0] = '\0';
}
return 1;
}
if (url->url_scheme && url->url_scheme[0]) {
i = strlen(url->url_scheme) + 1;
if (do_copy && (do_copy = i <= n)) {
memcpy(b, url->url_scheme, i - 1);
b[i - 1] = ':';
}
b += i; n -= i;
}
if (url->url_root && (url->url_host || url->url_user)) {
if (do_copy && (do_copy = 2 <= n))
memcpy(b, "//", 2);
b += 2; n -= 2;
}
if (url->url_user) {
i = strlen(url->url_user);
if (do_copy && (do_copy = i <= n))
memcpy(b, url->url_user, i);
b += i; n -= i;
if (url->url_password) {
if (do_copy && (do_copy = 1 <= n))
*b = ':';
b++; n--;
i = strlen(url->url_password);
if (do_copy && (do_copy = i <= n))
memcpy(b, url->url_password, i);
b += i; n -= i;
}
if (url->url_host) {
if (do_copy && (do_copy = 1 <= n))
*b = '@';
b++; n--;
}
}
if (url->url_host) {
i = strlen(url->url_host);
if (do_copy && (do_copy = i <= n))
memcpy(b, url->url_host, i);
b += i; n -= i;
if (url->url_port) {
i = strlen(url->url_port) + 1;
if (do_copy && (do_copy = i <= n)) {
b[0] = ':';
memcpy(b + 1, url->url_port, i - 1);
}
b += i; n -= i;
}
}
if (url->url_path) {
if (url->url_root) {
if (do_copy && (do_copy = 1 <= n))
b[0] = '/';
b++, n--;
}
i = strlen(url->url_path);
if (do_copy && (do_copy = i < n))
memcpy(b, url->url_path, i);
b += i; n -= i;
}
{
static char const sep[] = ";?#";
char const *pp[3];
size_t j;
pp[0] = url->url_params;
pp[1] = url->url_headers;
pp[2] = url->url_fragment;
for (j = 0; j < 3; j++) {
char const *p = pp[j];
if (!p) continue;
i = strlen(p) + 1;
if (do_copy && (do_copy = i <= n)) {
*b = sep[j];
memcpy(b + 1, p, i - 1);
}
b += i; n -= i;
}
}
if (do_copy && (do_copy = 1 <= n))
*b = '\0';
else if (buffer && m > 0)
buffer[m - 1] = '\0';
assert((size_t)(b - buffer) == (size_t)(m - n));
/* This follows the snprintf(C99) return value,
* Number of characters written (excluding NUL)
*/
return b - buffer;
}
/** Calculate the length of URL when encoded.
*
*/
isize_t url_len(url_t const * url)
{
size_t rv = 0;
if (url->url_scheme) rv += strlen(url->url_scheme) + 1; /* plus ':' */
if (url->url_user) {
rv += strlen(url->url_user);
if (url->url_password)
rv += strlen(url->url_password) + 1; /* plus ':' */
rv += url->url_host != NULL; /* plus '@' */
}
if (url->url_host) rv += strlen(url->url_host);
if (url->url_port) rv += strlen(url->url_port) + 1; /* plus ':' */
if (url->url_path) rv += strlen(url->url_path) + 1; /* plus initial / */
if (url->url_params) rv += strlen(url->url_params) + 1; /* plus initial ; */
if (url->url_headers) rv += strlen(url->url_headers) + 1; /* plus '?' */
if (url->url_fragment) rv += strlen(url->url_fragment) + 1; /* plus '#' */
return rv;
}
/**@def URL_E(buf, end, url)
* Encode an URL: use @a buf up to @a end.
* @hideinitializer
*/
/**
* Calculate the size of strings associated with a #url_t sructure.
*
* @param url pointer to a #url_t structure or string
* @return Number of bytes for URL
*/
isize_t url_xtra(url_t const *url)
{
size_t xtra;
if (URL_STRING_P(url)) {
xtra = strlen((char const *)url) + 1;
}
else {
size_t len_scheme, len_user, len_password,
len_host, len_port, len_path, len_params,
len_headers, len_fragment;
len_scheme = (url->url_type <= url_unknown && url->url_scheme) ?
strlen(url->url_scheme) + 1 : 0;
len_user = url->url_user ? strlen(url->url_user) + 1 : 0;
len_password = url->url_password ? strlen(url->url_password) + 1 : 0;
len_host = url->url_host ? strlen(url->url_host) + 1 : 0;
len_port = url->url_port ? strlen(url->url_port) + 1 : 0;
len_path = url->url_path ? strlen(url->url_path) + 1 : 0;
len_params = url->url_params ? strlen(url->url_params) + 1 : 0;
len_headers = url->url_headers ? strlen(url->url_headers) + 1 : 0;
len_fragment = url->url_fragment ? strlen(url->url_fragment) + 1 : 0;
xtra =
len_scheme + len_user + len_password + len_host + len_port +
len_path + len_params + len_headers + len_fragment;
}
return xtra;
}
su_inline
char *copy(char *buf, char *end, char const *src)
{
#if HAVE_MEMCCPY
char *b = memccpy(buf, src, '\0', end - buf);
if (b)
return b;
else
return end + strlen(src + (end - buf)) + 1;
#else
for (; buf < end && (*buf = *src); buf++, src++)
;
if (buf >= end)
while (*src++)
buf++;
return buf + 1;
#endif
}
/**
* Duplicate the url.
*
* The function url_dup() copies the url structure @a src and the strings
* attached to it to @a url. The non-constant strings in @a src are copied
* to @a buf. If the size of duplicated strings exceed @a bufsize, the
* corresponding string fields in @a url are set to NULL.
*
* The calling function can calculate the size of buffer required by calling
* url_dup() with zero as @a bufsize and NULL as @a dst.
* @param buf Buffer for non-constant strings copied from @a src.
* @param bufsize Size of @a buf.
* @param dst Destination URL structure.
*<2A>@param src Source URL structure.
*
* @return Number of characters required for
* duplicating the strings in @a str, or -1 if an error
* occurred.
*/
issize_t url_dup(char *buf, isize_t bufsize, url_t *dst, url_t const *src)
{
if (!src && !dst)
return -1;
else if (URL_STRING_P(src)) {
size_t n = strlen((char *)src) + 1;
if (n > bufsize || dst == NULL)
return n;
strcpy(buf, (char *)src);
memset(dst, 0, sizeof(*dst));
if (url_d(dst, buf) < 0)
return -1;
return n;
}
else {
char *b = buf;
char *end = b + bufsize;
char const **dstp;
char const * const *srcp;
url_t dst0[1];
if (dst == NULL)
dst = dst0;
memset(dst, 0, sizeof(*dst));
if (!src)
return 0;
memset(dst->url_pad, 0, sizeof dst->url_pad);
dst->url_type = src->url_type;
dst->url_root = src->url_root;
dstp = &dst->url_scheme;
srcp = &src->url_scheme;
if (dst->url_type > url_unknown)
*dstp = url_scheme(dst->url_type);
if (*dstp != NULL)
dstp++, srcp++; /* Skip scheme if it is constant */
if (dst != dst0 && buf != NULL && bufsize != 0)
for (; srcp <= &src->url_fragment; srcp++, dstp++)
if (*srcp) {
char *next = copy(b, end, *srcp);
if (next > end)
break;
*dstp = b, b = next;
}
for (; srcp <= &src->url_fragment; srcp++)
if (*srcp) {
b += strlen(*srcp) + 1;
}
return b - buf;
}
}
/**@def URL_DUP(buf, end, dst, src)
* Duplicate the url: use @a buf up to @a end. @HI
*
* The macro URL_DUP() duplicates the url. The non-constant strings in @a
* src are copied to @a buf. However, no strings are copied past @a end.
* In other words, the size of buffer is @a end - @a buf.
*
* The macro updates the buffer pointer @a buf, so that it points to the
* first unused byte in the buffer. The buffer pointer @a buf is updated,
* even if the buffer is too small for the duplicated strings.
*
* @param buf Buffer for non-constant strings copied from @a src.
* @param end End of @a buf.
* @param dst Destination URL structure.
*<2A>@param src Source URL structure.
*
* @return
* The macro URL_DUP() returns pointer to first unused byte in the
* buffer @a buf.
*/
/** Duplicate the url to memory allocated via home.
*
* The function url_hdup() duplicates (deep copies) an #url_t structure.
* Alternatively, it can be passed a string; string is then copied and
* parsed to the #url_t structure.
*
* The function url_hdup() allocates the destination structure from @a home
* as a single memory block. It is possible to free the copied url structure
* and all the associated strings using a single call to su_free().
*
* @param home memory home used to allocate new url object
* @param src pointer to URL (or string)
*
* @return
* The function url_hdup() returns a pointer to the newly allocated #url_t
* structure, or NULL upon an error.
*/
url_t *url_hdup(su_home_t *home, url_t const *src)
{
if (src) {
size_t len = sizeof(*src) + url_xtra(src);
url_t *dst = su_alloc(home, len);
if (dst) {
ssize_t actual;
actual = url_dup((char *)(dst + 1), len - sizeof(*src), dst, src);
if (actual < 0)
su_free(home, dst), dst = NULL;
else
assert(len == sizeof(*src) + actual);
}
return dst;
}
else
return NULL;
}
/** Convert an string to an url */
url_t *url_make(su_home_t *h, char const *str)
{
return url_hdup(h, URL_STRING_MAKE(str)->us_url);
}
/** Print an URL */
url_t *url_format(su_home_t *h, char const *fmt, ...)
{
url_t *url;
char *us;
va_list ap;
va_start(ap, fmt);
us = su_vsprintf(h, fmt, ap);
va_end(ap);
if (us == NULL)
return NULL;
url = url_hdup(h, URL_STRING_MAKE(us)->us_url);
su_free(h, us);
return url;
}
/** Convert @a url to a string allocated from @a home.
*
* @param home memory home to allocate the new string
* @param url url to convert to string
*
* The @a url can be a string, too.
*
* @return Newly allocated conversion result, or NULL upon an error.
*/
char *url_as_string(su_home_t *home, url_t const *url)
{
if (url) {
int len = url_e(NULL, 0, url);
char *b = su_alloc(home, len + 1);
url_e(b, len + 1, url);
return b;
} else {
return NULL;
}
}
/** Test if param @a tag matches to parameter string @a p.
*/
#define URL_PARAM_MATCH(p, tag) \
(strncasecmp(p, tag, strlen(tag)) == 0 && \
(p[strlen(tag)] == '\0' || p[strlen(tag)] == ';' || p[strlen(tag)] == '='))
/**
* Search for a parameter.
*
* This function searches for a parameter from a parameter list.
*
* If you want to test if there is parameter @b user=phone,
* call this function like
* @code if (url_param(url->url_param, "user=phone", NULL, 0))
* @endcode
*
* @param params URL parameter string (excluding first semicolon)
* @param tag parameter name
* @param value string to which the parameter value is copied
* @param vlen length of string reserved for value
*
* @retval positive length of parameter value (including final NUL) if found
* @retval zero if not found.
*/
isize_t url_param(char const *params,
char const *tag,
char value[], isize_t vlen)
{
size_t n, tlen, flen;
char *p;
if (!params)
return 0;
tlen = strlen(tag);
if (tlen && tag[tlen - 1] == '=')
tlen--;
for (p = (char *)params; *p; p += n + 1) {
n = strcspn(p, ";");
if (n < tlen) {
if (p[n]) continue; else break;
}
if (strncasecmp(p, tag, tlen) == 0) {
if (n == tlen) {
if (vlen > 0)
value[0] = '\0';
return 1;
}
if (p[tlen] != '=')
continue;
flen = n - tlen - 1;
if (flen >= (size_t)vlen)
return flen + 1;
memcpy(value, p + tlen + 1, flen);
value[flen] = '\0';
return flen + 1;
}
if (!p[n])
break;
}
return 0;
}
/** Check for a parameter.
*
* @deprecated
* Bad grammar. Use url_has_param().
*/
isize_t url_have_param(char const *params, char const *tag)
{
return url_param(params, tag, NULL, 0);
}
/** Check for a parameter. */
int url_has_param(url_t const *url, char const *tag)
{
return url && url->url_params && url_param(url->url_params, tag, NULL, 0);
}
/** Add an parameter. */
int url_param_add(su_home_t *h, url_t *url, char const *param)
{
/* XXX - should remove existing parameters with same name? */
size_t n = url->url_params ? strlen(url->url_params) + 1: 0;
size_t nn = strlen(param) + 1;
char *s = su_alloc(h, n + nn);
if (!s)
return -1;
if (url->url_params)
strcpy(s, url->url_params)[n - 1] = ';';
strcpy(s + n, param);
url->url_params = s;
return 0;
}
/** Remove a named parameter from url_param string.
*
* Remove a named parameter and its possible value from the URL parameter
* string (url_s##url_param).
*
* @return Pointer to modified string, or NULL if nothing is left in there.
*/
char *url_strip_param_string(char *params, char const *name)
{
if (params && name) {
size_t i, n = strlen(name), remove, rest;
for (i = 0; params[i];) {
if (strncasecmp(params + i, name, n) ||
(params[i + n] != '=' && params[i + n] != ';' && params[i + n])) {
i = i + strcspn(params + i, ";");
if (!params[i++])
break;
continue;
}
remove = n + strcspn(params + i + n, ";");
if (params[i + remove] == ';')
remove++;
if (i == 0) {
params += remove;
continue;
}
rest = strlen(params + i + remove);
if (!rest) {
if (i == 0)
return NULL; /* removed everything */
params[i - 1] = '\0';
break;
}
memmove(params + i, params + i + remove, rest + 1);
}
if (!params[0])
return NULL;
}
return params;
}
int url_string_p(url_string_t const *url)
{
return URL_STRING_P(url);
}
int url_is_string(url_string_t const *url)
{
return URL_IS_STRING(url);
}
/** Strip transport-specific stuff. */
static
int url_strip_transport2(url_t *url, int modify)
{
char *p, *d;
size_t n;
int semi;
if (url->url_type != url_sip && url->url_type != url_sips)
return 0;
if (url->url_port != NULL) {
if (!modify)
return 1;
url->url_port = NULL;
}
if (!url->url_params)
return 0;
for (d = p = (char *)url->url_params; *p; p += n + semi) {
n = strcspn(p, ";");
semi = (p[n] != '\0');
if (modify && n == 0)
continue;
if (URL_PARAM_MATCH(p, "method"))
continue;
if (URL_PARAM_MATCH(p, "maddr"))
continue;
if (URL_PARAM_MATCH(p, "ttl"))
continue;
if (URL_PARAM_MATCH(p, "transport"))
continue;
if (p != d) {
if (d != url->url_params)
d++;
if (p != d) {
if (!modify)
return 1;
memmove(d, p, n + 1);
}
}
d += n;
}
if (d == p)
return 0;
else if (d + 1 == p) /* empty param */
return 0;
else if (!modify)
return 1;
if (d != url->url_params)
*d = '\0';
else
url->url_params = NULL;
return 1;
}
/** Strip transport-specific stuff.
*
* The function url_strip_transport() removes transport-specific parameters
* from a SIP or SIPS URI. These parameters include:
* - the port number
* - "maddr=" parameter
* - "transport=" parameter
* - "ttl=" parameter
* - "method=" parameter
*
* @note
* The @a url must be a pointer to a URL structure. It is stripped in-place.
*
* @note
* If the parameter string contains empty parameters, they are stripped, too.
*
* @return
* The function url_strip_transport() returns @e true, if the URL was
* modified, @e false otherwise.
*/
int url_strip_transport(url_t *url)
{
return url_strip_transport2(url, 1);
}
/** Check for transport-specific stuff.
*
* The function url_have_transport() tests if there are transport-specific
* parameters in a SIP or SIPS URI. These parameters include:
* - the port number
* - "maddr=" parameters
* - "transport=" parameters
*
* @note
* The @a url must be a pointer to a URL structure.
*
* @return The function url_have_transport() returns @e true, if the URL
* contains transport parameters, @e false otherwise.
*/
int url_have_transport(url_t const *url)
{
return url_strip_transport2((url_t *)url, 0);
}
/**Lazily compare two URLs.
*
* Compare essential parts of URLs: schema, host, port, and username.
*
* any_url compares 0 with any other URL.
*
* pres: and im: URIs compares 0 with SIP URIs.
*
* @note
* The @a a and @a b must be pointers to URL structures.
*
* @note Currently, the url parameters are not compared. This is because the
* url_cmp() is used to sort URLs: taking parameters into account makes that
* impossible.
*/
int url_cmp(url_t const *a, url_t const *b)
{
int rv;
int url_type;
if ((a && a->url_type == url_any) || (b && b->url_type == url_any))
return 0;
if (!a || !b)
return (a != NULL) - (b != NULL);
if ((rv = a->url_type - b->url_type)) {
#if 0
/* presence and instant messaging URLs match magically with SIP */
enum url_type_e a_type = a->url_type;
enum url_type_e b_type = b->url_type;
if (a_type == url_im || a_type == url_pres)
a_type = url_sip;
if (b_type == url_im || b_type == url_pres)
b_type = url_sip;
if (a_type != b_type)
#endif
return rv;
}
url_type = a->url_type; /* Or b->url_type, they are equal! */
if (url_type <= url_unknown &&
((rv = !a->url_scheme - !b->url_scheme) ||
(a->url_scheme && b->url_scheme &&
(rv = strcasecmp(a->url_scheme, b->url_scheme)))))
return rv;
if ((rv = host_cmp(a->url_host, b->url_host)))
return rv;
if (a->url_port != b->url_port) {
char const *a_port;
char const *b_port;
if (url_type != url_sip && url_type != url_sips)
a_port = b_port = url_port_default(url_type);
else if (host_is_ip_address(a->url_host))
a_port = b_port = url_port_default(url_type);
else
a_port = b_port = "";
if (a->url_port) a_port = a->url_port;
if (b->url_port) b_port = b->url_port;
if ((rv = strcmp(a_port, b_port)))
return rv;
}
if (a->url_user != b->url_user) {
if (a->url_user == NULL) return -1;
if (b->url_user == NULL) return +1;
switch (url_type) {
case url_tel: case url_modem: case url_fax:
rv = url_tel_cmp_numbers(a->url_user, b->url_user);
break;
default:
rv = strcmp(a->url_user, b->url_user);
break;
}
if (rv)
return rv;
}
#if 0
if (a->url_path != b->url_path) {
if (a->url_path == NULL) return -1;
if (b->url_path == NULL) return +1;
if ((rv = strcmp(a->url_path, b->url_path)))
return rv;
}
#endif
return 0;
}
static
int url_tel_cmp_numbers(char const *A, char const *B)
{
short a, b;
int rv;
while (*A && *B) {
#define UNHEX(a) (a - (a >= 'a' ? 'a' - 10 : (a >= 'A' ? 'A' - 10 : '0')))
/* Skip visual-separators */
do {
a = *A++;
if (a == '%' && IS_HEX(A[0]) && IS_HEX(A[1]))
a = (UNHEX(A[0]) << 4) | UNHEX(A[1]), A +=2;
} while (a == ' ' || a == '-' || a == '.' || a == '(' || a == ')');
if (isupper(a))
a = tolower(a);
do {
b = *B++;
if (b == '%' && IS_HEX(B[0]) && IS_HEX(B[1]))
b = (UNHEX(B[0]) << 4) | UNHEX(B[1]), B +=2;
} while (b == ' ' || b == '-' || b == '.' || b == '(' || b == ')');
if (isupper(b))
b = tolower(b);
if ((rv = a - b))
return rv;
}
return (int)*A - (int)*B;
}
/**Conservative comparison of urls.
*
* Compare all parts of URLs.
*
* @note
* The @a a and @a b must be pointers to URL structures.
*
*/
int url_cmp_all(url_t const *a, url_t const *b)
{
int rv, url_type;
if (!a || !b)
return (a != NULL) - (b != NULL);
if ((rv = a->url_type - b->url_type))
return rv;
url_type = a->url_type; /* Or b->url_type, they are equal! */
if (url_type <= url_unknown &&
((rv = !a->url_scheme - !b->url_scheme) ||
(a->url_scheme && b->url_scheme &&
(rv = strcasecmp(a->url_scheme, b->url_scheme)))))
return rv;
if ((rv = a->url_root - b->url_root))
return rv;
if ((rv = host_cmp(a->url_host, b->url_host)))
return rv;
if (a->url_port != b->url_port) {
char const *a_port;
char const *b_port;
if (url_type != url_sip && url_type != url_sips)
a_port = b_port = url_port_default(url_type);
else if (host_is_ip_address(a->url_host))
a_port = b_port = url_port_default(url_type);
else
a_port = b_port = "";
if (a->url_port) a_port = a->url_port;
if (b->url_port) b_port = b->url_port;
if ((rv = strcmp(a_port, b_port)))
return rv;
}
if (a->url_user != b->url_user) {
if (a->url_user == NULL) return -1;
if (b->url_user == NULL) return +1;
switch (url_type) {
case url_tel: case url_modem: case url_fax:
rv = url_tel_cmp_numbers(a->url_user, b->url_user);
break;
default:
rv = strcmp(a->url_user, b->url_user);
break;
}
if (rv)
return rv;
}
if (a->url_path != b->url_path) {
if (a->url_path == NULL) return -1;
if (b->url_path == NULL) return +1;
if ((rv = strcmp(a->url_path, b->url_path)))
return rv;
}
if (a->url_params != b->url_params) {
if (a->url_params == NULL) return -1;
if (b->url_params == NULL) return +1;
if ((rv = strcmp(a->url_params, b->url_params)))
return rv;
}
if (a->url_headers != b->url_headers) {
if (a->url_headers == NULL) return -1;
if (b->url_headers == NULL) return +1;
if ((rv = strcmp(a->url_headers, b->url_headers)))
return rv;
}
if (a->url_headers != b->url_headers) {
if (a->url_headers == NULL) return -1;
if (b->url_headers == NULL) return +1;
if ((rv = strcmp(a->url_headers, b->url_headers)))
return rv;
}
if (a->url_fragment != b->url_fragment) {
if (a->url_fragment == NULL) return -1;
if (b->url_fragment == NULL) return +1;
if ((rv = strcmp(a->url_fragment, b->url_fragment)))
return rv;
}
return 0;
}
/** Return default port number corresponding to the url type */
char const *url_port_default(enum url_type_e url_type)
{
switch (url_type) {
case url_sip: /* "sip:" */
return "5060";
case url_sips: /* "sips:" */
return "5061";
case url_http: /* "http:" */
return "80";
case url_https: /* "https:" */
return "443";
case url_ftp: /* "ftp:" */
case url_file: /* "file:" */
return "21";
case url_rtsp: /* "rtsp:" */
case url_rtspu: /* "rtspu:" */
return "554";
case url_mailto: /* "mailto:" */
return "25";
case url_any: /* "*" */
return "*";
case url_msrp:
case url_msrps:
return "9999"; /* XXXX */
case url_tel:
case url_fax:
case url_modem:
case url_im:
case url_pres:
case url_cid:
case url_wv:
default: /* Unknown scheme */
return "";
}
}
/** Return default transport name corresponding to the url type */
char const *url_tport_default(enum url_type_e url_type)
{
switch (url_type) {
case url_sip:
return "*";
case url_sips:
return "tls";
case url_http:
return "tcp";
case url_https:
return "tls";
case url_ftp:
case url_file:
return "tcp";
case url_rtsp:
return "tcp";
case url_rtspu:
return "udp";
case url_mailto:
return "tcp";
case url_msrp:
return "tcp";
case url_msrps:
return "tls";
case url_any: /* "*" */
case url_tel:
case url_fax:
case url_modem:
case url_im:
case url_pres:
case url_cid:
case url_wv:
default: /* Unknown scheme */
return "*";
}
}
/** Return the URL port string */
char const *url_port(url_t const *u)
{
if (!u)
return "";
else if (u->url_port && u->url_port[0])
return u->url_port;
if (u->url_type == url_sips || u->url_type == url_sip)
if (!host_is_ip_address(u->url_host))
return "";
return url_port_default(u->url_type);
}
/** Sanitize URL.
*
* The function url_sanitize() adds a scheme to an incomplete URL. It
* modifies its parameter structure @a url. Currently, the function follows
* simple heuristics:
*
* - URL with host name starting with @c ftp. is an FTP URL
* - URL with host name starting with @c www. is an HTTP URL
* - URL with host and path, e.g., @c host/foo;bar, is an HTTP URL
* - URL with host name, no path is a SIP URL.
*
* @param url pointer to URL struct to be sanitized (IN/OUT)
*
* @return
* The function url_sanitize() returns 0 if it considers URL to be
* sane, and -1 otherwise.
*/
int url_sanitize(url_t *url)
{
if (!url)
return -1;
else if (url->url_scheme != NULL)
/* xyzzy */;
else if (url->url_host == NULL)
return -1;
else if (strncasecmp(url->url_host, "ftp.", strlen("ftp.")) == 0)
url->url_type = url_ftp, url->url_scheme = "ftp", url->url_root = '/';
else if (strncasecmp(url->url_host, "www.", strlen("www.")) == 0
|| url->url_path)
url->url_type = url_http, url->url_scheme = "http", url->url_root = '/';
else
url->url_type = url_sip, url->url_scheme = "sip";
return 0;
}
#include <sofia-sip/su_md5.h>
static
void canon_update(su_md5_t *md5, char const *s, size_t n, char const *allow)
{
size_t i, j;
for (i = 0, j = 0; i < n && s[i]; i++) {
char c;
if (s[i] == '%' && i + 2 < n && IS_HEX(s[i+1]) && IS_HEX(s[i+2])) {
#define UNHEX(a) (a - (a >= 'a' ? 'a' - 10 : (a >= 'A' ? 'A' - 10 : '0')))
c = (UNHEX(s[i+1]) << 4) | UNHEX(s[i+2]);
#undef UNHEX
if (c != '%' && c > ' ' && c < '\177' &&
(!strchr(EXCLUDED, c) || strchr(allow, c))) {
if (i != j)
su_md5_iupdate(md5, s + j, i - j);
su_md5_iupdate(md5, &c, 1);
j = i + 3;
}
i += 2;
}
}
if (i != j)
su_md5_iupdate(md5, s + j, i - j);
}
/** Update MD5 sum with url-string contents */
static
void url_string_update(su_md5_t *md5, char const *s)
{
size_t n, p;
int have_authority = 1;
enum url_type_e type = url_any;
char const *at, *colon;
char schema[48];
if (s == NULL || strlen(s) == 0 || strcmp(s, "*") == 0) {
su_md5_update(md5, "*\0\0*", 4);
return;
}
n = strcspn(s, ":/?#");
if (n >= sizeof schema) {
su_md5_update(md5, ":", 1);
}
else if (n && s[n] == ':' ) {
at = url_canonize(schema, s, n, 0, "+");
type = url_get_type(schema, at - schema);
su_md5_iupdate(md5, schema, at - schema);
have_authority = !url_type_is_opaque(type);
s += n + 1;
}
else {
su_md5_update(md5, "", 1);
}
if (type == url_sip || type == url_sips) {
/* SIP URL may have /;? in user part but no path */
/* user-unreserved = "&" / "=" / "+" / "$" / "," / ";" / "?" / "/" */
/* Some #*@#* phones include unescaped # there, too */
n = strcspn(s, "@/;?#");
p = strcspn(s + n, "@");
if (s[n + p] == '@') {
n += p;
/* Ignore password in hash */
colon = memchr(s, ':', n);
p = colon ? (size_t)(colon - s) : n;
canon_update(md5, s, p, SIP_USER_UNRESERVED);
s += n + 1; n = 0;
}
else
su_md5_iupdate(md5, "", 1); /* user */
n += strcspn(s + n, "/;?#");
}
else if (have_authority) {
if (type == url_wv) { /* WV URL may have / in user part */
n = strcspn(s, "@;?#");
}
else if (type != url_wv && s[0] == '/' && s[1] != '/') {
/* foo:/bar */
su_md5_update(md5, "\0\0", 2); /* user, host */
su_md5_striupdate(md5, url_port_default(type));
return;
}
else if (s[0] == '/' && s[1] == '/') {
/* We have authority, / / foo or foo */
s += 2;
n = strcspn(s, "/?#@[]");
}
else
n = strcspn(s, "@;/?#");
if (s[n] == '@') {
/* Ignore password in hash */
colon = type != url_unknown ? memchr(s, ':', n) : NULL;
p = colon ? (size_t)(colon - s) : n;
canon_update(md5, s, p, SIP_USER_UNRESERVED);
s += n + 1;
n = strcspn(s, "/;?#"); /* Until path, query or fragment */
}
else {
su_md5_iupdate(md5, "", 1); /* user */
n += strcspn(s + n, "/;?#"); /* Until path, query or fragment */
}
}
else /* if (!have_authority) */ {
n = strcspn(s, ":/;?#"); /* Until pass, path, query or fragment */
canon_update(md5, s, n, ""); /* user */
su_md5_update(md5, "\0", 1); /* host, no port */
su_md5_striupdate(md5, url_port_default(type));
return;
}
if (n > 0 && s[0] == '[') { /* IPv6reference */
colon = memchr(s, ']', n);
if (colon == NULL || ++colon == s + n || *colon != ':')
colon = NULL;
}
else
colon = memchr(s, ':', n);
if (colon) {
canon_update(md5, s, colon - s, ""); /* host */
canon_update(md5, colon + 1, (s + n) - (colon + 1), "");
}
else {
canon_update(md5, s, n, ""); /* host */
su_md5_strupdate(md5, url_port_default(type)); /* port */
}
/* ignore parameters/path/headers.... */
}
/** Update md5 digest with contents of URL.
*
*/
void url_update(su_md5_t *md5, url_t const *url)
{
if (url_string_p((url_string_t *)url)) {
url_string_update(md5, (char const *)url);
}
else {
SU_MD5_STRI0UPDATE(md5, url->url_scheme);
SU_MD5_STRI0UPDATE(md5, url->url_user);
SU_MD5_STRI0UPDATE(md5, url->url_host);
su_md5_striupdate(md5, URL_PORT(url));
/* XXX - parameters/path.... */
/* SU_MD5_STRI0UPDATE(md5, url->url_path); */
}
}
/** Calculate a digest from URL contents. */
void url_digest(void *hash, int hsize, url_t const *url, char const *key)
{
su_md5_t md5[1];
uint8_t digest[SU_MD5_DIGEST_SIZE];
su_md5_init(md5);
if (key) su_md5_strupdate(md5, key);
url_update(md5, url);
su_md5_digest(md5, digest);
if (hsize > SU_MD5_DIGEST_SIZE) {
memset((char *)hash + SU_MD5_DIGEST_SIZE, 0, hsize - SU_MD5_DIGEST_SIZE);
hsize = SU_MD5_DIGEST_SIZE;
}
memcpy(hash, digest, hsize);
}
/** Convert a URL query to a header string.
*
* URL query is converted by replacing each "=" in header name "=" value
* pair with semicolon (":"), and the "&" separating header-name-value pairs
* with line feed ("\n"). The "body" pseudoheader is moved last in the
* string. The %-escaping is removed. Note that if the @a query contains %00,
* the resulting string will be truncated.
*
* @param home memory home used to alloate string (if NULL, malloc() it)
* @param query query part from SIP URL
*
* The result string is allocated from @a home, and it can be used as
* argument to msg_header_parse_str(), msg_header_add_str() or
* SIPTAG_HEADER_STR().
*
* @sa msg_header_add_str(), SIPTAG_HEADER_STR(),
* sip_headers_as_url_query(), sip_url_query_as_taglist(),
* @RFC3261 section 19.1.1 "Headers", #url_t, url_s#url_headers,
* url_unescape(), url_unescape_to()
*
* @since New in @VERSION_1_12_4.
*/
char *url_query_as_header_string(su_home_t *home,
char const *query)
{
size_t i, j, n, b_start = 0, b_len = 0;
char *s = su_strdup(home, query);
if (!s)
return NULL;
for (i = 0, j = 0; s[i];) {
n = strcspn(s + i, "=");
if (!s[i + n])
break;
if (n == 4 && strncasecmp(s + i, "body", 4) == 0) {
if (b_start)
break;
b_start = i + n + 1, b_len = strcspn(s + b_start, "&");
i = b_start + b_len + 1;
continue;
}
if (i != j)
memmove(s + j, s + i, n);
s[j + n] = ':';
i += n + 1, j += n + 1;
n = strcspn(s + i, "&");
j += url_unescape_to(s + j, s + i, n);
i += n;
if (s[i]) {
s[j++] = '\n', i++;
}
}
if (s[i])
return (void)su_free(home, s), NULL;
if (b_start) {
s[j++] = '\n', s[j++] = '\n';
j += url_unescape_to(s + j, query + b_start, b_len);
}
s[j] = '\0'; assert(j <= i);
return s;
}