source: trunk/third/libsoup/libsoup/soup-uri.c @ 21108

Revision 21108, 12.3 KB checked in by ghudson, 20 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r21107, which included commits to RCS files with non-trunk default branches.
Line 
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
14static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars);
15
16static inline SoupProtocol
17soup_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
28static inline const char *
29soup_protocol_name (SoupProtocol proto)
30{
31        return g_quark_to_string (proto);
32}
33
34static inline guint
35soup_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 **/
54SoupUri *
55soup_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 **/
242SoupUri *
243soup_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
259static inline void
260append_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 **/
278char *
279soup_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 **/
334SoupUri *
335soup_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 **/
366SoupUri *
367soup_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
381static inline gboolean
382parts_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 **/
400gboolean
401soup_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 **/
422void
423soup_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 */
438static 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
457static void
458append_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 **/
482char *
483soup_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 */
504void
505soup_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 **/
530gboolean
531soup_uri_uses_default_port (const SoupUri *uri)
532{
533        return uri->port == soup_protocol_default_port (uri->protocol);
534}
Note: See TracBrowser for help on using the repository browser.