Fix FSF address (Tobias Mueller, #470445)
[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  *  Jeffrey Stedfast <fejj@ximian.com>
8  *
9  * Copyright 1999-2001 Ximian, Inc. (www.ximian.com)
10  *
11  * This program is free software; you can redistribute it and/or 
12  * modify it under the terms of version 2 of the GNU Lesser General Public 
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
23  * USA
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <ctype.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <glib.h>
36 #include <glib/gi18n-lib.h>
37
38 #include "camel-exception.h"
39 #include "camel-mime-utils.h"
40 #include "camel-object.h"
41 #include "camel-string-utils.h"
42 #include "camel-url.h"
43
44 static void copy_param (GQuark key_id, gpointer data, gpointer user_data);
45 static void output_param (GQuark key_id, gpointer data, gpointer user_data);
46
47 static void append_url_encoded (GString *str, const char *in, const char *extra_enc_chars);
48
49 /**
50  * camel_url_new_with_base:
51  * @base: a base URL
52  * @url_string: the URL
53  *
54  * Parses @url_string relative to @base.
55  *
56  * Returns a parsed #CamelURL
57  **/
58 CamelURL *
59 camel_url_new_with_base (CamelURL *base, const char *url_string)
60 {
61         CamelURL *url;
62         const char *start;
63         const char *end, *hash, *colon, *semi, *at, *slash, *question;
64         const char *p;
65
66         g_return_val_if_fail (url_string != NULL, NULL);
67
68         url = g_new0 (CamelURL, 1);
69         start = url_string;
70
71         /* See RFC1808 for details. IF YOU CHANGE ANYTHING IN THIS
72          * FUNCTION, RUN tests/misc/url AFTERWARDS.
73          */
74
75         /* Find fragment.  RFC 1808 2.4.1 */
76         end = hash = strchr (url_string, '#');
77         if (hash) {
78                 if (hash[1]) {
79                         url->fragment = g_strdup (hash + 1);
80                         camel_url_decode (url->fragment);
81                 }
82         } else
83                 end = url_string + strlen (url_string);
84
85         /* Find protocol: initial [a-z+.-]* substring until ":" */
86         p = url_string;
87         while (p < end && (isalnum ((unsigned char)*p) ||
88                            *p == '.' || *p == '+' || *p == '-'))
89                 p++;
90
91         if (p > url_string && *p == ':') {
92                 url->protocol = g_strndup (url_string, p - url_string);
93                 camel_strdown (url->protocol);
94                 url_string = p + 1;
95         }
96
97         if (!*url_string && !base)
98                 return url;
99
100 #ifdef G_OS_WIN32
101         if (url->protocol && !strcmp(url->protocol, "file")) {
102                 url->path = g_filename_from_uri(start, &url->host, NULL);
103                 return url;
104         }
105 #endif
106
107         /* Check for authority */
108         if (strncmp (url_string, "//", 2) == 0) {
109                 url_string += 2;
110
111                 slash = url_string + strcspn (url_string, "/#");
112                 at = strchr (url_string, '@');
113                 if (at && at < slash) {
114                         colon = strchr (url_string, ':');
115                         if (colon && colon < at) {
116                                 url->passwd = g_strndup (colon + 1,
117                                                          at - colon - 1);
118                                 camel_url_decode (url->passwd);
119                         } else {
120                                 url->passwd = NULL;
121                                 colon = at;
122                         }
123
124                         semi = strchr(url_string, ';');
125                         if (semi && semi < colon &&
126                             !g_ascii_strncasecmp (semi, ";auth=", 6)) {
127                                 url->authmech = g_strndup (semi + 6,
128                                                            colon - semi - 6);
129                                 camel_url_decode (url->authmech);
130                         } else {
131                                 url->authmech = NULL;
132                                 semi = colon;
133                         }
134
135                         url->user = g_strndup (url_string, semi - url_string);
136                         camel_url_decode (url->user);
137                         url_string = at + 1;
138                 } else
139                         url->user = url->passwd = url->authmech = NULL;
140
141                 /* Find host and port. */
142                 colon = strchr (url_string, ':');
143                 if (colon && colon < slash) {
144                         url->host = g_strndup (url_string, colon - url_string);
145                         url->port = strtoul (colon + 1, NULL, 10);
146                 } else {
147                         url->host = g_strndup (url_string, slash - url_string);
148                         camel_url_decode (url->host);
149                         url->port = 0;
150                 }
151
152                 url_string = slash;
153         }
154
155         /* Find query */
156         question = memchr (url_string, '?', end - url_string);
157         if (question) {
158                 if (question[1]) {
159                         url->query = g_strndup (question + 1,
160                                                 end - (question + 1));
161                         camel_url_decode (url->query);
162                 }
163                 end = question;
164         }
165
166         /* Find parameters */
167         semi = memchr (url_string, ';', end - url_string);
168         if (semi) {
169                 if (semi[1]) {
170                         const char *cur, *p, *eq;
171                         char *name, *value;
172
173                         for (cur = semi + 1; cur < end; cur = p + 1) {
174                                 p = memchr (cur, ';', end - cur);
175                                 if (!p)
176                                         p = end;
177                                 eq = memchr (cur, '=', p - cur);
178                                 if (eq) {
179                                         name = g_strndup (cur, eq - cur);
180                                         value = g_strndup (eq + 1, p - (eq + 1));
181                                         camel_url_decode (value);
182                                 } else {
183                                         name = g_strndup (cur, p - cur);
184                                         value = g_strdup ("");
185                                 }
186                                 camel_url_decode (name);
187                                 g_datalist_set_data_full (&url->params, name,
188                                                           value, g_free);
189                                 g_free (name);
190                         }
191                 }
192                 end = semi;
193         }
194
195         if (end != url_string) {
196                 url->path = g_strndup (url_string, end - url_string);
197                 camel_url_decode (url->path);
198         }
199
200         /* Apply base URL. Again, this is spelled out in RFC 1808. */
201         if (base && !url->protocol && url->host)
202                 url->protocol = g_strdup (base->protocol);
203         else if (base && !url->protocol) {
204                 if (!url->user && !url->authmech && !url->passwd &&
205                     !url->host && !url->port && !url->path &&
206                     !url->params && !url->query && !url->fragment)
207                         url->fragment = g_strdup (base->fragment);
208
209                 url->protocol = g_strdup (base->protocol);
210                 url->user = g_strdup (base->user);
211                 url->authmech = g_strdup (base->authmech);
212                 url->passwd = g_strdup (base->passwd);
213                 url->host = g_strdup (base->host);
214                 url->port = base->port;
215
216                 if (!url->path) {
217                         url->path = g_strdup (base->path);
218                         if (!url->params) {
219                                 g_datalist_foreach (&base->params, copy_param,
220                                                     &url->params);
221                                 if (!url->query)
222                                         url->query = g_strdup (base->query);
223                         }
224                 } else if (*url->path != '/') {
225                         char *newpath, *last, *p, *q;
226
227                         last = strrchr (base->path, '/');
228                         if (last) {
229                                 newpath = g_strdup_printf ("%.*s/%s",
230                                                            (int)(last - base->path),
231                                                            base->path,
232                                                            url->path);
233                         } else
234                                 newpath = g_strdup_printf ("/%s", url->path);
235
236                         /* Remove "./" where "." is a complete segment. */
237                         for (p = newpath + 1; *p; ) {
238                                 if (*(p - 1) == '/' &&
239                                     *p == '.' && *(p + 1) == '/')
240                                         memmove (p, p + 2, strlen (p + 2) + 1);
241                                 else
242                                         p++;
243                         }
244                         /* Remove "." at end. */
245                         if (p > newpath + 2 &&
246                             *(p - 1) == '.' && *(p - 2) == '/')
247                                 *(p - 1) = '\0';
248                         /* Remove "<segment>/../" where <segment> != ".." */
249                         for (p = newpath + 1; *p; ) {
250                                 if (!strncmp (p, "../", 3)) {
251                                         p += 3;
252                                         continue;
253                                 }
254                                 q = strchr (p + 1, '/');
255                                 if (!q)
256                                         break;
257                                 if (strncmp (q, "/../", 4) != 0) {
258                                         p = q + 1;
259                                         continue;
260                                 }
261                                 memmove (p, q + 4, strlen (q + 4) + 1);
262                                 p = newpath + 1;
263                         }
264                         /* Remove "<segment>/.." at end */
265                         q = strrchr (newpath, '/');
266                         if (q && !strcmp (q, "/..")) {
267                                 p = q - 1;
268                                 while (p > newpath && *p != '/')
269                                         p--;
270                                 if (strncmp (p, "/../", 4) != 0)
271                                         *(p + 1) = 0;
272                         }
273                         g_free (url->path);
274                         url->path = newpath;
275                 }
276         }
277
278         return url;
279 }
280
281 static void
282 copy_param (GQuark key_id, gpointer data, gpointer user_data)
283 {
284         GData **copy = user_data;
285
286         g_datalist_id_set_data_full (copy, key_id, g_strdup (data), g_free);
287 }
288
289 /**
290  * camel_url_new:
291  * @url_string: a URL string
292  * @ex: a #CamelException
293  *
294  * Parses an absolute URL.
295  *
296  * Returns a #CamelURL if it can be parsed, or %NULL otherwise
297  **/
298 CamelURL *
299 camel_url_new (const char *url_string, CamelException *ex)
300 {
301         CamelURL *url;
302
303         g_return_val_if_fail (url_string != NULL, NULL);
304
305         url = camel_url_new_with_base (NULL, url_string);
306
307         if (!url->protocol) {
308                 camel_url_free (url);
309                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
310                                       _("Could not parse URL `%s'"),
311                                       url_string);
312                 return NULL;
313         }
314         return url;
315 }
316
317 /**
318  * camel_url_to_string:
319  * @url: a #CamelURL
320  * @flags: additional translation options
321  *
322  * Flatten a #CamelURL into a string.
323  *
324  * Returns a string representing @url, which the caller must free
325  **/
326 char *
327 camel_url_to_string (CamelURL *url, guint32 flags)
328 {
329         GString *str;
330         char *return_result;
331
332         g_return_val_if_fail (url != NULL, NULL);
333
334         /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
335          * tests/misc/url AFTERWARD.
336          */
337         
338 #ifdef G_OS_WIN32
339         if (url->protocol && !strcmp(url->protocol, "file"))
340                 return g_filename_to_uri(url->path, url->host, NULL);
341 #endif
342         
343         str = g_string_sized_new (20);
344         
345         if (url->protocol)
346                 g_string_append_printf (str, "%s:", url->protocol);
347         
348         if (url->host) {
349                 g_string_append (str, "//");
350                 if (url->user) {
351                         append_url_encoded (str, url->user, ":;@/");
352                         if (url->authmech && *url->authmech) {
353                                 g_string_append (str, ";auth=");
354                                 append_url_encoded (str, url->authmech, ":@/");
355                         }
356                         if (url->passwd && !(flags & CAMEL_URL_HIDE_PASSWORD)) {
357                                 g_string_append_c (str, ':');
358                                 append_url_encoded (str, url->passwd, "@/");
359                         }
360                         g_string_append_c (str, '@');
361                 }
362                 append_url_encoded (str, url->host, ":/");
363                 if (url->port)
364                         g_string_append_printf (str, ":%d", url->port);
365                 if (!url->path && (url->params || url->query || url->fragment))
366                         g_string_append_c (str, '/');
367         }
368         
369         if (url->path)
370                 append_url_encoded (str, url->path, ";?");
371         if (url->params && !(flags & CAMEL_URL_HIDE_PARAMS))
372                 g_datalist_foreach (&url->params, output_param, str);
373         if (url->query) {
374                 g_string_append_c (str, '?');
375                 append_url_encoded (str, url->query, NULL);
376         }
377         if (url->fragment) {
378                 g_string_append_c (str, '#');
379                 append_url_encoded (str, url->fragment, NULL);
380         }
381         
382         return_result = str->str;
383         g_string_free (str, FALSE);
384         
385         return return_result;
386 }
387
388 static void
389 output_param (GQuark key_id, gpointer data, gpointer user_data)
390 {
391         GString *str = user_data;
392
393         g_string_append_c (str, ';');
394         append_url_encoded (str, g_quark_to_string (key_id), "?=");
395         if (*(char *)data) {
396                 g_string_append_c (str, '=');
397                 append_url_encoded (str, data, "?");
398         }
399 }
400
401 /**
402  * camel_url_free:
403  * @url: a #CamelURL
404  *
405  * Frees @url.
406  **/
407 void
408 camel_url_free (CamelURL *url)
409 {
410         if (url) {
411                 if (url->passwd)
412                         memset(url->passwd, 0, strlen(url->passwd));
413                 if (url->user)
414                         memset(url->user, 0, strlen(url->user));
415                 if (url->host)
416                         memset(url->host, 0, strlen(url->host));
417                 g_free (url->protocol);
418                 g_free (url->user);
419                 g_free (url->authmech);
420                 g_free (url->passwd);
421                 g_free (url->host);
422                 g_free (url->path);
423                 g_datalist_clear (&url->params);
424                 g_free (url->query);
425                 g_free (url->fragment);
426                 
427                 g_free (url);
428         }
429 }
430
431
432 #define DEFINE_CAMEL_URL_SET(part)                      \
433 void                                                    \
434 camel_url_set_##part (CamelURL *url, const char *part)  \
435 {                                                       \
436         g_return_if_fail (url != NULL);                 \
437                                                         \
438         g_free (url->part);                             \
439         url->part = g_strdup (part);                    \
440 }
441
442
443 /**
444  * camel_url_set_protocol:
445  * @url: a #CamelURL
446  * @protocol: protocol schema
447  *
448  * Set the protocol of a #CamelURL.
449  **/
450 DEFINE_CAMEL_URL_SET (protocol)
451
452
453 /**
454  * camel_url_set_user:
455  * @url: a #CamelURL
456  * @user: username
457  *
458  * Set the user of a #CamelURL.
459  **/
460 DEFINE_CAMEL_URL_SET (user)
461
462
463 /**
464  * camel_url_set_authmech:
465  * @url: a #CamelURL
466  * @authmech: authentication mechanism
467  *
468  * Set the authmech of a #CamelURL.
469  **/
470 DEFINE_CAMEL_URL_SET (authmech)
471
472
473 /**
474  * camel_url_set_passwd:
475  * @url: a #CamelURL
476  * @passwd: password
477  *
478  * Set the password of a #CamelURL.
479  **/
480 DEFINE_CAMEL_URL_SET (passwd)
481
482
483 /**
484  * camel_url_set_host:
485  * @url: a #CamelURL
486  * @host: hostname
487  *
488  * Set the hostname of a #CamelURL.
489  **/
490 DEFINE_CAMEL_URL_SET (host)
491
492
493 /**
494  * camel_url_set_path:
495  * @url: a #CamelURL
496  * @path: path
497  *
498  * Set the path component of a #CamelURL.
499  **/
500 DEFINE_CAMEL_URL_SET (path)
501
502
503 /**
504  * camel_url_set_query:
505  * @url: a #CamelURL
506  * @query: url query
507  *
508  * Set the query of a #CamelURL.
509  **/
510 DEFINE_CAMEL_URL_SET (query)
511
512
513 /**
514  * camel_url_set_fragment:
515  * @url: a #CamelURL
516  * @fragment: url fragment
517  *
518  * Set the fragment of a #CamelURL.
519  **/
520 DEFINE_CAMEL_URL_SET (fragment)
521
522
523 /**
524  * camel_url_set_port:
525  * @url: a #CamelURL
526  * @port: port
527  *
528  * Set the port on a #CamelURL.
529  **/
530 void
531 camel_url_set_port (CamelURL *url, int port)
532 {
533         g_return_if_fail (url != NULL);
534
535         url->port = port;
536 }
537
538
539 /**
540  * camel_url_set_param:
541  * @url: a #CamelURL
542  * @name: name of the param to set
543  * @value: value of the param to set
544  *
545  * Set a param on the #CamelURL.
546  **/
547 void
548 camel_url_set_param (CamelURL *url, const char *name, const char *value)
549 {
550         g_return_if_fail (url != NULL);
551
552         if (value)
553                 g_datalist_set_data_full (&url->params, name, g_strdup(value), g_free);
554         else
555                 g_datalist_remove_data(&url->params, name);
556 }
557
558
559 /**
560  * camel_url_get_param:
561  * @url: a #CamelURL
562  * @name: name of the param
563  *
564  * Get the value of the specified param on the URL.
565  *
566  * Returns the value of a param if found or %NULL otherwise
567  **/
568 const char *
569 camel_url_get_param (CamelURL *url, const char *name)
570 {
571         g_return_val_if_fail (url != NULL, NULL);
572
573         return g_datalist_get_data (&url->params, name);
574 }
575
576 /* From RFC 2396 2.4.3, the characters that should always be encoded */
577 static const char url_encoded_char[] = {
578         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x00 - 0x0f */
579         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x10 - 0x1f */
580         1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  ' ' - '/'  */
581         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,  /*  '0' - '?'  */
582         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '@' - 'O'  */
583         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,  /*  'P' - '_'  */
584         1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '`' - 'o'  */
585         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,  /*  'p' - 0x7f */
586         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
587         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
588         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
589         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
590         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
591         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
592         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
593         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
594 };
595
596 static void
597 append_url_encoded (GString *str, const char *in, const char *extra_enc_chars)
598 {
599         const unsigned char *s = (const unsigned char *)in;
600
601         while (*s) {
602                 if (url_encoded_char[*s] ||
603                     (extra_enc_chars && strchr (extra_enc_chars, *s)))
604                         g_string_append_printf (str, "%%%02x", (int)*s++);
605                 else
606                         g_string_append_c (str, *s++);
607         }
608 }
609
610 /**
611  * camel_url_encode:
612  * @part: a URL part
613  * @escape_extra: additional characters beyond " \"%#<>{}|\^[]`"
614  * to escape (or %NULL)
615  *
616  * This %-encodes the given URL part and returns the escaped version
617  * in allocated memory, which the caller must free when it is done.
618  *
619  * Returns the encoded string
620  **/
621 char *
622 camel_url_encode (const char *part, const char *escape_extra)
623 {
624         GString *str;
625         char *encoded;
626
627         g_return_val_if_fail (part != NULL, NULL);
628
629         str = g_string_new (NULL);
630         append_url_encoded (str, part, escape_extra);
631         encoded = str->str;
632         g_string_free (str, FALSE);
633
634         return encoded;
635 }
636
637 /**
638  * camel_url_decode:
639  * @part: a URL part
640  *
641  * %-decodes the passed-in URL *in place*. The decoded version is
642  * never longer than the encoded version, so there does not need to
643  * be any additional space at the end of the string.
644  */
645 void
646 camel_url_decode (char *part)
647 {
648         unsigned char *s, *d;
649
650         g_return_if_fail (part != NULL);
651
652 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
653
654         s = d = (unsigned char *)part;
655         do {
656                 if (*s == '%' && isxdigit(s[1]) && isxdigit(s[2])) {
657                         *d++ = (XDIGIT (s[1]) << 4) + XDIGIT (s[2]);
658                         s += 2;
659                 } else
660                         *d++ = *s;
661         } while (*s++);
662 }
663
664
665 guint
666 camel_url_hash (const void *v)
667 {
668         const CamelURL *u = v;
669         guint hash = 0;
670
671 #define ADD_HASH(s) if (s) hash ^= g_str_hash (s);
672
673         ADD_HASH (u->protocol);
674         ADD_HASH (u->user);
675         ADD_HASH (u->authmech);
676         ADD_HASH (u->host);
677         ADD_HASH (u->path);
678         ADD_HASH (u->query);
679         hash ^= u->port;
680         
681         return hash;
682 }
683
684 static int
685 check_equal (char *s1, char *s2)
686 {
687         if (s1 == NULL) {
688                 if (s2 == NULL)
689                         return TRUE;
690                 else
691                         return FALSE;
692         }
693         
694         if (s2 == NULL)
695                 return FALSE;
696
697         return strcmp (s1, s2) == 0;
698 }
699
700 int
701 camel_url_equal(const void *v, const void *v2)
702 {
703         const CamelURL *u1 = v, *u2 = v2;
704
705         return check_equal(u1->protocol, u2->protocol)
706                 && check_equal(u1->user, u2->user)
707                 && check_equal(u1->authmech, u2->authmech)
708                 && check_equal(u1->host, u2->host)
709                 && check_equal(u1->path, u2->path)
710                 && check_equal(u1->query, u2->query)
711                 && u1->port == u2->port;
712 }
713
714
715 /**
716  * camel_url_copy:
717  * @in: a #CamelURL to copy
718  *
719  * Copy a #CamelURL.
720  *
721  * Returns a duplicate copy of @in
722  **/
723 CamelURL *
724 camel_url_copy(const CamelURL *in)
725 {
726         CamelURL *out;
727
728         g_return_val_if_fail (in != NULL, NULL);
729
730         out = g_malloc(sizeof(*out));
731         out->protocol = g_strdup(in->protocol);
732         out->user = g_strdup(in->user);
733         out->authmech = g_strdup(in->authmech);
734         out->passwd = g_strdup(in->passwd);
735         out->host = g_strdup(in->host);
736         out->port = in->port;
737         out->path = g_strdup(in->path);
738         out->params = NULL;
739         if (in->params)
740                 g_datalist_foreach(&((CamelURL *)in)->params, copy_param, &out->params);
741         out->query = g_strdup(in->query);
742         out->fragment = g_strdup(in->fragment);
743
744         return out;
745 }
746
747 char *
748 camel_url_decode_path (const char *path)
749 {
750         gchar **comps;
751         char *new_path = NULL;
752         GString *str;
753         int i = 0;
754
755         if (!path)
756                 return g_strdup("");    /* ??? or NULL? */
757
758         str = g_string_new (NULL);
759
760         comps = g_strsplit (path, "/", -1);
761         while (comps[i]) {
762                 camel_url_decode (comps[i]);
763                 g_string_append (str, comps[i]);
764                 g_string_append_c (str, '/');
765                 i++;
766         }
767
768         /* Strip-off the trailing "/" */
769         new_path = g_strndup (str->str, str->len-1);
770
771         g_strfreev (comps);
772         g_string_free (str, TRUE);
773
774         return new_path;
775 }