Update the licensing information to require version 2 of the GPL
[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  * Authors:
6  *  Dan Winship <danw@ximian.com>
7  *  Tiago Antào <tiagoantao@bigfoot.com>
8  *  Jeffrey Stedfast <fejj@ximian.com>
9  *
10  * Copyright 1999-2001 Ximian, Inc. (www.ximian.com)
11  *
12  * This program is free software; you can redistribute it and/or 
13  * modify it under the terms of version 2 of the GNU General Public 
14  * published by the Free Software Foundation; either version 2 of the
15  * License as published by the Free Software Foundation.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
25  * USA
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31
32 #include <ctype.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include "camel-url.h"
38 #include "camel-exception.h"
39 #include "camel-mime-utils.h"
40 #include "camel-object.h"
41
42 static void copy_param (GQuark key_id, gpointer data, gpointer user_data);
43 static void output_param (GQuark key_id, gpointer data, gpointer user_data);
44
45 /**
46  * camel_url_new_with_base:
47  * @base: a base URL
48  * @url_string: the URL
49  *
50  * Parses @url_string relative to @base.
51  *
52  * Return value: a parsed CamelURL.
53  **/
54 CamelURL *
55 camel_url_new_with_base (CamelURL *base, const char *url_string)
56 {
57         CamelURL *url;
58         const char *end, *hash, *colon, *semi, *at, *slash, *question;
59         const char *p;
60
61         url = g_new0 (CamelURL, 1);
62
63         /* See RFC1808 for details. IF YOU CHANGE ANYTHING IN THIS
64          * FUNCTION, RUN tests/misc/url AFTERWARDS.
65          */
66
67         /* Find fragment. */
68         end = hash = strchr (url_string, '#');
69         if (hash && hash[1]) {
70                 url->fragment = g_strdup (hash + 1);
71                 camel_url_decode (url->fragment);
72         } else
73                 end = url_string + strlen (url_string);
74
75         /* Find protocol: initial [a-z+.-]* substring until ":" */
76         p = url_string;
77         while (p < end && (isalnum ((unsigned char)*p) ||
78                            *p == '.' || *p == '+' || *p == '-'))
79                 p++;
80
81         if (p > url_string && *p == ':') {
82                 url->protocol = g_strndup (url_string, p - url_string);
83                 g_strdown (url->protocol);
84                 url_string = p + 1;
85         }
86
87         if (!*url_string && !base)
88                 return url;
89
90         /* Check for authority */
91         if (strncmp (url_string, "//", 2) == 0) {
92                 url_string += 2;
93
94                 slash = url_string + strcspn (url_string, "/#");
95                 at = strchr (url_string, '@');
96                 if (at && at < slash) {
97                         colon = strchr (url_string, ':');
98                         if (colon && colon < at) {
99                                 url->passwd = g_strndup (colon + 1,
100                                                          at - colon - 1);
101                                 camel_url_decode (url->passwd);
102                         } else {
103                                 url->passwd = NULL;
104                                 colon = at;
105                         }
106
107                         semi = strchr(url_string, ';');
108                         if (semi && semi < colon &&
109                             !strncasecmp (semi, ";auth=", 6)) {
110                                 url->authmech = g_strndup (semi + 6,
111                                                            colon - semi - 6);
112                                 camel_url_decode (url->authmech);
113                         } else {
114                                 url->authmech = NULL;
115                                 semi = colon;
116                         }
117
118                         url->user = g_strndup (url_string, semi - url_string);
119                         camel_url_decode (url->user);
120                         url_string = at + 1;
121                 } else
122                         url->user = url->passwd = url->authmech = NULL;
123
124                 /* Find host and port. */
125                 colon = strchr (url_string, ':');
126                 if (colon && colon < slash) {
127                         url->host = g_strndup (url_string, colon - url_string);
128                         url->port = strtoul (colon + 1, NULL, 10);
129                 } else {
130                         url->host = g_strndup (url_string, slash - url_string);
131                         camel_url_decode (url->host);
132                         url->port = 0;
133                 }
134
135                 url_string = slash;
136         }
137
138         /* Find query */
139         question = memchr (url_string, '?', end - url_string);
140         if (question) {
141                 if (question[1]) {
142                         url->query = g_strndup (question + 1,
143                                                 end - (question + 1));
144                         camel_url_decode (url->query);
145                 }
146                 end = question;
147         }
148
149         /* Find parameters */
150         semi = memchr (url_string, ';', end - url_string);
151         if (semi) {
152                 if (semi[1]) {
153                         const char *cur, *p, *eq;
154                         char *name, *value;
155
156                         for (cur = semi + 1; cur < end; cur = p + 1) {
157                                 p = memchr (cur, ';', end - cur);
158                                 if (!p)
159                                         p = end;
160                                 eq = memchr (cur, '=', p - cur);
161                                 if (eq) {
162                                         name = g_strndup (cur, eq - cur);
163                                         value = g_strndup (eq + 1, p - (eq + 1));
164                                         camel_url_decode (value);
165                                 } else {
166                                         name = g_strndup (cur, p - cur);
167                                         value = g_strdup ("");
168                                 }
169                                 camel_url_decode (name);
170                                 g_datalist_set_data_full (&url->params, name,
171                                                           value, g_free);
172                                 g_free (name);
173                         }
174                 }
175                 end = semi;
176         }
177
178         if (end != url_string) {
179                 url->path = g_strndup (url_string, end - url_string);
180                 camel_url_decode (url->path);
181         }
182
183         /* Apply base URL. Again, this is spelled out in RFC 1808. */
184         if (base && !url->protocol && url->host)
185                 url->protocol = g_strdup (base->protocol);
186         else if (base && !url->protocol) {
187                 if (!url->user && !url->authmech && !url->passwd &&
188                     !url->host && !url->port && !url->path &&
189                     !url->params && !url->query && !url->fragment)
190                         url->fragment = g_strdup (base->fragment);
191
192                 url->protocol = g_strdup (base->protocol);
193                 url->user = g_strdup (base->user);
194                 url->authmech = g_strdup (base->authmech);
195                 url->passwd = g_strdup (base->passwd);
196                 url->host = g_strdup (base->host);
197                 url->port = base->port;
198
199                 if (!url->path) {
200                         url->path = g_strdup (base->path);
201                         if (!url->params) {
202                                 g_datalist_foreach (&base->params, copy_param,
203                                                     &url->params);
204                                 if (!url->query)
205                                         url->query = g_strdup (base->query);
206                         }
207                 } else if (*url->path != '/') {
208                         char *newpath, *last, *p, *q;
209
210                         last = strrchr (base->path, '/');
211                         if (last) {
212                                 newpath = g_strdup_printf ("%.*s/%s",
213                                                            last - base->path,
214                                                            base->path,
215                                                            url->path);
216                         } else
217                                 newpath = g_strdup_printf ("/%s", url->path);
218
219                         /* Remove "./" where "." is a complete segment. */
220                         for (p = newpath + 1; *p; ) {
221                                 if (*(p - 1) == '/' &&
222                                     *p == '.' && *(p + 1) == '/')
223                                         memmove (p, p + 2, strlen (p + 2) + 1);
224                                 else
225                                         p++;
226                         }
227                         /* Remove "." at end. */
228                         if (p > newpath + 2 &&
229                             *(p - 1) == '.' && *(p - 2) == '/')
230                                 *(p - 1) = '\0';
231                         /* Remove "<segment>/../" where <segment> != ".." */
232                         for (p = newpath + 1; *p; ) {
233                                 if (!strncmp (p, "../", 3)) {
234                                         p += 3;
235                                         continue;
236                                 }
237                                 q = strchr (p + 1, '/');
238                                 if (!q)
239                                         break;
240                                 if (strncmp (q, "/../", 4) != 0) {
241                                         p = q + 1;
242                                         continue;
243                                 }
244                                 memmove (p, q + 4, strlen (q + 4) + 1);
245                                 p = newpath + 1;
246                         }
247                         /* Remove "<segment>/.." at end */
248                         q = strrchr (newpath, '/');
249                         if (q && !strcmp (q, "/..")) {
250                                 p = q - 1;
251                                 while (p > newpath && *p != '/')
252                                         p--;
253                                 if (strncmp (p, "/../", 4) != 0)
254                                         *(p + 1) = 0;
255                         }
256                         g_free (url->path);
257                         url->path = newpath;
258                 }
259         }
260
261         return url;
262 }
263
264 static void
265 copy_param (GQuark key_id, gpointer data, gpointer user_data)
266 {
267         GData **copy = user_data;
268
269         g_datalist_id_set_data_full (copy, key_id, g_strdup (data), g_free);
270 }
271
272 /**
273  * camel_url_new:
274  * @url_string: a URL
275  * @ex: a CamelException
276  *
277  * Parses an absolute URL.
278  *
279  * Return value: a CamelURL, or %NULL.
280  **/
281 CamelURL *
282 camel_url_new (const char *url_string, CamelException *ex)
283 {
284         CamelURL *url = camel_url_new_with_base (NULL, url_string);
285
286         if (!url->protocol) {
287                 camel_url_free (url);
288                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
289                                       _("Could not parse URL `%s'"),
290                                       url_string);
291                 return NULL;
292         }
293         return url;
294 }
295
296 /**
297  * camel_url_to_string:
298  * @url: a CamelURL
299  * @flags: additional translation options.
300  *
301  * Return value: a string representing @url, which the caller must free.
302  **/
303 char *
304 camel_url_to_string (CamelURL *url, guint32 flags)
305 {
306         GString *str;
307         char *enc, *return_result;
308
309         /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
310          * tests/misc/url AFTERWARD.
311          */
312
313         str = g_string_sized_new (20);
314
315         if (url->protocol)
316                 g_string_sprintfa (str, "%s:", url->protocol);
317         if (url->host) {
318                 g_string_append (str, "//");
319                 if (url->user) {
320                         enc = camel_url_encode (url->user, TRUE, ":;@/");
321                         g_string_append (str, enc);
322                         g_free (enc);
323                 }
324                 if (url->authmech && *url->authmech) {
325                         enc = camel_url_encode (url->authmech, TRUE, ":@/");
326                         g_string_sprintfa (str, ";auth=%s", enc);
327                         g_free (enc);
328                 }
329                 if (url->passwd && !(flags & CAMEL_URL_HIDE_PASSWORD)) {
330                         enc = camel_url_encode (url->passwd, TRUE, "@/");
331                         g_string_sprintfa (str, ":%s", enc);
332                         g_free (enc);
333                 }
334                 if (url->host) {
335                         enc = camel_url_encode (url->host, TRUE, ":/");
336                         g_string_sprintfa (str, "%s%s", url->user ? "@" : "", enc);
337                         g_free (enc);
338                 }
339                 if (url->port)
340                         g_string_sprintfa (str, ":%d", url->port);
341                 if (!url->path && (url->params || url->query || url->fragment))
342                         g_string_append_c (str, '/');
343         }
344
345         if (url->path) {
346                 enc = camel_url_encode (url->path, FALSE, ";?#");
347                 g_string_sprintfa (str, "%s", enc);
348                 g_free (enc);
349         }
350         if (url->params && !(flags & CAMEL_URL_HIDE_PARAMS))
351                 g_datalist_foreach (&url->params, output_param, str);
352         if (url->query) {
353                 enc = camel_url_encode (url->query, FALSE, "#");
354                 g_string_sprintfa (str, "?%s", enc);
355                 g_free (enc);
356         }
357         if (url->fragment) {
358                 enc = camel_url_encode (url->fragment, FALSE, NULL);
359                 g_string_sprintfa (str, "#%s", enc);
360                 g_free (enc);
361         }
362
363         return_result = str->str;
364         g_string_free (str, FALSE);
365         return return_result;
366 }
367
368 static void
369 output_param (GQuark key_id, gpointer data, gpointer user_data)
370 {
371         GString *str = user_data;
372         char *enc;
373
374         enc = camel_url_encode (g_quark_to_string (key_id), FALSE, "?#");
375         g_string_sprintfa (str, ";%s", enc);
376         g_free (enc);
377         if (*(char *)data) {
378                 enc = camel_url_encode (data, FALSE, "?#");
379                 g_string_sprintfa (str, "=%s", enc);
380                 g_free (enc);
381         }
382 }
383
384 /**
385  * camel_url_free:
386  * @url: a CamelURL
387  *
388  * Frees @url
389  **/
390 void
391 camel_url_free (CamelURL *url)
392 {
393         if (url) {
394                 g_free (url->protocol);
395                 g_free (url->user);
396                 g_free (url->authmech);
397                 g_free (url->passwd);
398                 g_free (url->host);
399                 g_free (url->path);
400                 g_datalist_clear (&url->params);
401                 g_free (url->query);
402                 g_free (url->fragment);
403                 
404                 g_free (url);
405         }
406 }
407
408
409 #define DEFINE_CAMEL_URL_SET(part)                      \
410 void                                                    \
411 camel_url_set_##part (CamelURL *url, const char *part)  \
412 {                                                       \
413         g_free (url->part);                             \
414         url->part = g_strdup (part);                    \
415 }
416
417 DEFINE_CAMEL_URL_SET (protocol)
418 DEFINE_CAMEL_URL_SET (user)
419 DEFINE_CAMEL_URL_SET (authmech)
420 DEFINE_CAMEL_URL_SET (passwd)
421 DEFINE_CAMEL_URL_SET (host)
422 DEFINE_CAMEL_URL_SET (path)
423 DEFINE_CAMEL_URL_SET (query)
424 DEFINE_CAMEL_URL_SET (fragment)
425
426 void
427 camel_url_set_port (CamelURL *url, int port)
428 {
429         url->port = port;
430 }
431
432 void
433 camel_url_set_param (CamelURL *url, const char *name, const char *value)
434 {
435         g_datalist_set_data_full (&url->params, name, value ? g_strdup (value) : NULL, g_free);
436 }
437
438 const char *
439 camel_url_get_param (CamelURL *url, const char *name)
440 {
441         return g_datalist_get_data (&url->params, name);
442 }
443
444
445 /**
446  * camel_url_encode:
447  * @part: a URL part
448  * @escape_unsafe: whether or not to %-escape "unsafe" characters.
449  * ("%#<>{}|\^~[]`)
450  * @escape_extra: additional characters to escape.
451  *
452  * This %-encodes the given URL part and returns the escaped version
453  * in allocated memory, which the caller must free when it is done.
454  **/
455 char *
456 camel_url_encode (const char *part, gboolean escape_unsafe,
457                   const char *escape_extra)
458 {
459         char *work, *p;
460
461         /* worst case scenario = 3 times the initial */
462         p = work = g_malloc (3 * strlen (part) + 1);
463
464         while (*part) {
465                 if (((guchar) *part >= 127) || ((guchar) *part <= ' ') ||
466                     (escape_unsafe && strchr ("\"%#<>{}|\\^~[]`", *part)) ||
467                     (escape_extra && strchr (escape_extra, *part))) {
468                         sprintf (p, "%%%.02hX", (guchar) *part++);
469                         p += 3;
470                 } else
471                         *p++ = *part++;
472         }
473         *p = '\0';
474
475         return work;
476 }
477
478 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10)
479
480 /**
481  * camel_url_decode:
482  * @part: a URL part
483  *
484  * %-decodes the passed-in URL *in place*. The decoded version is
485  * never longer than the encoded version, so there does not need to
486  * be any additional space at the end of the string.
487  */
488 void
489 camel_url_decode (char *part)
490 {
491         guchar *s, *d;
492
493         s = d = (guchar *)part;
494         while (*s) {
495                 if (*s == '%') {
496                         if (isxdigit (s[1]) && isxdigit (s[2])) {
497                                 *d++ = HEXVAL (s[1]) * 16 + HEXVAL (s[2]);
498                                 s += 3;
499                         } else
500                                 *d++ = *s++;
501                 } else
502                         *d++ = *s++;
503         }
504         *d = '\0';
505 }
506
507 guint
508 camel_url_hash (const void *v)
509 {
510         const CamelURL *u = v;
511         guint hash = 0;
512
513 #define ADD_HASH(s) if (s) hash ^= g_str_hash (s);
514
515         ADD_HASH (u->protocol);
516         ADD_HASH (u->user);
517         ADD_HASH (u->authmech);
518         ADD_HASH (u->host);
519         ADD_HASH (u->path);
520         ADD_HASH (u->query);
521         hash ^= u->port;
522         
523         return hash;
524 }
525
526 static int
527 check_equal (char *s1, char *s2)
528 {
529         if (s1 == NULL) {
530                 if (s2 == NULL)
531                         return TRUE;
532                 else
533                         return FALSE;
534         }
535         
536         if (s2 == NULL)
537                 return FALSE;
538
539         return strcmp (s1, s2) == 0;
540 }
541
542 int
543 camel_url_equal(const void *v, const void *v2)
544 {
545         const CamelURL *u1 = v, *u2 = v2;
546
547         return check_equal(u1->protocol, u2->protocol)
548                 && check_equal(u1->user, u2->user)
549                 && check_equal(u1->authmech, u2->authmech)
550                 && check_equal(u1->host, u2->host)
551                 && check_equal(u1->path, u2->path)
552                 && check_equal(u1->query, u2->query)
553                 && u1->port == u2->port;
554 }