123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160 |
- /*
- * 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:
- * @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];
- if (!h1) {
- *d = '\0';
- return NULL;
- }
- 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];
- if (!h1) {
- *d = '\0';
- return NULL;
- }
- 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_urn: return "urn";
- 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((enum url_type_e)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 'u': case 'U':
- test_scheme(urn); 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 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;
- }
- 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((enum url_type_e)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 && (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.
- * @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((enum url_type_e)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.
- * @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((enum url_type_e)url_type);
- else if (host_is_ip_address(a->url_host))
- a_port = b_port = url_port_default((enum url_type_e)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((enum url_type_e)url_type);
- else if (host_is_ip_address(a->url_host))
- a_port = b_port = url_port_default((enum url_type_e)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_urn:
- 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_urn:
- 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((enum url_type_e)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] = { 0 };
- 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;
- if (s[i] != '\0') i += 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;
- }
|