Assert url_string != NULL.
[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 /* 
6  * Authors:
7  *  Bertrand Guiheneuf <bertrand@helixcode.com>
8  *  Dan Winship <danw@helixcode.com>
9  *  Tiago Antào <tiagoantao@bigfoot.com>
10  *  Jeffrey Stedfast <fejj@helixcode.com>
11  *
12  * Copyright 1999, 2000 Helix Code, Inc. (http://www.helixcode.com)
13  *
14  * This program is free software; you can redistribute it and/or 
15  * modify it under the terms of the GNU General Public License as 
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
27  * USA
28  */
29
30 #include <config.h>
31
32 #include <ctype.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include "camel-url.h"
37 #include "camel-mime-utils.h"
38 #include "camel-exception.h"
39 #include "camel-object.h"
40
41 /**
42  * camel_url_new: create a CamelURL object from a string
43  * @url_string: The string containing the URL to scan
44  * 
45  * This routine takes a string and parses it as a URL of the form:
46  *
47  *   protocol://user;AUTH=mech:password@host:port/path
48  *
49  * The protocol, followed by a ":" is required. If it is followed by * "//",
50  * there must be an "authority" containing at least a host,
51  * which ends at the end of the string or at the next "/". If there
52  * is an "@" in the authority, there must be a username before it,
53  * and the host comes after it. The authmech, password, and port are
54  * optional, and the punctuation that preceeds them is omitted if
55  * they are. Everything after the authority (or everything after the
56  * protocol if there was no authority) is the path. We consider the
57  * "/" between the authority and the path to be part of the path,
58  * although this is incorrect according to RFC 1738.
59  *
60  * The port, if present, must be numeric.
61  * 
62  * Return value: a CamelURL structure containing the URL items.
63  **/
64 CamelURL *
65 camel_url_new (const char *url_string, CamelException *ex)
66 {
67         CamelURL *url;
68         char *semi, *colon, *at, *slash;
69         char *p;
70
71         g_assert (url_string);
72
73         /* Find protocol: initial substring until ":" */
74         colon = strchr (url_string, ':');
75         if (!colon) {
76                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
77                                       _("URL string `%s' contains no protocol"),
78                                       url_string);
79                 return NULL;
80         }
81         
82         url = g_new0 (CamelURL, 1);
83         url->protocol = g_strndup (url_string, colon - url_string);
84         g_strdown (url->protocol);
85
86         /* Check protocol */
87         p = url->protocol;
88         while (*p) {
89                 if (!((*p >= 'a' && *p <= 'z') ||
90                       (*p == '-') || (*p == '+') || (*p == '.'))) {
91                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
92                                               _("URL string `%s' contains an invalid protocol"),
93                                               url_string);
94                         return NULL;
95                 }
96                 p++;
97         }
98
99         if (strncmp (colon, "://", 3) != 0) {
100                 if (*(colon + 1)) {
101                         url->path = g_strdup (colon + 1);
102                         camel_url_decode (url->path);
103                 }
104                 return url;
105         }
106
107         url_string = colon + 3;
108
109         /* If there is an @ sign in the authority, look for user,
110          * authmech, and password before it.
111          */
112         slash = strchr (url_string, '/');
113         at = strchr (url_string, '@');
114         if (at && (!slash || at < slash)) {
115                 colon = strchr (url_string, ':');
116                 if (colon && colon < at) {
117                         url->passwd = g_strndup (colon + 1, 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 || (!colon && semi < at)) &&
126                     !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         slash = strchr (url_string, '/');
143         colon = strchr (url_string, ':');
144         if (slash && colon > slash)
145                 colon = NULL;
146
147         if (colon) {
148                 url->host = g_strndup (url_string, colon - url_string);
149                 url->port = strtoul (colon + 1, &colon, 10);
150                 if (*colon && colon != slash) {
151                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
152                                               _("Port number in URL `%s' is non-"
153                                                 "numeric"), url_string);
154                         camel_url_free (url);
155                         return NULL;
156                 }
157         } else if (slash) {
158                 url->host = g_strndup (url_string, slash - url_string);
159                 camel_url_decode (url->host);
160                 url->port = 0;
161         } else {
162                 url->host = g_strdup (url_string);
163                 camel_url_decode (url->host);
164                 url->port = 0;
165         }
166
167         if (!slash)
168                 slash = "/";
169         url->path = g_strdup (slash);
170         camel_url_decode (url->path);
171
172         return url;
173 }
174
175 char *
176 camel_url_to_string (CamelURL *url, gboolean show_passwd)
177 {
178         char *return_result;
179         char *user = NULL, *authmech = NULL, *passwd = NULL;
180         char *host = NULL, *path = NULL;
181         char port[20];
182
183         if (url->user)
184                 user = camel_url_encode (url->user, TRUE, ":;@/");
185         
186         if (url->authmech && *url->authmech)
187                 authmech = camel_url_encode (url->authmech, TRUE, ":@/");
188         
189         if (show_passwd && url->passwd)
190                 passwd = camel_url_encode (url->passwd, TRUE, "@/");
191         
192         if (url->host)
193                 host = camel_url_encode (url->host, TRUE, ":/");
194         
195         if (url->port)
196                 g_snprintf (port, sizeof (port), "%d", url->port);
197         else
198                 *port = '\0';
199         
200         if (url->path)
201                 path = camel_url_encode (url->path, FALSE, NULL);
202
203         return_result = g_strdup_printf ("%s:%s%s%s%s%s%s%s%s%s%s%s%s",
204                                          url->protocol,
205                                          host ? "//" : "",
206                                          user ? user : "",
207                                          authmech ? ";auth=" : "",
208                                          authmech ? authmech : "",
209                                          passwd ? ":" : "",
210                                          passwd ? passwd : "",
211                                          user ? "@" : "",
212                                          host ? host : "",
213                                          *port ? ":" : "",
214                                          port,
215                                          path && host && *path != '/' ? "/" : "",
216                                          path ? path : "");
217         g_free (user);
218         g_free (authmech);
219         g_free (passwd);
220         g_free (host);
221         g_free (path);
222
223         return return_result;
224 }
225
226 void
227 camel_url_free (CamelURL *url)
228 {
229         g_assert (url);
230
231         g_free (url->protocol);
232         g_free (url->user);
233         g_free (url->authmech);
234         g_free (url->passwd);
235         g_free (url->host);
236         g_free (url->path);
237
238         g_free (url);
239 }
240
241 void camel_url_set_protocol(CamelURL *url, const char *p)
242 {
243         g_free(url->protocol);
244         url->protocol = g_strdup(p);
245 }
246
247 void camel_url_set_host(CamelURL *url, const char *h)
248 {
249         g_free(url->host);
250         url->host = g_strdup(h);
251 }
252
253 void camel_url_set_port(CamelURL *url, int port)
254 {
255         url->port = port;
256 }
257 void camel_url_set_path(CamelURL *url, const char *p)
258 {
259         g_free(url->path);
260         url->path = g_strdup(p);
261 }
262
263
264 /**
265  * camel_url_encode:
266  * @part: a URL part
267  * @escape_unsafe: whether or not to %-escape "unsafe" characters.
268  * ("%#<>{}|\^~[]`)
269  * @escape_extra: additional characters to escape.
270  *
271  * This %-encodes the given URL part and returns the escaped version
272  * in allocated memory, which the caller must free when it is done.
273  **/
274 char *
275 camel_url_encode (char *part, gboolean escape_unsafe, char *escape_extra)
276 {
277         char *work, *p;
278
279         /* worst case scenario = 3 times the initial */
280         p = work = g_malloc (3 * strlen (part) + 1);
281
282         while (*part) {
283                 if (((guchar) *part >= 127) || ((guchar) *part <= ' ') ||
284                     (escape_unsafe && strchr ("\"%#<>{}|\\^~[]`", *part)) ||
285                     (escape_extra && strchr (escape_extra, *part))) {
286                         sprintf (p, "%%%.02hX", (guchar) *part++);
287                         p += 3;
288                 } else
289                         *p++ = *part++;
290         }
291         *p = '\0';
292
293         return work;
294 }
295
296 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10)
297
298 /**
299  * camel_url_decode:
300  * @part: a URL part
301  *
302  * %-decodes the passed-in URL *in place*. The decoded version is
303  * never longer than the encoded version, so there does not need to
304  * be any additional space at the end of the string.
305  */
306 void
307 camel_url_decode (char *part)
308 {
309         guchar *s, *d;
310
311         s = d = (guchar *)part;
312         while (*s) {
313                 if (*s == '%') {
314                         if (isxdigit (s[1]) && isxdigit (s[2])) {
315                                 *d++ = HEXVAL (s[1]) * 16 + HEXVAL (s[2]);
316                                 s += 3;
317                         } else
318                                 *d++ = *s++;
319                 } else
320                         *d++ = *s++;
321         }
322         *d = '\0';
323 }
324
325 static void
326 add_hash (guint *hash, char *s)
327 {
328         if (s)
329                 *hash ^= g_str_hash(s);
330 }
331
332 guint camel_url_hash (const void *v)
333 {
334         const CamelURL *u = v;
335         guint hash = 0;
336
337         add_hash (&hash, u->protocol);
338         add_hash (&hash, u->user);
339         add_hash (&hash, u->authmech);
340         add_hash (&hash, u->host);
341         add_hash (&hash, u->path);
342         hash ^= u->port;
343         
344         return hash;
345 }
346
347 static int
348 check_equal (char *s1, char *s2)
349 {
350         if (s1 == NULL) {
351                 if (s2 == NULL)
352                         return TRUE;
353                 else
354                         return FALSE;
355         }
356         
357         if (s2 == NULL)
358                 return FALSE;
359
360         return strcmp (s1, s2) == 0;
361 }
362
363 int camel_url_equal(const void *v, const void *v2)
364 {
365         const CamelURL *u1 = v, *u2 = v2;
366         
367         return check_equal(u1->protocol, u2->protocol)
368                 && check_equal(u1->user, u2->user)
369                 && check_equal(u1->authmech, u2->authmech)
370                 && check_equal(u1->host, u2->host)
371                 && check_equal(u1->path, u2->path)
372                 && u1->port == u2->port;
373 }