reverted rev 8210 which just changed whitespace indent
[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                         /* the base->path is NULL if given Content-Base url was without last slash,
228                            i.e. like "http://example.com" (this expected only "http://example.com/") */
229                         last = base->path ? strrchr (base->path, '/') : NULL;
230                         if (last) {
231                                 newpath = g_strdup_printf ("%.*s/%s",
232                                                            (int)(last - base->path),
233                                                            base->path,
234                                                            url->path);
235                         } else
236                                 newpath = g_strdup_printf ("/%s", url->path);
237
238                         /* Remove "./" where "." is a complete segment. */
239                         for (p = newpath + 1; *p; ) {
240                                 if (*(p - 1) == '/' &&
241                                     *p == '.' && *(p + 1) == '/')
242                                         memmove (p, p + 2, strlen (p + 2) + 1);
243                                 else
244                                         p++;
245                         }
246                         /* Remove "." at end. */
247                         if (p > newpath + 2 &&
248                             *(p - 1) == '.' && *(p - 2) == '/')
249                                 *(p - 1) = '\0';
250                         /* Remove "<segment>/../" where <segment> != ".." */
251                         for (p = newpath + 1; *p; ) {
252                                 if (!strncmp (p, "../", 3)) {
253                                         p += 3;
254                                         continue;
255                                 }
256                                 q = strchr (p + 1, '/');
257                                 if (!q)
258                                         break;
259                                 if (strncmp (q, "/../", 4) != 0) {
260                                         p = q + 1;
261                                         continue;
262                                 }
263                                 memmove (p, q + 4, strlen (q + 4) + 1);
264                                 p = newpath + 1;
265                         }
266                         /* Remove "<segment>/.." at end */
267                         q = strrchr (newpath, '/');
268                         if (q && !strcmp (q, "/..")) {
269                                 p = q - 1;
270                                 while (p > newpath && *p != '/')
271                                         p--;
272                                 if (strncmp (p, "/../", 4) != 0)
273                                         *(p + 1) = 0;
274                         }
275                         g_free (url->path);
276                         url->path = newpath;
277                 }
278         }
279
280         return url;
281 }
282
283 static void
284 copy_param (GQuark key_id, gpointer data, gpointer user_data)
285 {
286         GData **copy = user_data;
287
288         g_datalist_id_set_data_full (copy, key_id, g_strdup (data), g_free);
289 }
290
291 /**
292  * camel_url_new:
293  * @url_string: a URL string
294  * @ex: a #CamelException
295  *
296  * Parses an absolute URL.
297  *
298  * Returns a #CamelURL if it can be parsed, or %NULL otherwise
299  **/
300 CamelURL *
301 camel_url_new (const char *url_string, CamelException *ex)
302 {
303         CamelURL *url;
304
305         if (!url_string || !*url_string)
306                 return NULL;
307
308         url = camel_url_new_with_base (NULL, url_string);
309
310         if (!url->protocol) {
311                 camel_url_free (url);
312                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
313                                       _("Could not parse URL `%s'"),
314                                       url_string);
315                 return NULL;
316         }
317         return url;
318 }
319
320 /**
321  * camel_url_to_string:
322  * @url: a #CamelURL
323  * @flags: additional translation options
324  *
325  * Flatten a #CamelURL into a string.
326  *
327  * Returns a string representing @url, which the caller must free
328  **/
329 char *
330 camel_url_to_string (CamelURL *url, guint32 flags)
331 {
332         GString *str;
333         char *return_result;
334
335         g_return_val_if_fail (url != NULL, NULL);
336
337         /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
338          * tests/misc/url AFTERWARD.
339          */
340
341 #ifdef G_OS_WIN32
342         if (url->protocol && !strcmp(url->protocol, "file"))
343                 return g_filename_to_uri(url->path, url->host, NULL);
344 #endif /* G_OS_WIN32 */
345
346         str = g_string_sized_new (20);
347
348         if (url->protocol)
349                 g_string_append_printf (str, "%s:", url->protocol);
350
351         if (url->host) {
352                 g_string_append (str, "//");
353                 if (url->user) {
354                         append_url_encoded (str, url->user, ":;@/");
355                         if (url->authmech && *url->authmech && !(flags & CAMEL_URL_HIDE_AUTH)) {
356                                 g_string_append (str, ";auth=");
357                                 append_url_encoded (str, url->authmech, ":@/");
358                         }
359                         if (url->passwd && !(flags & CAMEL_URL_HIDE_PASSWORD)) {
360                                 g_string_append_c (str, ':');
361                                 append_url_encoded (str, url->passwd, "@/");
362                         }
363                         g_string_append_c (str, '@');
364                 }
365                 append_url_encoded (str, url->host, ":/");
366                 if (url->port)
367                         g_string_append_printf (str, ":%d", url->port);
368                 if (!url->path && (url->params || url->query || url->fragment))
369                         g_string_append_c (str, '/');
370         }
371         
372         if (url->path)
373                 append_url_encoded (str, url->path, ";?");
374         if (url->params && !(flags & CAMEL_URL_HIDE_PARAMS))
375                 g_datalist_foreach (&url->params, output_param, str);
376         if (url->query) {
377                 g_string_append_c (str, '?');
378                 append_url_encoded (str, url->query, NULL);
379         }
380         if (url->fragment) {
381                 g_string_append_c (str, '#');
382                 append_url_encoded (str, url->fragment, NULL);
383         }
384         
385         return_result = str->str;
386         g_string_free (str, FALSE);
387         
388         return return_result;
389 }
390
391 static void
392 output_param (GQuark key_id, gpointer data, gpointer user_data)
393 {
394         GString *str = user_data;
395
396         g_string_append_c (str, ';');
397         append_url_encoded (str, g_quark_to_string (key_id), "?=");
398         if (*(char *)data) {
399                 g_string_append_c (str, '=');
400                 append_url_encoded (str, data, "?");
401         }
402 }
403
404 /**
405  * camel_url_free:
406  * @url: a #CamelURL
407  *
408  * Frees @url.
409  **/
410 void
411 camel_url_free (CamelURL *url)
412 {
413         if (url) {
414                 if (url->passwd)
415                         memset(url->passwd, 0, strlen(url->passwd));
416                 if (url->user)
417                         memset(url->user, 0, strlen(url->user));
418                 if (url->host)
419                         memset(url->host, 0, strlen(url->host));
420                 g_free (url->protocol);
421                 g_free (url->user);
422                 g_free (url->authmech);
423                 g_free (url->passwd);
424                 g_free (url->host);
425                 g_free (url->path);
426                 g_datalist_clear (&url->params);
427                 g_free (url->query);
428                 g_free (url->fragment);
429                 
430                 g_free (url);
431         }
432 }
433
434
435 #define DEFINE_CAMEL_URL_SET(part)                      \
436 void                                                    \
437 camel_url_set_##part (CamelURL *url, const char *part)  \
438 {                                                       \
439         g_return_if_fail (url != NULL);                 \
440                                                         \
441         g_free (url->part);                             \
442         url->part = g_strdup (part);                    \
443 }
444
445
446 /**
447  * camel_url_set_protocol:
448  * @url: a #CamelURL
449  * @protocol: protocol schema
450  *
451  * Set the protocol of a #CamelURL.
452  **/
453 DEFINE_CAMEL_URL_SET (protocol)
454
455
456 /**
457  * camel_url_set_user:
458  * @url: a #CamelURL
459  * @user: username
460  *
461  * Set the user of a #CamelURL.
462  **/
463 DEFINE_CAMEL_URL_SET (user)
464
465
466 /**
467  * camel_url_set_authmech:
468  * @url: a #CamelURL
469  * @authmech: authentication mechanism
470  *
471  * Set the authmech of a #CamelURL.
472  **/
473 DEFINE_CAMEL_URL_SET (authmech)
474
475
476 /**
477  * camel_url_set_passwd:
478  * @url: a #CamelURL
479  * @passwd: password
480  *
481  * Set the password of a #CamelURL.
482  **/
483 DEFINE_CAMEL_URL_SET (passwd)
484
485
486 /**
487  * camel_url_set_host:
488  * @url: a #CamelURL
489  * @host: hostname
490  *
491  * Set the hostname of a #CamelURL.
492  **/
493 DEFINE_CAMEL_URL_SET (host)
494
495
496 /**
497  * camel_url_set_path:
498  * @url: a #CamelURL
499  * @path: path
500  *
501  * Set the path component of a #CamelURL.
502  **/
503 DEFINE_CAMEL_URL_SET (path)
504
505
506 /**
507  * camel_url_set_query:
508  * @url: a #CamelURL
509  * @query: url query
510  *
511  * Set the query of a #CamelURL.
512  **/
513 DEFINE_CAMEL_URL_SET (query)
514
515
516 /**
517  * camel_url_set_fragment:
518  * @url: a #CamelURL
519  * @fragment: url fragment
520  *
521  * Set the fragment of a #CamelURL.
522  **/
523 DEFINE_CAMEL_URL_SET (fragment)
524
525
526 /**
527  * camel_url_set_port:
528  * @url: a #CamelURL
529  * @port: port
530  *
531  * Set the port on a #CamelURL.
532  **/
533 void
534 camel_url_set_port (CamelURL *url, int port)
535 {
536         g_return_if_fail (url != NULL);
537
538         url->port = port;
539 }
540
541
542 /**
543  * camel_url_set_param:
544  * @url: a #CamelURL
545  * @name: name of the param to set
546  * @value: value of the param to set
547  *
548  * Set a param on the #CamelURL.
549  **/
550 void
551 camel_url_set_param (CamelURL *url, const char *name, const char *value)
552 {
553         g_return_if_fail (url != NULL);
554
555         if (value)
556                 g_datalist_set_data_full (&url->params, name, g_strdup(value), g_free);
557         else
558                 g_datalist_remove_data(&url->params, name);
559 }
560
561
562 /**
563  * camel_url_get_param:
564  * @url: a #CamelURL
565  * @name: name of the param
566  *
567  * Get the value of the specified param on the URL.
568  *
569  * Returns the value of a param if found or %NULL otherwise
570  **/
571 const char *
572 camel_url_get_param (CamelURL *url, const char *name)
573 {
574         g_return_val_if_fail (url != NULL, NULL);
575
576         return g_datalist_get_data (&url->params, name);
577 }
578
579 /* From RFC 2396 2.4.3, the characters that should always be encoded */
580 static const char url_encoded_char[] = {
581         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x00 - 0x0f */
582         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x10 - 0x1f */
583         1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  ' ' - '/'  */
584         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,  /*  '0' - '?'  */
585         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '@' - 'O'  */
586         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,  /*  'P' - '_'  */
587         1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '`' - 'o'  */
588         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,  /*  'p' - 0x7f */
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         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
595         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
596         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
597 };
598
599 static void
600 append_url_encoded (GString *str, const char *in, const char *extra_enc_chars)
601 {
602         const unsigned char *s = (const unsigned char *)in;
603
604         while (*s) {
605                 if (url_encoded_char[*s] ||
606                     (extra_enc_chars && strchr (extra_enc_chars, *s)))
607                         g_string_append_printf (str, "%%%02x", (int)*s++);
608                 else
609                         g_string_append_c (str, *s++);
610         }
611 }
612
613 /**
614  * camel_url_encode:
615  * @part: a URL part
616  * @escape_extra: additional characters beyond " \"%#<>{}|\^[]`"
617  * to escape (or %NULL)
618  *
619  * This %-encodes the given URL part and returns the escaped version
620  * in allocated memory, which the caller must free when it is done.
621  *
622  * Returns the encoded string
623  **/
624 char *
625 camel_url_encode (const char *part, const char *escape_extra)
626 {
627         GString *str;
628         char *encoded;
629
630         g_return_val_if_fail (part != NULL, NULL);
631
632         str = g_string_new (NULL);
633         append_url_encoded (str, part, escape_extra);
634         encoded = str->str;
635         g_string_free (str, FALSE);
636
637         return encoded;
638 }
639
640 /**
641  * camel_url_decode:
642  * @part: a URL part
643  *
644  * %-decodes the passed-in URL *in place*. The decoded version is
645  * never longer than the encoded version, so there does not need to
646  * be any additional space at the end of the string.
647  */
648 void
649 camel_url_decode (char *part)
650 {
651         unsigned char *s, *d;
652
653         g_return_if_fail (part != NULL);
654
655 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
656
657         s = d = (unsigned char *)part;
658         do {
659                 if (*s == '%' && isxdigit(s[1]) && isxdigit(s[2])) {
660                         *d++ = (XDIGIT (s[1]) << 4) + XDIGIT (s[2]);
661                         s += 2;
662                 } else
663                         *d++ = *s;
664         } while (*s++);
665 }
666
667
668 guint
669 camel_url_hash (const void *v)
670 {
671         const CamelURL *u = v;
672         guint hash = 0;
673
674 #define ADD_HASH(s) if (s) hash ^= g_str_hash (s);
675
676         ADD_HASH (u->protocol);
677         ADD_HASH (u->user);
678         ADD_HASH (u->authmech);
679         ADD_HASH (u->host);
680         ADD_HASH (u->path);
681         ADD_HASH (u->query);
682         hash ^= u->port;
683         
684         return hash;
685 }
686
687 static int
688 check_equal (char *s1, char *s2)
689 {
690         if (s1 == NULL) {
691                 if (s2 == NULL)
692                         return TRUE;
693                 else
694                         return FALSE;
695         }
696         
697         if (s2 == NULL)
698                 return FALSE;
699
700         return strcmp (s1, s2) == 0;
701 }
702
703 int
704 camel_url_equal(const void *v, const void *v2)
705 {
706         const CamelURL *u1 = v, *u2 = v2;
707
708         return check_equal(u1->protocol, u2->protocol)
709                 && check_equal(u1->user, u2->user)
710                 && check_equal(u1->authmech, u2->authmech)
711                 && check_equal(u1->host, u2->host)
712                 && check_equal(u1->path, u2->path)
713                 && check_equal(u1->query, u2->query)
714                 && u1->port == u2->port;
715 }
716
717
718 /**
719  * camel_url_copy:
720  * @in: a #CamelURL to copy
721  *
722  * Copy a #CamelURL.
723  *
724  * Returns a duplicate copy of @in
725  **/
726 CamelURL *
727 camel_url_copy(const CamelURL *in)
728 {
729         CamelURL *out;
730
731         g_return_val_if_fail (in != NULL, NULL);
732
733         out = g_malloc0(sizeof(*out));
734         out->protocol = g_strdup(in->protocol);
735         out->user = g_strdup(in->user);
736         out->authmech = g_strdup(in->authmech);
737         out->passwd = g_strdup(in->passwd);
738         out->host = g_strdup(in->host);
739         out->port = in->port;
740         out->path = g_strdup(in->path);
741         out->params = NULL;
742         if (in->params)
743                 g_datalist_foreach(&((CamelURL *)in)->params, copy_param, &out->params);
744         out->query = g_strdup(in->query);
745         out->fragment = g_strdup(in->fragment);
746
747         return out;
748 }
749
750 char *
751 camel_url_decode_path (const char *path)
752 {
753         gchar **comps;
754         char *new_path = NULL;
755         GString *str;
756         int i = 0;
757
758         if (!path)
759                 return g_strdup("");    /* ??? or NULL? */
760
761         str = g_string_new (NULL);
762
763         comps = g_strsplit (path, "/", -1);
764         while (comps[i]) {
765                 camel_url_decode (comps[i]);
766                 g_string_append (str, comps[i]);
767                 g_string_append_c (str, '/');
768                 i++;
769         }
770
771         /* Strip-off the trailing "/" */
772         new_path = g_strndup (str->str, str->len-1);
773
774         g_strfreev (comps);
775         g_string_free (str, TRUE);
776
777         return new_path;
778 }