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