add/fix lots of gtk-doc comments
[platform/upstream/libsoup.git] / libsoup / soup-uri.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* soup-uri.c : utility functions to parse URLs */
3
4 /*
5  * Copyright 1999-2003 Ximian, Inc.
6  */
7
8 #include <ctype.h>
9 #include <string.h>
10 #include <stdlib.h>
11
12 #include "soup-uri.h"
13
14 static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars);
15
16 static inline SoupProtocol
17 soup_uri_get_protocol (const char *proto, int len)
18 {
19         char proto_buf[128];
20
21         g_return_val_if_fail (len < sizeof (proto_buf), 0);
22
23         memcpy (proto_buf, proto, len);
24         proto_buf[len] = '\0';
25         return g_quark_from_string (proto_buf);
26 }
27
28 static inline const char *
29 soup_protocol_name (SoupProtocol proto)
30 {
31         return g_quark_to_string (proto);
32 }
33
34 static inline guint
35 soup_protocol_default_port (SoupProtocol proto)
36 {
37         if (proto == SOUP_PROTOCOL_HTTP)
38                 return 80;
39         else if (proto == SOUP_PROTOCOL_HTTPS)
40                 return 443;
41         else
42                 return 0;
43 }
44
45 /**
46  * soup_uri_new_with_base:
47  * @base: a base URI
48  * @uri_string: the URI
49  *
50  * Parses @uri_string relative to @base.
51  *
52  * Return value: a parsed #SoupUri.
53  **/
54 SoupUri *
55 soup_uri_new_with_base (const SoupUri *base, const char *uri_string)
56 {
57         SoupUri *uri;
58         const char *end, *hash, *colon, *at, *slash, *question;
59         const char *p;
60
61         uri = g_new0 (SoupUri, 1);
62
63         /* See RFC2396 for details. IF YOU CHANGE ANYTHING IN THIS
64          * FUNCTION, RUN tests/uri-parsing AFTERWARDS.
65          */
66
67         /* Find fragment. */
68         end = hash = strchr (uri_string, '#');
69         if (hash && hash[1]) {
70                 uri->fragment = g_strdup (hash + 1);
71                 soup_uri_decode (uri->fragment);
72         } else
73                 end = uri_string + strlen (uri_string);
74
75         /* Find protocol: initial [a-z+.-]* substring until ":" */
76         p = uri_string;
77         while (p < end && (isalnum ((unsigned char)*p) ||
78                            *p == '.' || *p == '+' || *p == '-'))
79                 p++;
80
81         if (p > uri_string && *p == ':') {
82                 uri->protocol = soup_uri_get_protocol (uri_string, p - uri_string);
83                 if (!uri->protocol) {
84                         soup_uri_free (uri);
85                         return NULL;
86                 }
87                 uri_string = p + 1;
88         }
89
90         if (!*uri_string && !base)
91                 return uri;
92
93         /* Check for authority */
94         if (strncmp (uri_string, "//", 2) == 0) {
95                 uri_string += 2;
96
97                 slash = uri_string + strcspn (uri_string, "/#");
98                 at = strchr (uri_string, '@');
99                 if (at && at < slash) {
100                         colon = strchr (uri_string, ':');
101                         if (colon && colon < at) {
102                                 uri->passwd = g_strndup (colon + 1,
103                                                          at - colon - 1);
104                                 soup_uri_decode (uri->passwd);
105                         } else {
106                                 uri->passwd = NULL;
107                                 colon = at;
108                         }
109
110                         uri->user = g_strndup (uri_string, colon - uri_string);
111                         soup_uri_decode (uri->user);
112                         uri_string = at + 1;
113                 } else
114                         uri->user = uri->passwd = NULL;
115
116                 /* Find host and port. */
117                 colon = strchr (uri_string, ':');
118                 if (colon && colon < slash) {
119                         uri->host = g_strndup (uri_string, colon - uri_string);
120                         uri->port = strtoul (colon + 1, NULL, 10);
121                 } else {
122                         uri->host = g_strndup (uri_string, slash - uri_string);
123                         soup_uri_decode (uri->host);
124                 }
125
126                 uri_string = slash;
127         }
128
129         /* Find query */
130         question = memchr (uri_string, '?', end - uri_string);
131         if (question) {
132                 if (question[1]) {
133                         uri->query = g_strndup (question + 1,
134                                                 end - (question + 1));
135                         soup_uri_decode (uri->query);
136                 }
137                 end = question;
138         }
139
140         if (end != uri_string) {
141                 uri->path = g_strndup (uri_string, end - uri_string);
142                 soup_uri_decode (uri->path);
143         }
144
145         /* Apply base URI. Again, this is spelled out in RFC 2396. */
146         if (base && !uri->protocol && uri->host)
147                 uri->protocol = base->protocol;
148         else if (base && !uri->protocol) {
149                 uri->protocol = base->protocol;
150                 uri->user = g_strdup (base->user);
151                 uri->passwd = g_strdup (base->passwd);
152                 uri->host = g_strdup (base->host);
153                 uri->port = base->port;
154
155                 if (!uri->path) {
156                         if (uri->query)
157                                 uri->path = g_strdup ("");
158                         else {
159                                 uri->path = g_strdup (base->path);
160                                 uri->query = g_strdup (base->query);
161                         }
162                 }
163
164                 if (*uri->path != '/') {
165                         char *newpath, *last, *p, *q;
166
167                         last = strrchr (base->path, '/');
168                         if (last) {
169                                 newpath = g_strdup_printf ("%.*s/%s",
170                                                            last - base->path,
171                                                            base->path,
172                                                            uri->path);
173                         } else
174                                 newpath = g_strdup_printf ("/%s", uri->path);
175
176                         /* Remove "./" where "." is a complete segment. */
177                         for (p = newpath + 1; *p; ) {
178                                 if (*(p - 1) == '/' &&
179                                     *p == '.' && *(p + 1) == '/')
180                                         memmove (p, p + 2, strlen (p + 2) + 1);
181                                 else
182                                         p++;
183                         }
184                         /* Remove "." at end. */
185                         if (p > newpath + 2 &&
186                             *(p - 1) == '.' && *(p - 2) == '/')
187                                 *(p - 1) = '\0';
188                         /* Remove "<segment>/../" where <segment> != ".." */
189                         for (p = newpath + 1; *p; ) {
190                                 if (!strncmp (p, "../", 3)) {
191                                         p += 3;
192                                         continue;
193                                 }
194                                 q = strchr (p + 1, '/');
195                                 if (!q)
196                                         break;
197                                 if (strncmp (q, "/../", 4) != 0) {
198                                         p = q + 1;
199                                         continue;
200                                 }
201                                 memmove (p, q + 4, strlen (q + 4) + 1);
202                                 p = newpath + 1;
203                         }
204                         /* Remove "<segment>/.." at end where <segment> != ".." */
205                         q = strrchr (newpath, '/');
206                         if (q && !strcmp (q, "/..")) {
207                                 p = q - 1;
208                                 while (p > newpath && *p != '/')
209                                         p--;
210                                 if (strncmp (p, "/../", 4) != 0)
211                                         *(p + 1) = 0;
212                         }
213
214                         g_free (uri->path);
215                         uri->path = newpath;
216                 }
217         }
218
219         /* Sanity check */
220         if ((uri->protocol == SOUP_PROTOCOL_HTTP ||
221              uri->protocol == SOUP_PROTOCOL_HTTPS) && !uri->host) {
222                 soup_uri_free (uri);
223                 return NULL;
224         }
225
226         if (!uri->port)
227                 uri->port = soup_protocol_default_port (uri->protocol);
228         if (!uri->path)
229                 uri->path = g_strdup ("");
230
231         return uri;
232 }
233
234 /**
235  * soup_uri_new:
236  * @uri_string: a URI
237  *
238  * Parses an absolute URI.
239  *
240  * Return value: a #SoupUri, or %NULL.
241  **/
242 SoupUri *
243 soup_uri_new (const char *uri_string)
244 {
245         SoupUri *uri;
246
247         uri = soup_uri_new_with_base (NULL, uri_string);
248         if (!uri)
249                 return NULL;
250         if (!uri->protocol) {
251                 soup_uri_free (uri);
252                 return NULL;
253         }
254
255         return uri;
256 }
257
258
259 static inline void
260 append_uri (GString *str, const char *in, const char *extra_enc_chars,
261             gboolean pre_encoded)
262 {
263         if (pre_encoded)
264                 g_string_append (str, in);
265         else
266                 append_uri_encoded (str, in, extra_enc_chars);
267 }
268
269 /**
270  * soup_uri_to_string:
271  * @uri: a #SoupUri
272  * @just_path: if %TRUE, output just the path and query portions
273  *
274  * Returns a string representing @uri.
275  *
276  * Return value: a string representing @uri, which the caller must free.
277  **/
278 char *
279 soup_uri_to_string (const SoupUri *uri, gboolean just_path)
280 {
281         GString *str;
282         char *return_result;
283         gboolean pre_encoded = uri->broken_encoding;
284
285         /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
286          * tests/uri-parsing AFTERWARD.
287          */
288
289         str = g_string_sized_new (20);
290
291         if (uri->protocol && !just_path)
292                 g_string_sprintfa (str, "%s:", soup_protocol_name (uri->protocol));
293         if (uri->host && !just_path) {
294                 g_string_append (str, "//");
295                 if (uri->user) {
296                         append_uri (str, uri->user, ":;@/", pre_encoded);
297                         g_string_append_c (str, '@');
298                 }
299                 append_uri (str, uri->host, ":/", pre_encoded);
300                 if (uri->port && uri->port != soup_protocol_default_port (uri->protocol))
301                         g_string_append_printf (str, ":%d", uri->port);
302                 if (!uri->path && (uri->query || uri->fragment))
303                         g_string_append_c (str, '/');
304         }
305
306         if (uri->path && *uri->path)
307                 append_uri (str, uri->path, "?", pre_encoded);
308         else if (just_path)
309                 g_string_append_c (str, '/');
310
311         if (uri->query) {
312                 g_string_append_c (str, '?');
313                 append_uri (str, uri->query, NULL, pre_encoded);
314         }
315         if (uri->fragment && !just_path) {
316                 g_string_append_c (str, '#');
317                 append_uri (str, uri->fragment, NULL, pre_encoded);
318         }
319
320         return_result = str->str;
321         g_string_free (str, FALSE);
322
323         return return_result;
324 }
325
326 /**
327  * soup_uri_copy:
328  * @uri: a #SoupUri
329  *
330  * Copies @uri
331  *
332  * Return value: a copy of @uri, which must be freed with soup_uri_free()
333  **/
334 SoupUri *
335 soup_uri_copy (const SoupUri *uri)
336 {
337         SoupUri *dup;
338
339         g_return_val_if_fail (uri != NULL, NULL);
340
341         dup = g_new0 (SoupUri, 1);
342         dup->protocol = uri->protocol;
343         dup->user     = g_strdup (uri->user);
344         dup->passwd   = g_strdup (uri->passwd);
345         dup->host     = g_strdup (uri->host);
346         dup->port     = uri->port;
347         dup->path     = g_strdup (uri->path);
348         dup->query    = g_strdup (uri->query);
349         dup->fragment = g_strdup (uri->fragment);
350
351         dup->broken_encoding = uri->broken_encoding;
352
353         return dup;
354 }
355
356 /**
357  * soup_uri_copy_root:
358  * @uri: a #SoupUri
359  *
360  * Copies the protocol, host, and port of @uri into a new #SoupUri
361  * (all other fields in the new URI will be empty.)
362  *
363  * Return value: a partial copy of @uri, which must be freed with
364  * soup_uri_free()
365  **/
366 SoupUri *
367 soup_uri_copy_root (const SoupUri *uri)
368 {
369         SoupUri *dup;
370
371         g_return_val_if_fail (uri != NULL, NULL);
372
373         dup = g_new0 (SoupUri, 1);
374         dup->protocol = uri->protocol;
375         dup->host     = g_strdup (uri->host);
376         dup->port     = uri->port;
377
378         return dup;
379 }
380
381 static inline gboolean
382 parts_equal (const char *one, const char *two)
383 {
384         if (!one && !two)
385                 return TRUE;
386         if (!one || !two)
387                 return FALSE;
388         return !strcmp (one, two);
389 }
390
391 /**
392  * soup_uri_equal:
393  * @u1: a #SoupUri
394  * @u2: another #SoupUri
395  *
396  * Tests whether or not @u1 and @u2 are equal in all parts
397  *
398  * Return value: %TRUE or %FALSE
399  **/
400 gboolean 
401 soup_uri_equal (const SoupUri *u1, const SoupUri *u2)
402 {
403         if (u1->protocol != u2->protocol              ||
404             u1->port     != u2->port                  ||
405             !parts_equal (u1->user, u2->user)         ||
406             !parts_equal (u1->passwd, u2->passwd)     ||
407             !parts_equal (u1->host, u2->host)         ||
408             !parts_equal (u1->path, u2->path)         ||
409             !parts_equal (u1->query, u2->query)       ||
410             !parts_equal (u1->fragment, u2->fragment))
411                 return FALSE;
412
413         return TRUE;
414 }
415
416 /**
417  * soup_uri_free:
418  * @uri: a #SoupUri
419  *
420  * Frees @uri.
421  **/
422 void
423 soup_uri_free (SoupUri *uri)
424 {
425         g_return_if_fail (uri != NULL);
426
427         g_free (uri->user);
428         g_free (uri->passwd);
429         g_free (uri->host);
430         g_free (uri->path);
431         g_free (uri->query);
432         g_free (uri->fragment);
433
434         g_free (uri);
435 }
436
437 /* From RFC 2396 2.4.3, the characters that should always be encoded */
438 static const char uri_encoded_char[] = {
439         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x00 - 0x0f */
440         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x10 - 0x1f */
441         1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  ' ' - '/'  */
442         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,  /*  '0' - '?'  */
443         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '@' - 'O'  */
444         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,  /*  'P' - '_'  */
445         1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '`' - 'o'  */
446         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,  /*  'p' - 0x7f */
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         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
451         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
452         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
453         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
454         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
455 };
456
457 static void
458 append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
459 {
460         const unsigned char *s = (const unsigned char *)in;
461
462         while (*s) {
463                 if (uri_encoded_char[*s] ||
464                     (extra_enc_chars && strchr (extra_enc_chars, *s)))
465                         g_string_append_printf (str, "%%%02x", (int)*s++);
466                 else
467                         g_string_append_c (str, *s++);
468         }
469 }
470
471 /**
472  * soup_uri_encode:
473  * @part: a URI part
474  * @escape_extra: additional characters beyond " \"%#<>{}|\^[]`"
475  * to escape (or %NULL)
476  *
477  * This %-encodes the given URI part and returns the escaped version
478  * in allocated memory, which the caller must free when it is done.
479  *
480  * Return value: the encoded URI part
481  **/
482 char *
483 soup_uri_encode (const char *part, const char *escape_extra)
484 {
485         GString *str;
486         char *encoded;
487
488         str = g_string_new (NULL);
489         append_uri_encoded (str, part, escape_extra);
490         encoded = str->str;
491         g_string_free (str, FALSE);
492
493         return encoded;
494 }
495
496 /**
497  * soup_uri_decode:
498  * @part: a URI part
499  *
500  * %-decodes the passed-in URI *in place*. The decoded version is
501  * never longer than the encoded version, so there does not need to
502  * be any additional space at the end of the string.
503  */
504 void
505 soup_uri_decode (char *part)
506 {
507         unsigned char *s, *d;
508
509 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
510
511         s = d = (unsigned char *)part;
512         do {
513                 if (*s == '%' && s[1] && s[2]) {
514                         *d++ = (XDIGIT (s[1]) << 4) + XDIGIT (s[2]);
515                         s += 2;
516                 } else
517                         *d++ = *s;
518         } while (*s++);
519 }
520
521 /**
522  * soup_uri_uses_default_port:
523  * @uri: a #SoupUri
524  *
525  * Tests if @uri uses the default port for its protocol. (Eg, 80 for
526  * http.)
527  *
528  * Return value: %TRUE or %FALSE
529  **/
530 gboolean
531 soup_uri_uses_default_port (const SoupUri *uri)
532 {
533         return uri->port == soup_protocol_default_port (uri->protocol);
534 }