Kill off a long-hated Camel kludge: "empty" URLs and
[platform/upstream/evolution-data-server.git] / camel / camel-url.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-url.c : utility functions to parse URLs */
3
4
5 /* 
6  * Authors:
7  *  Bertrand Guiheneuf <bertrand@helixcode.com>
8  *  Dan Winship <danw@helixcode.com>
9  *  Tiago Antào <tiagoantao@bigfoot.com>
10  *  Jeffrey Stedfast <fejj@helixcode.com>
11  *
12  * Copyright 1999, 2000 Helix Code, Inc. (http://www.helixcode.com)
13  *
14  * This program is free software; you can redistribute it and/or 
15  * modify it under the terms of the GNU General Public License as 
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
27  * USA
28  */
29
30 #include <config.h>
31
32 #include <ctype.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include "camel-url.h"
37 #include "camel-mime-utils.h"
38 #include "camel-exception.h"
39 #include "camel-object.h"
40
41 /**
42  * camel_url_new: create a CamelURL object from a string
43  * @url_string: The string containing the URL to scan
44  * 
45  * This routine takes a string and parses it as a URL of the form:
46  *
47  *   protocol://user;AUTH=mech:password@host:port/path
48  *
49  * The protocol, followed by a ":" is required. If it is followed by * "//",
50  * there must be an "authority" containing at least a host,
51  * which ends at the end of the string or at the next "/". If there
52  * is an "@" in the authority, there must be a username before it,
53  * and the host comes after it. The authmech, password, and port are
54  * optional, and the punctuation that preceeds them is omitted if
55  * they are. Everything after the authority (or everything after the
56  * protocol if there was no authority) is the path. We consider the
57  * "/" between the authority and the path to be part of the path,
58  * although this is incorrect according to RFC 1738.
59  *
60  * The port, if present, must be numeric.
61  * 
62  * Return value: a CamelURL structure containing the URL items.
63  **/
64 CamelURL *
65 camel_url_new (const char *url_string, CamelException *ex)
66 {
67         CamelURL *url;
68         char *semi, *colon, *at, *slash;
69         char *p;
70         
71         /* Find protocol: initial substring until ":" */
72         colon = strchr (url_string, ':');
73         if (!colon) {
74                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
75                                       _("URL string `%s' contains no protocol"),
76                                       url_string);
77                 return NULL;
78         }
79         
80         url = g_new0 (CamelURL, 1);
81         url->protocol = g_strndup (url_string, colon - url_string);
82         g_strdown (url->protocol);
83
84         /* Check protocol */
85         p = url->protocol;
86         while (*p) {
87                 if (!((*p >= 'a' && *p <= 'z') ||
88                       (*p == '-') || (*p == '+') || (*p == '.'))) {
89                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
90                                               _("URL string `%s' contains an invalid protocol"),
91                                               url_string);
92                         return NULL;
93                 }
94                 p++;
95         }
96
97         if (strncmp (colon, "://", 3) != 0) {
98                 if (*(colon + 1)) {
99                         url->path = g_strdup (colon + 1);
100                         camel_url_decode (url->path);
101                 }
102                 return url;
103         }
104
105         url_string = colon + 3;
106
107         /* If there is an @ sign in the authority, look for user,
108          * authmech, and password before it.
109          */
110         slash = strchr (url_string, '/');
111         at = strchr (url_string, '@');
112         if (at && (!slash || at < slash)) {
113                 colon = strchr (url_string, ':');
114                 if (colon && colon < at) {
115                         url->passwd = g_strndup (colon + 1, at - colon - 1);
116                         camel_url_decode (url->passwd);
117                 } else {
118                         url->passwd = NULL;
119                         colon = at;
120                 }
121
122                 semi = strchr(url_string, ';');
123                 if (semi && (semi < colon || (!colon && semi < at)) &&
124                     !strncasecmp (semi, ";auth=", 6)) {
125                         url->authmech = g_strndup (semi + 6,
126                                                      colon - semi - 6);
127                         camel_url_decode (url->authmech);
128                 } else {
129                         url->authmech = NULL;
130                         semi = colon;
131                 }
132
133                 url->user = g_strndup (url_string, semi - url_string);
134                 camel_url_decode (url->user);
135                 url_string = at + 1;
136         } else
137                 url->user = url->passwd = url->authmech = NULL;
138
139         /* Find host and port. */
140         slash = strchr (url_string, '/');
141         colon = strchr (url_string, ':');
142         if (slash && colon > slash)
143                 colon = NULL;
144
145         if (colon) {
146                 url->host = g_strndup (url_string, colon - url_string);
147                 url->port = strtoul (colon + 1, &colon, 10);
148                 if (*colon && colon != slash) {
149                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
150                                               _("Port number in URL `%s' is non-"
151                                                 "numeric"), url_string);
152                         camel_url_free (url);
153                         return NULL;
154                 }
155         } else if (slash) {
156                 url->host = g_strndup (url_string, slash - url_string);
157                 camel_url_decode (url->host);
158                 url->port = 0;
159         } else {
160                 url->host = g_strdup (url_string);
161                 camel_url_decode (url->host);
162                 url->port = 0;
163         }
164
165         if (!slash)
166                 slash = "/";
167         url->path = g_strdup (slash);
168         camel_url_decode (url->path);
169
170         return url;
171 }
172
173 char *
174 camel_url_to_string (CamelURL *url, gboolean show_passwd)
175 {
176         char *return_result;
177         char *user = NULL, *authmech = NULL, *passwd = NULL;
178         char *host = NULL, *path = NULL;
179         char port[20];
180
181         if (url->user)
182                 user = camel_url_encode (url->user, TRUE, ":;@/");
183         
184         if (url->authmech && *url->authmech)
185                 authmech = camel_url_encode (url->authmech, TRUE, ":@/");
186         
187         if (show_passwd && url->passwd)
188                 passwd = camel_url_encode (url->passwd, TRUE, "@/");
189         
190         if (url->host)
191                 host = camel_url_encode (url->host, TRUE, ":/");
192         
193         if (url->port)
194                 g_snprintf (port, sizeof (port), "%d", url->port);
195         else
196                 *port = '\0';
197         
198         if (url->path)
199                 path = camel_url_encode (url->path, FALSE, NULL);
200
201         return_result = g_strdup_printf ("%s:%s%s%s%s%s%s%s%s%s%s%s%s",
202                                          url->protocol,
203                                          host ? "//" : "",
204                                          user ? user : "",
205                                          authmech ? ";auth=" : "",
206                                          authmech ? authmech : "",
207                                          passwd ? ":" : "",
208                                          passwd ? passwd : "",
209                                          user ? "@" : "",
210                                          host ? host : "",
211                                          *port ? ":" : "",
212                                          port,
213                                          path && host && *path != '/' ? "/" : "",
214                                          path ? path : "");
215         g_free (user);
216         g_free (authmech);
217         g_free (passwd);
218         g_free (host);
219         g_free (path);
220
221         return return_result;
222 }
223
224 void
225 camel_url_free (CamelURL *url)
226 {
227         g_assert (url);
228
229         g_free (url->protocol);
230         g_free (url->user);
231         g_free (url->authmech);
232         g_free (url->passwd);
233         g_free (url->host);
234         g_free (url->path);
235
236         g_free (url);
237 }
238
239 void camel_url_set_protocol(CamelURL *url, const char *p)
240 {
241         g_free(url->protocol);
242         url->protocol = g_strdup(p);
243 }
244
245 void camel_url_set_host(CamelURL *url, const char *h)
246 {
247         g_free(url->host);
248         url->host = g_strdup(h);
249 }
250
251 void camel_url_set_port(CamelURL *url, int port)
252 {
253         url->port = port;
254 }
255 void camel_url_set_path(CamelURL *url, const char *p)
256 {
257         g_free(url->path);
258         url->path = g_strdup(p);
259 }
260
261
262 /**
263  * camel_url_encode:
264  * @part: a URL part
265  * @escape_unsafe: whether or not to %-escape "unsafe" characters.
266  * ("%#<>{}|\^~[]`)
267  * @escape_extra: additional characters to escape.
268  *
269  * This %-encodes the given URL part and returns the escaped version
270  * in allocated memory, which the caller must free when it is done.
271  **/
272 char *
273 camel_url_encode (char *part, gboolean escape_unsafe, char *escape_extra)
274 {
275         char *work, *p;
276
277         /* worst case scenario = 3 times the initial */
278         p = work = g_malloc (3 * strlen (part) + 1);
279
280         while (*part) {
281                 if (((guchar) *part >= 127) || ((guchar) *part <= ' ') ||
282                     (escape_unsafe && strchr ("\"%#<>{}|\\^~[]`", *part)) ||
283                     (escape_extra && strchr (escape_extra, *part))) {
284                         sprintf (p, "%%%.02hX", (guchar) *part++);
285                         p += 3;
286                 } else
287                         *p++ = *part++;
288         }
289         *p = '\0';
290
291         return work;
292 }
293
294 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10)
295
296 /**
297  * camel_url_decode:
298  * @part: a URL part
299  *
300  * %-decodes the passed-in URL *in place*. The decoded version is
301  * never longer than the encoded version, so there does not need to
302  * be any additional space at the end of the string.
303  */
304 void
305 camel_url_decode (char *part)
306 {
307         guchar *s, *d;
308
309         s = d = (guchar *)part;
310         while (*s) {
311                 if (*s == '%') {
312                         if (isxdigit (s[1]) && isxdigit (s[2])) {
313                                 *d++ = HEXVAL (s[1]) * 16 + HEXVAL (s[2]);
314                                 s += 3;
315                         } else
316                                 *d++ = *s++;
317                 } else
318                         *d++ = *s++;
319         }
320         *d = '\0';
321 }
322
323 static void
324 add_hash (guint *hash, char *s)
325 {
326         if (s)
327                 *hash ^= g_str_hash(s);
328 }
329
330 guint camel_url_hash (const void *v)
331 {
332         const CamelURL *u = v;
333         guint hash = 0;
334
335         add_hash (&hash, u->protocol);
336         add_hash (&hash, u->user);
337         add_hash (&hash, u->authmech);
338         add_hash (&hash, u->host);
339         add_hash (&hash, u->path);
340         hash ^= u->port;
341         
342         return hash;
343 }
344
345 static int
346 check_equal (char *s1, char *s2)
347 {
348         if (s1 == NULL) {
349                 if (s2 == NULL)
350                         return TRUE;
351                 else
352                         return FALSE;
353         }
354         
355         if (s2 == NULL)
356                 return FALSE;
357
358         return strcmp (s1, s2) == 0;
359 }
360
361 int camel_url_equal(const void *v, const void *v2)
362 {
363         const CamelURL *u1 = v, *u2 = v2;
364         
365         return check_equal(u1->protocol, u2->protocol)
366                 && check_equal(u1->user, u2->user)
367                 && check_equal(u1->authmech, u2->authmech)
368                 && check_equal(u1->host, u2->host)
369                 && check_equal(u1->path, u2->path)
370                 && u1->port == u2->port;
371 }