/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* camel-url.c : utility functions to parse URLs */ /* * Authors: * Bertrand Guiheneuf <bertrand@helixcode.com> * Dan Winship <danw@helixcode.com> * Tiago Ant�o <tiagoantao@bigfoot.com> * Jeffrey Stedfast <fejj@helixcode.com> * * Copyright 1999, 2000 Helix Code, Inc. (http://www.helixcode.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ #include <config.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "camel-url.h" #include "camel-exception.h" /** * camel_url_new: create a CamelURL object from a string * @url_string: The string containing the URL to scan * * This routine takes a string and parses it as a URL of the form: * * protocol://user;AUTH=mech:password@host:port/path * * The protocol, followed by a ":" is required. If it is followed by * "//", * there must be an "authority" containing at least a host, * which ends at the end of the string or at the next "/". If there * is an "@" in the authority, there must be a username before it, * and the host comes after it. The authmech, password, and port are * optional, and the punctuation that preceeds them is omitted if * they are. Everything after the authority (or everything after the * protocol if there was no authority) is the path. We consider the * "/" between the authority and the path to be part of the path, * although this is incorrect according to RFC 1738. * * The port, if present, must be numeric. * * If nothing but the protocol (and the ":") is present, the "empty" * flag will be set on the returned URL. * * Return value: a CamelURL structure containing the URL items. **/ CamelURL * camel_url_new (const char *url_string, CamelException *ex) { CamelURL *url; char *semi, *colon, *at, *slash; char *p; /* Find protocol: initial substring until ":" */ colon = strchr (url_string, ':'); if (!colon) { camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID, "URL string `%s' contains no protocol", url_string); return NULL; } url = g_new0 (CamelURL, 1); url->protocol = g_strndup (url_string, colon - url_string); g_strdown (url->protocol); /* Check protocol */ p = url->protocol; while (*p) { if (!((*p >= 'a' && *p <= 'z') || (*p == '-') || (*p == '+') || (*p == '.'))) { camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID, "URL string `%s' contains an invalid protocol", url_string); return NULL; } p++; } if (strncmp (colon, "://", 3) != 0) { if (*(colon + 1)) { url->path = g_strdup (colon + 1); camel_url_decode (url->path); } else url->empty = TRUE; return url; } url_string = colon + 3; /* If there is an @ sign in the authority, look for user, * authmech, and password before it. */ slash = strchr (url_string, '/'); at = strchr (url_string, '@'); if (at && (!slash || at < slash)) { colon = strchr (url_string, ':'); if (colon && colon < at) { url->passwd = g_strndup (colon + 1, at - colon - 1); camel_url_decode (url->passwd); } else { url->passwd = NULL; colon = at; } semi = strchr(url_string, ';'); if (semi && (semi < colon || (!colon && semi < at)) && !strncasecmp (semi, ";auth=", 6)) { url->authmech = g_strndup (semi + 6, colon - semi - 6); camel_url_decode (url->authmech); } else { url->authmech = NULL; semi = colon; } url->user = g_strndup (url_string, semi - url_string); camel_url_decode (url->user); url_string = at + 1; } else url->user = url->passwd = url->authmech = NULL; /* Find host and port. */ slash = strchr (url_string, '/'); colon = strchr (url_string, ':'); if (slash && colon > slash) colon = NULL; if (colon) { url->host = g_strndup (url_string, colon - url_string); url->port = strtoul (colon + 1, &colon, 10); if (*colon && colon != slash) { camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID, "Port number in URL `%s' is non-" "numeric", url_string); camel_url_free (url); return NULL; } } else if (slash) { url->host = g_strndup (url_string, slash - url_string); camel_url_decode (url->host); url->port = 0; } else { url->host = g_strdup (url_string); camel_url_decode (url->host); url->port = 0; } if (!slash) slash = "/"; url->path = g_strdup (slash); camel_url_decode (url->path); return url; } char * camel_url_to_string (CamelURL *url, gboolean show_passwd) { char *return_result; char *user = NULL, *authmech = NULL, *passwd = NULL; char *host = NULL, *path = NULL; char port[20]; if (url->user) user = camel_url_encode (url->user, TRUE, ":;@/"); if (url->authmech) authmech = camel_url_encode (url->authmech, TRUE, ":@/"); if (show_passwd && url->passwd) passwd = camel_url_encode (url->passwd, TRUE, "@/"); if (url->host) host = camel_url_encode (url->host, TRUE, ":/"); if (url->port) g_snprintf (port, sizeof (port), "%d", url->port); else *port = '\0'; if (url->path) path = camel_url_encode (url->path, FALSE, NULL); return_result = g_strdup_printf ("%s:%s%s%s%s%s%s%s%s%s%s%s%s", url->protocol, host ? "//" : "", user ? user : "", authmech ? ";auth=" : "", authmech ? authmech : "", passwd ? ":" : "", passwd ? passwd : "", user ? "@" : "", host ? host : "", *port ? ":" : "", port, path && host && *path != '/' ? "/" : "", path ? path : ""); g_free (user); g_free (authmech); g_free (passwd); g_free (host); g_free (path); return return_result; } void camel_url_free (CamelURL *url) { g_assert (url); g_free (url->protocol); g_free (url->user); g_free (url->authmech); g_free (url->passwd); g_free (url->host); g_free (url->path); g_free (url); } void camel_url_set_protocol(CamelURL *url, const char *p) { g_free(url->protocol); url->protocol = g_strdup(p); } void camel_url_set_host(CamelURL *url, const char *h) { g_free(url->host); url->host = g_strdup(h); } void camel_url_set_port(CamelURL *url, int port) { url->port = port; } void camel_url_set_path(CamelURL *url, const char *p) { g_free(url->path); url->path = g_strdup(p); } /** * camel_url_encode: * @part: a URL part * @escape_unsafe: whether or not to %-escape "unsafe" characters. * ("%#<>{}|\^~[]`) * @escape_extra: additional characters to escape. * * This %-encodes the given URL part and returns the escaped version * in allocated memory, which the caller must free when it is done. **/ char * camel_url_encode (char *part, gboolean escape_unsafe, char *escape_extra) { char *work, *p; /* worst case scenario = 3 times the initial */ p = work = g_malloc (3 * strlen (part) + 1); while (*part) { if (((guchar) *part >= 127) || ((guchar) *part <= ' ') || (escape_unsafe && strchr ("\"%#<>{}|\\^~[]`", *part)) || (escape_extra && strchr (escape_extra, *part))) { sprintf (p, "%%%.02hX", (guchar) *part++); p += 3; } else *p++ = *part++; } *p = '\0'; return work; } #define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10) /** * camel_url_decode: * @part: a URL part * * %-decodes the passed-in URL *in place*. The decoded version is * never longer than the encoded version, so there does not need to * be any additional space at the end of the string. */ void camel_url_decode (char *part) { guchar *s, *d; s = d = (guchar *)part; while (*s) { if (*s == '%') { if (isxdigit (s[1]) && isxdigit (s[2])) { *d++ = HEXVAL (s[1]) * 16 + HEXVAL (s[2]); s += 3; } else *d++ = *s++; } else *d++ = *s++; } *d = '\0'; } static void add_hash (guint *hash, char *s) { if (s) *hash ^= g_str_hash(s); } guint camel_url_hash (const void *v) { const CamelURL *u = v; guint hash = 0; add_hash (&hash, u->protocol); add_hash (&hash, u->user); add_hash (&hash, u->authmech); add_hash (&hash, u->host); add_hash (&hash, u->path); hash ^= u->port; return hash; } static int check_equal (char *s1, char *s2) { if (s1 == NULL) { if (s2 == NULL) return TRUE; else return FALSE; } if (s2 == NULL) return FALSE; return strcmp (s1, s2) == 0; } int camel_url_equal(const void *v, const void *v2) { const CamelURL *u1 = v, *u2 = v2; return check_equal(u1->protocol, u2->protocol) && check_equal(u1->user, u2->user) && check_equal(u1->authmech, u2->authmech) && check_equal(u1->host, u2->host) && check_equal(u1->path, u2->path) && u1->port == u2->port; } d><td>1</td><td><span class='deletions'>-0</span>/<span class='insertions'>+1</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=1068ae6edbfdf135636b0c961f416ec77d3fa5d9'>Add an explicit dependency on pkgconf</a></td><td>bapt</td><td><span title='2013-09-03 04:51:40 +0800'>2013-09-03</span></td><td>1</td><td><span class='deletions'>-2</span>/<span class='insertions'>+1</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=be0532aa1927751208f2697351ec6365d64f46e3'>- Convert USE_GETTEXT to USES (part 4)</a></td><td>ak</td><td><span title='2013-04-26 18:44:28 +0800'>2013-04-26</span></td><td>1</td><td><span class='deletions'>-2</span>/<span class='insertions'>+1</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=4f3e737e3339df65a7737d86948a5c8dacedae60'>Convert almost all gnome@ ports to OptionsNG, trim header, use USES=pathfix</a></td><td>kwm</td><td><span title='2013-03-19 18:40:22 +0800'>2013-03-19</span></td><td>1</td><td><span class='deletions'>-11</span>/<span class='insertions'>+10</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=6b6a7576e9cb35e982e07842b41335b933cbdee7'>Fix the build with new binutils.</a></td><td>mezz</td><td><span title='2012-12-29 14:22:21 +0800'>2012-12-29</span></td><td>1</td><td><span class='deletions'>-0</span>/<span class='insertions'>+1</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=530706893f31269bbfa303e069f2e76cbbe2fce7'>- update png to 1.5.10</a></td><td>dinoex</td><td><span title='2012-06-01 13:26:28 +0800'>2012-06-01</span></td><td>1</td><td><span class='deletions'>-1</span>/<span class='insertions'>+1</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=e61d6a701f43e8cec5a4ec8c9641daf5376441de'>- Add LDFLAGS to CONFIGURE_ENV and MAKE_ENV (as it was done with LDFLAGS)</a></td><td>amdmi3</td><td><span title='2011-09-24 06:26:39 +0800'>2011-09-24</span></td><td>1</td><td><span class='deletions'>-2</span>/<span class='insertions'>+2</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=39ef19450c2cf8b75e3b20495c6e2331f67e3138'>Chase libnotify, libproxy and webkit-gtk2 shlib changes, and fix build where ...</a></td><td>kwm</td><td><span title='2011-08-24 02:39:19 +0800'>2011-08-24</span></td><td>2</td><td><span class='deletions'>-1</span>/<span class='insertions'>+17</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=3d272740920a1868e8acfed76d6e4c67c331e101'>Presenting GNOME 2.32.1 for FreeBSD. The offical release notes for this</a></td><td>kwm</td><td><span title='2010-11-20 23:37:08 +0800'>2010-11-20</span></td><td>3</td><td><span class='deletions'>-6</span>/<span class='insertions'>+19</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=338bcf898e6d90768d058c2b05f288f58d1371de'>Bounce PORTREVISION for gettext-related ports. Have fun, ya'll.</a></td><td>ade</td><td><span title='2010-05-31 10:01:56 +0800'>2010-05-31</span></td><td>1</td><td><span class='deletions'>-0</span>/<span class='insertions'>+1</span></td></tr> <tr><td class='commitgraph'>* </td><td><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports-gnome/commit/x11/zenity?h=gstreamer&id=6a2a890c5cd397f9f0287409c2aa0244d50f3573'>Presenting GNOME 2.30.1 for FreeBSD. The offical release notes for this</a></td><td>kwm</td><td><span title='2010-05-11 05:19:08 +0800'>2010-05-11</span>