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