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