ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / libgnomevfs / gnome-vfs-uri.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* gnome-vfs-uri.c - URI handling for the GNOME Virtual File System.
3
4    Copyright (C) 1999 Free Software Foundation
5    Copyright (C) 2000, 2001 Eazel, Inc.
6
7    The Gnome Library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public License as
9    published by the Free Software Foundation; either version 2 of the
10    License, or (at your option) any later version.
11
12    The Gnome Library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16
17    You should have received a copy of the GNU Library General Public
18    License along with the Gnome Library; see the file COPYING.LIB.  If not,
19    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20    Boston, MA 02111-1307, USA.
21
22    Author: Ettore Perazzoli <ettore@gnu.org>
23
24 */
25
26 #include <config.h>
27 #include "gnome-vfs-uri.h"
28
29 #include "gnome-vfs-module.h"
30 #include "gnome-vfs-private-utils.h"
31 #include "gnome-vfs-transform.h"
32 #include "gnome-vfs-utils.h"
33 #include <glib/ghash.h>
34 #include <glib/gmessages.h>
35 #include <glib/gstrfuncs.h>
36 #include <glib/gstring.h>
37 #include <stdio.h>
38 #include <string.h>
39
40 /* 
41    split_toplevel_uri
42
43    Extract hostname and username from "path" with length "path_len"
44
45    examples:
46        sunsite.unc.edu/pub/linux
47        miguel@sphinx.nuclecu.unam.mx/c/nc
48        tsx-11.mit.edu:8192/
49        joe@foo.edu:11321/private
50        joe:password@foo.se
51
52    This function implements the following regexp: (whitespace for clarity)
53
54    ( ( ([^:@/]*) (:[^@/]*)? @ )? ([^/:]*) (:([0-9]*)?) )?  (/.*)?
55    ( ( ( user  ) (  pw  )?   )?   (host)    (port)?   )? (path <return value>)?
56
57   It returns NULL if neither <host> nor <path> could be matched.
58
59   port is checked to ensure that it does not exceed 0xffff.
60
61   return value is <path> or is "/" if the path portion is not present
62   All other arguments are set to 0 or NULL if their portions are not present
63
64   pedantic: this function ends up doing an unbounded lookahead, making it 
65   potentially O(n^2) instead of O(n).  This could be avoided.  Realistically, though,
66   its just the password field.
67
68   Differences between the old and the new implemention:
69
70                      Old                     New
71   localhost:8080     host="localhost:8080"   host="localhost" port=8080
72   /Users/mikef       host=""                 host=NULL
73
74 */ 
75
76
77 #define URI_MOVE_PAST_DELIMITER \
78         do {                                                    \
79                 cur_tok_start = (++cur);                        \
80                 if (path_end == cur) {                          \
81                         success = FALSE;                        \
82                         goto done;                              \
83                 }                                               \
84         } while (0);
85
86
87 #define uri_strlen_to(from, to)  ( (to) - (from) )
88 #define uri_strdup_to(from, to)  g_strndup ((from), uri_strlen_to((from), (to)))
89
90 typedef struct {
91         const char *chrs;
92         gboolean primed;
93         char bv[32];
94 } UriStrspnSet; 
95
96 UriStrspnSet uri_strspn_sets[] = {
97         {":@]" GNOME_VFS_URI_PATH_STR, FALSE, ""},
98         {"@" GNOME_VFS_URI_PATH_STR, FALSE, ""},
99         {":" GNOME_VFS_URI_PATH_STR, FALSE, ""},
100         {"]" GNOME_VFS_URI_PATH_STR, FALSE, ""}
101 };
102
103 #define URI_DELIMITER_ALL_SET (uri_strspn_sets + 0)
104 #define URI_DELIMITER_USER_SET (uri_strspn_sets + 1)
105 #define URI_DELIMITER_HOST_SET (uri_strspn_sets + 2)
106 #define URI_DELIMITER_IPV6_SET (uri_strspn_sets + 3)
107
108 #define BV_SET(bv, idx) (bv)[((guchar)(idx))>>3] |= (1 << ( (idx) & 7) )
109 #define BV_IS_SET(bv, idx) ((bv)[((guchar)(idx))>>3] & (1 << ( (idx) & 7)))
110
111 static const char *
112 uri_strspn_to(const char *str, UriStrspnSet *set, const char *path_end)
113 {
114         const char *cur;
115         const char *cur_chr;
116
117         if (!set->primed) {
118                 memset (set->bv, 0, sizeof(set->bv));
119         
120                 for (cur_chr = set->chrs; '\0' != *cur_chr; cur_chr++) {
121                         BV_SET (set->bv, *cur_chr);
122                 }
123
124                 BV_SET (set->bv, '\0');
125                 set->primed = TRUE;
126         }
127         
128         for (cur = str; cur < path_end && ! BV_IS_SET (set->bv, *cur); cur++) 
129                 ;
130
131         if (cur >= path_end || '\0' == *cur) {
132                 return NULL;
133         }
134
135         return cur;
136 }
137
138
139 static gchar *
140 split_toplevel_uri (const gchar *path, guint path_len,
141                     gchar **host_return, gchar **user_return,
142                     guint *port_return, gchar **password_return)
143 {
144         const char *path_end;
145         const char *cur_tok_start;
146         const char *cur;
147         const char *next_delimiter;
148         char *ret;
149         char *host;
150         gboolean success;
151
152         g_assert (host_return != NULL);
153         g_assert (user_return != NULL);
154         g_assert (port_return != NULL);
155         g_assert (password_return != NULL);
156
157         *host_return = NULL;
158         *user_return = NULL;
159         *port_return = 0;
160         *password_return = NULL;
161         ret = NULL;
162
163         success = FALSE;
164
165         if (path == NULL || path_len == 0) {
166                 return NULL;
167         }
168         
169
170         path_end = path + path_len;
171
172         cur_tok_start = path;
173         cur = uri_strspn_to (cur_tok_start, URI_DELIMITER_ALL_SET, path_end);
174
175         if (cur != NULL) {
176                 const char *tmp;
177
178                 if (*cur == ':') {
179                         /* This ':' belongs to username or IPv6 address.*/
180                         tmp = uri_strspn_to (cur_tok_start, URI_DELIMITER_USER_SET, path_end);
181
182                         if (tmp == NULL || *tmp != '@') {
183                                 tmp = uri_strspn_to (cur_tok_start, URI_DELIMITER_IPV6_SET, path_end);
184
185                                 if (tmp != NULL && *tmp == ']') {
186                                         cur = tmp;
187                                 }
188                         }
189                 }
190         }
191
192         if (cur != NULL) {
193
194                 /* Check for IPv6 address. */
195                 if (*cur == ']') {
196
197                         /*  No username:password in the URI  */
198                         /*  cur points to ']'  */
199
200                         cur = uri_strspn_to (cur, URI_DELIMITER_HOST_SET, path_end);
201                 }
202         }
203
204         if (cur != NULL) {
205                 next_delimiter = uri_strspn_to (cur, URI_DELIMITER_USER_SET, path_end);
206         } else {
207                 next_delimiter = NULL;
208         }
209         
210         if (cur != NULL
211                 && (*cur == '@'
212                     || (next_delimiter != NULL && *next_delimiter != '/' ))) {
213
214                 /* *cur == ':' or '@' and string contains a @ before a / */
215
216                 if (uri_strlen_to (cur_tok_start, cur) > 0) {
217                         *user_return = uri_strdup_to (cur_tok_start,cur);
218                 }
219
220                 if (*cur == ':') {
221                         URI_MOVE_PAST_DELIMITER;
222
223                         cur = uri_strspn_to(cur_tok_start, URI_DELIMITER_USER_SET, path_end);
224
225                         if (cur == NULL || *cur != '@') {
226                                 success = FALSE;
227                                 goto done;
228                         } else if (uri_strlen_to (cur_tok_start, cur) > 0) {
229                                 *password_return = uri_strdup_to (cur_tok_start,cur);
230                         }
231                 }
232
233                 if (*cur != '/') {
234                         URI_MOVE_PAST_DELIMITER;
235
236                         /* Move cur to point to ':' after ']' */
237                         cur = uri_strspn_to (cur_tok_start, URI_DELIMITER_IPV6_SET, path_end);
238
239                         if (cur != NULL && *cur == ']') {  /* For IPv6 address */
240                                 cur = uri_strspn_to (cur, URI_DELIMITER_HOST_SET, path_end);
241                         } else {
242                                 cur = uri_strspn_to (cur_tok_start, URI_DELIMITER_HOST_SET, path_end);
243                         }
244                 } else {
245                         cur_tok_start = cur;
246                 }
247         }
248
249         if (cur == NULL) {
250                 /* [^:/]+$ */
251                 if (uri_strlen_to (cur_tok_start, path_end) > 0) {
252                         *host_return = uri_strdup_to (cur_tok_start, path_end);
253                         if (*(path_end - 1) == GNOME_VFS_URI_PATH_CHR) {
254                                 ret = g_strdup (GNOME_VFS_URI_PATH_STR);
255                         } else {
256                                 ret = g_strdup ("");
257                         }
258                         success = TRUE;
259                 } else { /* No host, no path */
260                         success = FALSE;
261                 }
262
263                 goto done;
264
265         } else if (*cur == ':') {
266                 guint port;
267                 /* [^:/]*:.* */
268
269                 if (uri_strlen_to (cur_tok_start, cur) > 0) {
270                         *host_return = uri_strdup_to (cur_tok_start, cur);
271                 } else {
272                         success = FALSE;
273                         goto done;      /*No host but a port?*/
274                 }
275
276                 URI_MOVE_PAST_DELIMITER;
277
278                 port = 0;
279
280                 for ( ; cur < path_end && g_ascii_isdigit (*cur); cur++) {
281                         port *= 10;
282                         port += *cur - '0'; 
283                 }
284
285                 /* We let :(/.*)$ be treated gracefully */
286                 if (*cur != '\0' && *cur != GNOME_VFS_URI_PATH_CHR) {
287                         success = FALSE;
288                         goto done;      /* ...but this would be an error */
289                 } 
290
291                 if (port > 0xffff) {
292                         success = FALSE;
293                         goto done;
294                 }
295
296                 *port_return = port;
297
298                 cur_tok_start = cur;
299                 
300         } else /* GNOME_VFS_URI_PATH_CHR == *cur */ {
301                 /* ^[^:@/]+/.*$ */
302
303                 if (uri_strlen_to (cur_tok_start, cur) > 0) {
304                         *host_return = uri_strdup_to (cur_tok_start, cur);
305                 }
306
307                 cur_tok_start = cur;
308         }
309
310         if (*cur_tok_start != '\0' && uri_strlen_to (cur_tok_start, path_end) > 0) {
311                 ret = uri_strdup_to(cur, path_end);
312         } else if (*host_return != NULL) {
313                 ret = g_strdup (GNOME_VFS_URI_PATH_STR);
314         }
315
316         success = TRUE;
317
318 done:
319         if (*host_return != NULL) {
320
321                 /* Check for an IPv6 address in square brackets.*/
322                 if (strchr (*host_return, '[') && strchr (*host_return, ']') && strchr (*host_return, ':')) {
323
324                         /* Extract the IPv6 address from square braced string. */
325                         host = g_ascii_strdown ((*host_return) + 1, strlen (*host_return) - 2);
326                 } else {
327                         host = g_ascii_strdown (*host_return, -1);
328                 }
329
330                 g_free (*host_return);
331                 *host_return = host;
332
333         }
334
335         /* If we didn't complete our mission, discard all the partials */
336         if (!success) {
337                 g_free (*host_return);
338                 g_free (*user_return);
339                 g_free (*password_return);
340                 g_free (ret);
341
342                 *host_return = NULL;
343                 *user_return = NULL;
344                 *port_return = 0;
345                 *password_return = NULL;
346                 ret = NULL;
347         }
348
349         return ret;
350 }
351
352
353 static void
354 set_uri_element (GnomeVFSURI *uri,
355                  const gchar *text,
356                  guint len)
357 {
358         char *escaped_text;
359
360         if (text == NULL || len == 0) {
361                 uri->text = g_strdup("/");;
362                 return;
363         }
364
365         if (uri->parent == NULL && text[0] == '/' && text[1] == '/') {
366                 GnomeVFSToplevelURI *toplevel;
367
368                 toplevel = (GnomeVFSToplevelURI *) uri;
369                 uri->text = split_toplevel_uri (text + 2, len - 2,
370                                                 &toplevel->host_name,
371                                                 &toplevel->user_name,
372                                                 &toplevel->host_port,
373                                                 &toplevel->password);
374         } else {
375                 uri->text = g_strndup (text, len);
376         }
377
378         /* FIXME: this should be handled/supported by the specific method.
379          * This is a quick and dirty hack to minimize the amount of changes
380          * right before a milestone release. 
381          * 
382          * Do some method specific escaping. This for instance converts
383          * '?' to %3F in every method except "http" where it has a special 
384          * meaning.
385          */
386         if ( ! (strcmp (uri->method_string, "http") == 0 
387                 || strcmp (uri->method_string, "eazel-services") == 0
388                 || strcmp (uri->method_string, "ghelp") == 0
389                 || strcmp (uri->method_string, "gnome-help") == 0
390                 || strcmp (uri->method_string, "help") == 0
391                 )) {
392
393                 escaped_text = gnome_vfs_escape_set (uri->text, ";?&=+$,");
394                 g_free (uri->text);
395                 uri->text = escaped_text;
396         }
397         
398         gnome_vfs_remove_optional_escapes (uri->text);
399         _gnome_vfs_canonicalize_pathname (uri->text);
400 }
401
402 static const gchar *
403 get_method_string (const gchar *substring, gchar **method_string)
404 {
405         const gchar *p;
406         char *method;
407         
408         for (p = substring;
409              g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.';
410              p++)
411                 ;
412
413         if (*p == ':') {
414                 /* Found toplevel method specification.  */
415                 method = g_strndup (substring, p - substring);
416                 *method_string = g_ascii_strdown (method, -1);
417                 g_free (method);
418                 p++;
419         } else {
420                 *method_string = g_strdup ("file");
421                 p = substring;
422         }
423         return p;
424 }
425
426 static GnomeVFSURI *
427 parse_uri_substring (const gchar *substring, GnomeVFSURI *parent)
428 {
429         GnomeVFSMethod *method;
430         GnomeVFSURI *uri, *child_uri;
431         gchar *method_string;
432         const gchar *method_scanner;
433         const gchar *extension_scanner;
434
435         if (substring == NULL || *substring == '\0') {
436                 return NULL;
437         }
438         
439         method_scanner = get_method_string (substring, &method_string);
440
441         method = gnome_vfs_method_get (method_string);
442         if (!method) {
443                 g_free (method_string);
444                 return NULL;
445         }
446
447         uri = g_new0 (GnomeVFSURI, 1);
448         uri->method = method;
449         uri->method_string = method_string;
450         uri->ref_count = 1;
451         uri->parent = parent;
452
453         extension_scanner = strchr (method_scanner, GNOME_VFS_URI_MAGIC_CHR);
454
455         if (extension_scanner == NULL) {
456                 set_uri_element (uri, method_scanner, strlen (method_scanner));
457                 return uri;
458         }
459
460         /* handle '#' */
461         set_uri_element (uri, method_scanner, extension_scanner - method_scanner);
462
463         if (strchr (extension_scanner, ':') == NULL) {
464                 /* extension is a fragment identifier */
465                 uri->fragment_id = g_strdup (extension_scanner + 1);
466                 return uri;
467         }
468
469         /* extension is a uri chain */
470         child_uri = parse_uri_substring (extension_scanner + 1, uri);
471
472         if (child_uri != NULL) {
473                 return child_uri;
474         }
475
476         return uri;
477 }
478
479 /**
480  * gnome_vfs_uri_new:
481  * @text_uri: A string representing a URI.
482  * 
483  * Create a new URI from @text_uri. Unsupported and unsafe methods
484  * are not allowed and will result in %null% being returned. URL
485  * transforms are allowed.
486  * 
487  * Return value: The new URI.
488  **/
489 GnomeVFSURI *
490 gnome_vfs_uri_new (const gchar *text_uri)
491 {
492         return gnome_vfs_uri_new_private (text_uri, FALSE, FALSE, TRUE);
493 }
494
495 GnomeVFSURI *
496 gnome_vfs_uri_new_private (const gchar *text_uri,
497                            gboolean allow_unknown_methods,
498                            gboolean allow_unsafe_methods,
499                            gboolean allow_transforms)
500 {
501         GnomeVFSMethod *method;
502         GnomeVFSTransform *trans;
503         GnomeVFSToplevelURI *toplevel;
504         GnomeVFSURI *uri, *child_uri;
505         const gchar *method_scanner, *extension_scanner;
506         gchar *method_string;
507         gchar *new_uri_string = NULL;
508
509         g_return_val_if_fail (text_uri != NULL, NULL);
510
511         if (text_uri[0] == '\0') {
512                 return NULL;
513         }
514
515         method_scanner = get_method_string (text_uri, &method_string);
516         if (strcmp (method_string, "pipe") == 0 && !allow_unsafe_methods) {
517                 g_free (method_string);
518                 return NULL;
519         }
520
521         toplevel = g_new (GnomeVFSToplevelURI, 1);
522         toplevel->host_name = NULL;
523         toplevel->host_port = 0;
524         toplevel->user_name = NULL;
525         toplevel->password = NULL;
526
527         uri = (GnomeVFSURI *) toplevel;
528         uri->parent = NULL;
529
530         if (allow_transforms) {
531                 trans = gnome_vfs_transform_get (method_string);
532                 if (trans != NULL && trans->transform) {
533                         const GnomeVFSContext *context;
534                         
535                         context = gnome_vfs_context_peek_current ();
536                         (* trans->transform) (trans, 
537                                               method_scanner, 
538                                               &new_uri_string, 
539                                               (GnomeVFSContext *) context);
540                         if (new_uri_string != NULL) {
541                                 toplevel->urn = g_strdup (text_uri);
542                                 g_free (method_string);
543                                 method_scanner = get_method_string (new_uri_string, &method_string);
544                         }
545                 }
546         }
547         
548         method = gnome_vfs_method_get (method_string);
549         /* The toplevel URI element is special, as it also contains host/user
550            information.  */
551         uri->method = method;
552         uri->ref_count = 1;
553         uri->method_string = method_string;
554         uri->text = NULL;
555         uri->fragment_id = NULL;
556         if (method == NULL && !allow_unknown_methods) {
557                 g_free (new_uri_string);
558                 gnome_vfs_uri_unref (uri);
559                 return NULL;
560         }
561
562         extension_scanner = strchr (method_scanner, GNOME_VFS_URI_MAGIC_CHR);
563         if (extension_scanner == NULL) {
564                 set_uri_element (uri, method_scanner, strlen (method_scanner));
565                 g_free (new_uri_string);
566                 return uri;
567         }
568
569         /* handle '#' */
570         set_uri_element (uri, method_scanner, extension_scanner - method_scanner);
571
572         if (strchr (extension_scanner, ':') == NULL) {
573                 /* extension is a fragment identifier */
574                 uri->fragment_id = g_strdup (extension_scanner + 1);
575                 g_free (new_uri_string);
576                 return uri;
577         }
578
579         /* extension is a uri chain */
580         child_uri = parse_uri_substring (extension_scanner + 1, uri);
581
582         g_free (new_uri_string);
583
584         if (child_uri != NULL) {
585                 return child_uri;
586         }
587         
588         return uri;
589 }
590
591 /* Destroy an URI element, but not its parent.  */
592 static void
593 destroy_element (GnomeVFSURI *uri)
594 {
595         g_free (uri->text);
596         g_free (uri->fragment_id);
597         g_free (uri->method_string);
598
599         if (uri->parent == NULL) {
600                 GnomeVFSToplevelURI *toplevel;
601
602                 toplevel = (GnomeVFSToplevelURI *) uri;
603                 g_free (toplevel->host_name);
604                 g_free (toplevel->user_name);
605                 g_free (toplevel->password);
606         }
607
608         g_free (uri);
609 }
610
611 static gboolean
612 is_uri_relative (const char *uri)
613 {
614         const char *current;
615
616         /* RFC 2396 section 3.1 */
617         for (current = uri ; 
618                 *current
619                 &&      ((*current >= 'a' && *current <= 'z')
620                          || (*current >= 'A' && *current <= 'Z')
621                          || (*current >= '0' && *current <= '9')
622                          || ('-' == *current)
623                          || ('+' == *current)
624                          || ('.' == *current)) ;
625              current++);
626
627         return  !(':' == *current);
628 }
629
630
631 /*
632  * Remove "./" segments
633  * Compact "../" segments inside the URI
634  * Remove "." at the end of the URL 
635  * Leave any ".."'s at the beginning of the URI
636  
637 */
638 /*
639  * FIXME this is not the simplest or most time-efficent way
640  * to do this.  Probably a far more clear way of doing this processing
641  * is to split the path into segments, rather than doing the processing
642  * in place.
643  */
644 static void
645 remove_internal_relative_components (char *uri_current)
646 {
647         char *segment_prev, *segment_cur;
648         gsize len_prev, len_cur;
649
650         len_prev = len_cur = 0;
651         segment_prev = NULL;
652
653         segment_cur = uri_current;
654
655         while (*segment_cur) {
656                 len_cur = strcspn (segment_cur, "/");
657
658                 if (len_cur == 1 && segment_cur[0] == '.') {
659                         /* Remove "." 's */
660                         if (segment_cur[1] == '\0') {
661                                 segment_cur[0] = '\0';
662                                 break;
663                         } else {
664                                 memmove (segment_cur, segment_cur + 2, strlen (segment_cur + 2) + 1);
665                                 continue;
666                         }
667                 } else if (len_cur == 2 && segment_cur[0] == '.' && segment_cur[1] == '.' ) {
668                         /* Remove ".."'s (and the component to the left of it) that aren't at the
669                          * beginning or to the right of other ..'s
670                          */
671                         if (segment_prev) {
672                                 if (! (len_prev == 2
673                                        && segment_prev[0] == '.'
674                                        && segment_prev[1] == '.')) {
675                                         if (segment_cur[2] == '\0') {
676                                                 segment_prev[0] = '\0';
677                                                 break;
678                                         } else {
679                                                 memmove (segment_prev, segment_cur + 3, strlen (segment_cur + 3) + 1);
680
681                                                 segment_cur = segment_prev;
682                                                 len_cur = len_prev;
683
684                                                 /* now we find the previous segment_prev */
685                                                 if (segment_prev == uri_current) {
686                                                         segment_prev = NULL;
687                                                 } else if (segment_prev - uri_current >= 2) {
688                                                         segment_prev -= 2;
689                                                         for ( ; segment_prev > uri_current && segment_prev[0] != '/' 
690                                                               ; segment_prev-- );
691                                                         if (segment_prev[0] == '/') {
692                                                                 segment_prev++;
693                                                         }
694                                                 }
695                                                 continue;
696                                         }
697                                 }
698                         }
699                 }
700
701                 /*Forward to next segment */
702
703                 if (segment_cur [len_cur] == '\0') {
704                         break;
705                 }
706                  
707                 segment_prev = segment_cur;
708                 len_prev = len_cur;
709                 segment_cur += len_cur + 1;     
710         }
711         
712 }
713
714 /* If I had known this relative uri code would have ended up this long, I would
715  * have done it a different way
716  */
717 static char *
718 make_full_uri_from_relative (const char *base_uri, const char *uri)
719 {
720         char *result = NULL;
721
722         char *mutable_base_uri;
723         char *mutable_uri;
724         
725         char *uri_current;
726         gsize base_uri_length;
727         char *separator;
728         
729         /* We may need one extra character
730          * to append a "/" to uri's that have no "/"
731          * (such as help:)
732          */
733
734         mutable_base_uri = g_malloc(strlen(base_uri)+2);
735         strcpy (mutable_base_uri, base_uri);
736                 
737         uri_current = mutable_uri = g_strdup (uri);
738
739         /* Chew off Fragment and Query from the base_url */
740
741         separator = strrchr (mutable_base_uri, '#'); 
742
743         if (separator) {
744                 *separator = '\0';
745         }
746
747         separator = strrchr (mutable_base_uri, '?');
748
749         if (separator) {
750                 *separator = '\0';
751         }
752
753         if ('/' == uri_current[0] && '/' == uri_current [1]) {
754                 /* Relative URI's beginning with the authority
755                  * component inherit only the scheme from their parents
756                  */
757
758                 separator = strchr (mutable_base_uri, ':');
759
760                 if (separator) {
761                         separator[1] = '\0';
762                 }                         
763         } else if ('/' == uri_current[0]) {
764                 /* Relative URI's beginning with '/' absolute-path based
765                  * at the root of the base uri
766                  */
767
768                 separator = strchr (mutable_base_uri, ':');
769
770                 /* g_assert (separator), really */
771                 if (separator) {
772                         /* If we start with //, skip past the authority section */
773                         if ('/' == separator[1] && '/' == separator[2]) {
774                                 separator = strchr (separator + 3, '/');
775                                 if (separator) {
776                                         separator[0] = '\0';
777                                 }
778                         } else {
779                                 /* If there's no //, just assume the scheme is the root */
780                                 separator[1] = '\0';
781                         }
782                 }
783         } else if ('#' != uri_current[0]) {
784                 /* Handle the ".." convention for relative uri's */
785
786                 /* If there's a trailing '/' on base_url, treat base_url
787                  * as a directory path.
788                  * Otherwise, treat it as a file path, and chop off the filename
789                  */
790
791                 base_uri_length = strlen (mutable_base_uri);
792                 if ('/' == mutable_base_uri[base_uri_length-1]) {
793                         /* Trim off '/' for the operation below */
794                         mutable_base_uri[base_uri_length-1] = 0;
795                 } else {
796                         separator = strrchr (mutable_base_uri, '/');
797                         if (separator) {
798                                 /* Make sure we don't eat a domain part */
799                                 gchar *tmp = (gchar*)((int)separator - 1);
800                                 if ((separator != mutable_base_uri) && (*tmp != '/')) {
801                                         *separator = '\0';
802                                 }
803                         }
804                 }
805
806                 remove_internal_relative_components (uri_current);
807
808                 /* handle the "../"'s at the beginning of the relative URI */
809                 while (0 == strncmp ("../", uri_current, 3)) {
810                         uri_current += 3;
811                         separator = strrchr (mutable_base_uri, '/');
812                         if (separator) {
813                                 *separator = '\0';
814                         } else {
815                                 /* <shrug> */
816                                 break;
817                         }
818                 }
819
820                 /* handle a ".." at the end */
821                 if (uri_current[0] == '.' && uri_current[1] == '.' 
822                     && uri_current[2] == '\0') {
823
824                         uri_current += 2;
825                         separator = strrchr (mutable_base_uri, '/');
826                         if (separator) {
827                                 *separator = '\0';
828                         }
829                 }
830
831                 /* Re-append the '/' */
832                 mutable_base_uri [strlen(mutable_base_uri)+1] = '\0';
833                 mutable_base_uri [strlen(mutable_base_uri)] = '/';
834         }
835
836         result = g_strconcat (mutable_base_uri, uri_current, NULL);
837         g_free (mutable_base_uri); 
838         g_free (mutable_uri); 
839         
840         return result;
841 }
842
843 /**
844  * gnome_vfs_uri_resolve_relative
845  * @base: The base URI.
846  * @relative_reference: A string representing a possibly relative URI reference
847  * 
848  * Create a new URI from @relative_reference, relative to @base.
849  *
850  * Return value: The new URI.
851  **/
852 GnomeVFSURI *
853 gnome_vfs_uri_resolve_relative (const GnomeVFSURI *base,
854                                 const gchar *relative_reference)
855 {
856         char *text_base;
857         char *text_new;
858         GnomeVFSURI *uri;
859
860         g_assert (relative_reference != NULL);
861
862         if (base == NULL) {
863                 text_base = g_strdup ("");
864         } else {
865                 text_base = gnome_vfs_uri_to_string (base, 0);
866         }
867
868         if (is_uri_relative (relative_reference)) {
869                 text_new = make_full_uri_from_relative (text_base, 
870                                                         relative_reference);
871         } else {
872                 text_new = g_strdup (relative_reference);
873         }
874         
875         uri = gnome_vfs_uri_new (text_new);
876
877         g_free (text_base);
878         g_free (text_new);
879
880         return uri;
881 }
882
883 /**
884  * gnome_vfs_uri_ref:
885  * @uri: A GnomeVFSURI.
886  * 
887  * Increment @uri's reference count.
888  * 
889  * Return value: @uri.
890  **/
891 GnomeVFSURI *
892 gnome_vfs_uri_ref (GnomeVFSURI *uri)
893 {
894         GnomeVFSURI *p;
895
896         g_return_val_if_fail (uri != NULL, NULL);
897
898         for (p = uri; p != NULL; p = p->parent)
899                 p->ref_count++;
900
901         return uri;
902 }
903
904 /**
905  * gnome_vfs_uri_unref:
906  * @uri: A GnomeVFSURI.
907  * 
908  * Decrement @uri's reference count.  If the reference count reaches zero,
909  * @uri is destroyed.
910  **/
911 void
912 gnome_vfs_uri_unref (GnomeVFSURI *uri)
913 {
914         GnomeVFSURI *p, *parent;
915
916         g_return_if_fail (uri != NULL);
917         g_return_if_fail (uri->ref_count > 0);
918
919         for (p = uri; p != NULL; p = parent) {
920                 parent = p->parent;
921                 g_assert (p->ref_count > 0);
922                 p->ref_count--;
923                 if (p->ref_count == 0)
924                         destroy_element (p);
925         }
926 }
927
928 /**
929  * gnome_vfs_uri_dup:
930  * @uri: A GnomeVFSURI.
931  * 
932  * Duplicate @uri.
933  * 
934  * Return value: A pointer to a new URI that is exactly the same as @uri.
935  **/
936 GnomeVFSURI *
937 gnome_vfs_uri_dup (const GnomeVFSURI *uri)
938 {
939         const GnomeVFSURI *p;
940         GnomeVFSURI *new_uri, *child;
941
942         if (uri == NULL) {
943                 return NULL;
944         }
945
946         new_uri = NULL;
947         child = NULL;
948         for (p = uri; p != NULL; p = p->parent) {
949                 GnomeVFSURI *new_element;
950
951                 if (p->parent == NULL) {
952                         GnomeVFSToplevelURI *toplevel;
953                         GnomeVFSToplevelURI *new_toplevel;
954
955                         toplevel = (GnomeVFSToplevelURI *) p;
956                         new_toplevel = g_new (GnomeVFSToplevelURI, 1);
957
958                         new_toplevel->host_name = g_strdup (toplevel->host_name);
959                         new_toplevel->host_port = toplevel->host_port;
960                         new_toplevel->user_name = g_strdup (toplevel->user_name);
961                         new_toplevel->password = g_strdup (toplevel->password);
962
963                         new_element = (GnomeVFSURI *) new_toplevel;
964                 } else {
965                         new_element = g_new (GnomeVFSURI, 1);
966                 }
967
968                 new_element->ref_count = 1;
969                 new_element->text = g_strdup (p->text);
970                 new_element->fragment_id = g_strdup (p->fragment_id);
971                 new_element->method_string = g_strdup (p->method_string);
972                 new_element->method = p->method;
973                 new_element->parent = NULL;
974
975                 if (child != NULL) {
976                         child->parent = new_element;
977                 } else {
978                         new_uri = new_element;
979                 }
980                         
981                 child = new_element;
982         }
983
984         return new_uri;
985 }
986
987 /**
988  * gnome_vfs_uri_append_string:
989  * @uri: A GnomeVFSURI.
990  * @uri_fragment: A piece of a URI (ie a fully escaped partial path)
991  * 
992  * Create a new URI obtained by appending @path to @uri.  This will take care
993  * of adding an appropriate directory separator between the end of @uri and
994  * the start of @path if necessary.
995  * 
996  * Return value: The new URI obtained by combining @uri and @path.
997  **/
998 GnomeVFSURI *
999 gnome_vfs_uri_append_string (const GnomeVFSURI *uri,
1000                              const gchar *uri_fragment)
1001 {
1002         gchar *uri_string;
1003         GnomeVFSURI *new_uri;
1004         gchar *new_string;
1005         guint len;
1006
1007         g_return_val_if_fail (uri != NULL, NULL);
1008         g_return_val_if_fail (uri_fragment != NULL, NULL);
1009
1010         uri_string = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
1011         len = strlen (uri_string);
1012         if (len == 0) {
1013                 g_free (uri_string);
1014                 return gnome_vfs_uri_new (uri_fragment);
1015         }
1016
1017         len--;
1018         while (uri_string[len] == GNOME_VFS_URI_PATH_CHR && len > 0) {
1019                 len--;
1020         }
1021
1022         uri_string[len + 1] = '\0';
1023
1024         while (*uri_fragment == GNOME_VFS_URI_PATH_CHR) {
1025                 uri_fragment++;
1026         }
1027
1028         if (uri_fragment[0] != GNOME_VFS_URI_MAGIC_CHR) {
1029                 new_string = g_strconcat (uri_string, GNOME_VFS_URI_PATH_STR, uri_fragment, NULL);
1030         } else {
1031                 new_string = g_strconcat (uri_string, uri_fragment, NULL);
1032         }
1033         new_uri = gnome_vfs_uri_new (new_string);
1034
1035         g_free (new_string);
1036         g_free (uri_string);
1037
1038         return new_uri;
1039 }
1040
1041 /**
1042  * gnome_vfs_uri_append_path:
1043  * @uri: A GnomeVFSURI.
1044  * @path: A non-escaped file path
1045  * 
1046  * Create a new URI obtained by appending @path to @uri.  This will take care
1047  * of adding an appropriate directory separator between the end of @uri and
1048  * the start of @path if necessary as well as escaping @path as necessary.
1049  * 
1050  * Return value: The new URI obtained by combining @uri and @path.
1051  **/
1052 GnomeVFSURI *
1053 gnome_vfs_uri_append_path (const GnomeVFSURI *uri,
1054                            const gchar *path)
1055 {
1056         gchar *escaped_string;
1057         GnomeVFSURI *new_uri;
1058         
1059         escaped_string = gnome_vfs_escape_path_string (path);
1060         new_uri = gnome_vfs_uri_append_string (uri, escaped_string);
1061         g_free (escaped_string);
1062         return new_uri;
1063 }
1064
1065 /**
1066  * gnome_vfs_uri_append_file_name:
1067  * @uri: A GnomeVFSURI.
1068  * @filename: any "regular" file name (can include #, /, etc)
1069  * 
1070  * Create a new URI obtained by appending @file_name to @uri.  This will take care
1071  * of adding an appropriate directory separator between the end of @uri and
1072  * the start of @file_name if necessary.
1073  * 
1074  * Return value: The new URI obtained by combining @uri and @path.
1075  **/
1076 GnomeVFSURI *
1077 gnome_vfs_uri_append_file_name (const GnomeVFSURI *uri,
1078                                 const gchar *filename)
1079 {
1080         gchar *escaped_string;
1081         GnomeVFSURI *new_uri;
1082         
1083         escaped_string = gnome_vfs_escape_string (filename);
1084         new_uri = gnome_vfs_uri_append_string (uri, escaped_string);
1085         g_free (escaped_string);
1086         return new_uri;
1087 }
1088
1089
1090 /**
1091  * gnome_vfs_uri_to_string:
1092  * @uri: A GnomeVFSURI.
1093  * @hide_options: Bitmask specifying what URI elements (e.g. password,
1094  * user name etc.) should not be represented in the returned string.
1095  * 
1096  * Translate @uri into a printable string.  The string will not contain the
1097  * URI elements specified by @hide_options.
1098  * 
1099  * Return value: A malloced printable string representing @uri.
1100  **/
1101 gchar *
1102 gnome_vfs_uri_to_string (const GnomeVFSURI *uri,
1103                          GnomeVFSURIHideOptions hide_options)
1104 {
1105         GString *string;
1106         gchar *result;
1107
1108         string = g_string_new (uri->method_string);
1109         g_string_append_c (string, ':');
1110
1111         if (uri->parent == NULL) {
1112                 GnomeVFSToplevelURI *top_level_uri = (GnomeVFSToplevelURI *)uri;
1113                 gboolean shown_user_pass = FALSE;
1114
1115                 if (top_level_uri->user_name != NULL
1116                         || top_level_uri->host_name != NULL
1117                         || (uri->text != NULL && uri->text[0] == GNOME_VFS_URI_PATH_CHR)) {
1118                         /* don't append '//' for uris such as pipe:foo */
1119                         g_string_append (string, "//");
1120                 }
1121
1122                 if ((hide_options & GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD) != 0) {
1123                         g_string_free (string, TRUE); /* throw away method */
1124                         string = g_string_new ("");
1125                 }
1126
1127                 if (top_level_uri->user_name != NULL
1128                         && (hide_options & GNOME_VFS_URI_HIDE_USER_NAME) == 0) {
1129                         g_string_append (string, top_level_uri->user_name);
1130                         shown_user_pass = TRUE;
1131                 }
1132
1133                 if (top_level_uri->password != NULL
1134                         && (hide_options & GNOME_VFS_URI_HIDE_PASSWORD) == 0) {
1135                         g_string_append_c (string, ':');
1136                         g_string_append (string, top_level_uri->password);
1137                         shown_user_pass = TRUE;
1138                 }
1139
1140                 if (shown_user_pass) {
1141                         g_string_append_c (string, '@');
1142                 }
1143
1144                 if (top_level_uri->host_name != NULL
1145                         && (hide_options & GNOME_VFS_URI_HIDE_HOST_NAME) == 0) {
1146
1147                         /* Check for an IPv6 address. */
1148
1149                         if (strchr (top_level_uri->host_name, ':')) {
1150                                 g_string_append_c (string, '[');
1151                                 g_string_append (string, top_level_uri->host_name);
1152                                 g_string_append_c (string, ']');
1153                         } else {
1154                                 g_string_append (string, top_level_uri->host_name);
1155                         }
1156                 }
1157                 
1158                 if (top_level_uri->host_port > 0 
1159                         && (hide_options & GNOME_VFS_URI_HIDE_HOST_PORT) == 0) {
1160                         gchar tmp[128];
1161                         sprintf (tmp, ":%d", top_level_uri->host_port);
1162                         g_string_append (string, tmp);
1163                 }
1164
1165         }
1166         
1167         if (uri->text != NULL) {
1168                 g_string_append (string, uri->text);
1169         }
1170
1171         if (uri->fragment_id != NULL 
1172                 && (hide_options & GNOME_VFS_URI_HIDE_FRAGMENT_IDENTIFIER) == 0) {
1173                 g_string_append_c (string, '#');
1174                 g_string_append (string, uri->fragment_id);
1175         }
1176
1177         if (uri->parent != NULL) {
1178                 gchar *uri_str;
1179                 uri_str = gnome_vfs_uri_to_string (uri->parent, hide_options);
1180                 g_string_prepend_c (string, '#');
1181                 g_string_prepend (string, uri_str);
1182                 g_free (uri_str);
1183         }
1184
1185         result = string->str;
1186         g_string_free (string, FALSE);
1187
1188         return result;
1189 }
1190
1191 /**
1192  * gnome_vfs_uri_is_local:
1193  * @uri: A GnomeVFSURI.
1194  * 
1195  * Check if @uri is a local (native) file system.
1196  * 
1197  * Return value: %FALSE if @uri is not a local file system, %TRUE otherwise.
1198  **/
1199 gboolean
1200 gnome_vfs_uri_is_local (const GnomeVFSURI *uri)
1201 {
1202         g_return_val_if_fail (uri != NULL, FALSE);
1203
1204         /* It's illegal to have is_local be NULL in a method.
1205          * That's why we fail here. If we decide that it's legal,
1206          * then we can change this into an if statement.
1207          */
1208         g_return_val_if_fail (VFS_METHOD_HAS_FUNC (uri->method, is_local), FALSE);
1209
1210         return uri->method->is_local (uri->method, uri);
1211 }
1212
1213 /**
1214  * gnome_vfs_uri_has_parent:
1215  * @uri: A GnomeVFSURI.
1216  * 
1217  * Check if URI has a parent or not.
1218  * 
1219  * Return value: %TRUE if @uri has a parent, %FALSE otherwise.
1220  **/
1221 gboolean
1222 gnome_vfs_uri_has_parent (const GnomeVFSURI *uri)
1223 {
1224         GnomeVFSURI *parent;
1225
1226         parent = gnome_vfs_uri_get_parent (uri);
1227         if (parent == NULL) {
1228                 return FALSE;
1229         }
1230
1231         gnome_vfs_uri_unref (parent);
1232         return TRUE;
1233 }
1234
1235 /**
1236  * gnome_vfs_uri_get_parent:
1237  * @uri: A GnomeVFSURI.
1238  * 
1239  * Retrieve @uri's parent URI.
1240  * 
1241  * Return value: A pointer to @uri's parent URI.
1242  **/
1243 GnomeVFSURI *
1244 gnome_vfs_uri_get_parent (const GnomeVFSURI *uri)
1245 {
1246         g_return_val_if_fail (uri != NULL, NULL);
1247
1248         if (uri->text != NULL && strchr (uri->text, GNOME_VFS_URI_PATH_CHR) != NULL) {
1249                 gchar *p;
1250                 guint len;
1251
1252                 len = strlen (uri->text);
1253                 p = uri->text + len - 1;
1254
1255                 /* Skip trailing slashes  */
1256                 while (p != uri->text && *p == GNOME_VFS_URI_PATH_CHR)
1257                         p--;
1258
1259                 /* Search backwards to the next slash.  */
1260                 while (p != uri->text && *p != GNOME_VFS_URI_PATH_CHR)
1261                         p--;
1262
1263                 /* Get the parent without slashes  */
1264                 while (p > uri->text + 1 && p[-1] == GNOME_VFS_URI_PATH_CHR)
1265                         p--;
1266
1267                 if (p[1] != '\0') {
1268                         GnomeVFSURI *new_uri;
1269                         char *new_uri_text;
1270                         int length;
1271
1272                         /* build a new parent text */
1273                         length = p - uri->text;                 
1274                         if (length == 0) {
1275                                 new_uri_text = g_strdup (GNOME_VFS_URI_PATH_STR);
1276                         } else {
1277                                 new_uri_text = g_malloc (length + 1);
1278                                 memcpy (new_uri_text, uri->text, length);
1279                                 new_uri_text[length] = '\0';
1280                         }
1281
1282                         /* copy the uri and replace the uri text with the new parent text */
1283                         new_uri = gnome_vfs_uri_dup (uri);
1284                         g_free (new_uri->text);
1285                         new_uri->text = new_uri_text;
1286
1287                         /* The parent doesn't have the child's fragment */
1288                         g_free (new_uri->fragment_id);
1289                         new_uri->fragment_id = NULL;
1290                         
1291                         return new_uri;
1292                 }
1293         }
1294
1295         return gnome_vfs_uri_dup (uri->parent);
1296 }
1297
1298 /**
1299  * gnome_vfs_uri_get_toplevel:
1300  * @uri: A GnomeVFSURI.
1301  * 
1302  * Retrieve the toplevel URI in @uri.
1303  * 
1304  * Return value: A pointer to the toplevel URI object.
1305  **/
1306 GnomeVFSToplevelURI *
1307 gnome_vfs_uri_get_toplevel (const GnomeVFSURI *uri)
1308 {
1309         const GnomeVFSURI *p;
1310
1311         g_return_val_if_fail (uri != NULL, NULL);
1312
1313         for (p = uri; p->parent != NULL; p = p->parent)
1314                 ;
1315
1316         return (GnomeVFSToplevelURI *) p;
1317 }
1318
1319 /**
1320  * gnome_vfs_uri_get_host_name:
1321  * @uri: A GnomeVFSURI.
1322  * 
1323  * Retrieve the host name for @uri.
1324  * 
1325  * Return value: A string representing the host name.
1326  **/
1327 const gchar *
1328 gnome_vfs_uri_get_host_name (const GnomeVFSURI *uri)
1329 {
1330         GnomeVFSToplevelURI *toplevel;
1331
1332         g_return_val_if_fail (uri != NULL, NULL);
1333
1334         toplevel = gnome_vfs_uri_get_toplevel (uri);
1335         return toplevel->host_name;
1336 }
1337
1338 /**
1339  * gnome_vfs_uri_get_scheme:
1340  * @uri: A GnomeVFSURI
1341  *
1342  * Retrieve the scheme used for @uri
1343  *
1344  * Return value: A string representing the scheme
1345  **/
1346 const gchar *
1347 gnome_vfs_uri_get_scheme (const GnomeVFSURI *uri)
1348 {
1349         return uri->method_string;
1350 }
1351
1352 /**
1353  * gnome_vfs_uri_get_host_port:
1354  * @uri: A GnomeVFSURI.
1355  * 
1356  * Retrieve the host port number in @uri.
1357  * 
1358  * Return value: The host port number used by @uri.  If the value is zero, the
1359  * default port value for the specified toplevel access method is used.
1360  **/
1361 guint
1362 gnome_vfs_uri_get_host_port (const GnomeVFSURI *uri)
1363 {
1364         GnomeVFSToplevelURI *toplevel;
1365
1366         g_return_val_if_fail (uri != NULL, 0);
1367
1368         toplevel = gnome_vfs_uri_get_toplevel (uri);
1369         return toplevel->host_port;
1370 }
1371
1372 /**
1373  * gnome_vfs_uri_get_user_name:
1374  * @uri: A GnomeVFSURI.
1375  * 
1376  * Retrieve the user name in @uri.
1377  * 
1378  * Return value: A string representing the user name in @uri.
1379  **/
1380 const gchar *
1381 gnome_vfs_uri_get_user_name (const GnomeVFSURI *uri)
1382 {
1383         GnomeVFSToplevelURI *toplevel;
1384
1385         g_return_val_if_fail (uri != NULL, NULL);
1386
1387         toplevel = gnome_vfs_uri_get_toplevel (uri);
1388         return toplevel->user_name;
1389 }
1390
1391 /**
1392  * gnome_vfs_uri_get_password:
1393  * @uri: A GnomeVFSURI.
1394  * 
1395  * Retrieve the password for @uri.
1396  * 
1397  * Return value: The password for @uri.
1398  **/
1399 const gchar *
1400 gnome_vfs_uri_get_password (const GnomeVFSURI *uri)
1401 {
1402         GnomeVFSToplevelURI *toplevel;
1403
1404         g_return_val_if_fail (uri != NULL, NULL);
1405
1406         toplevel = gnome_vfs_uri_get_toplevel (uri);
1407         return toplevel->password;
1408 }
1409
1410 /**
1411  * gnome_vfs_uri_set_host_name:
1412  * @uri: A GnomeVFSURI.
1413  * @host_name: A string representing a host name.
1414  * 
1415  * Set @host_name as the host name accessed by @uri.
1416  **/
1417 void
1418 gnome_vfs_uri_set_host_name (GnomeVFSURI *uri,
1419                              const gchar *host_name)
1420 {
1421         GnomeVFSToplevelURI *toplevel;
1422
1423         g_return_if_fail (uri != NULL);
1424
1425         toplevel = gnome_vfs_uri_get_toplevel (uri);
1426
1427         g_free (toplevel->host_name);
1428         toplevel->host_name = g_strdup (host_name);
1429 }
1430
1431 /**
1432  * gnome_vfs_uri_set_host_port:
1433  * @uri: A GnomeVFSURI.
1434  * @host_port: A TCP/IP port number.
1435  * 
1436  * Set the host port number in @uri.  If @host_port is zero, the default port
1437  * for @uri's toplevel access method is used.
1438  **/
1439 void
1440 gnome_vfs_uri_set_host_port (GnomeVFSURI *uri,
1441                              guint host_port)
1442 {
1443         GnomeVFSToplevelURI *toplevel;
1444
1445         g_return_if_fail (uri != NULL);
1446
1447         toplevel = gnome_vfs_uri_get_toplevel (uri);
1448
1449         toplevel->host_port = host_port;
1450 }
1451
1452 /**
1453  * gnome_vfs_uri_set_user_name:
1454  * @uri: A GnomeVFSURI.
1455  * @user_name: A string representing a user name on the host accessed by @uri.
1456  * 
1457  * Set @user_name as the user name for @uri.
1458  **/
1459 void
1460 gnome_vfs_uri_set_user_name (GnomeVFSURI *uri,
1461                              const gchar *user_name)
1462 {
1463         GnomeVFSToplevelURI *toplevel;
1464
1465         g_return_if_fail (uri != NULL);
1466
1467         toplevel = gnome_vfs_uri_get_toplevel (uri);
1468
1469         g_free (toplevel->user_name);
1470         toplevel->user_name = g_strdup (user_name);
1471 }
1472
1473 /**
1474  * gnome_vfs_uri_set_password:
1475  * @uri: A GnomeVFSURI.
1476  * @password: A password string.
1477  * 
1478  * Set @password as the password for @uri.
1479  **/
1480 void
1481 gnome_vfs_uri_set_password (GnomeVFSURI *uri,
1482                             const gchar *password)
1483 {
1484         GnomeVFSToplevelURI *toplevel;
1485
1486         g_return_if_fail (uri != NULL);
1487
1488         toplevel = gnome_vfs_uri_get_toplevel (uri);
1489
1490         g_free (toplevel->password);
1491         toplevel->password = g_strdup (password);
1492 }
1493
1494 static gboolean
1495 string_match (const gchar *a, const gchar *b)
1496 {
1497         if (a == NULL || *a == '\0') {
1498                 return b == NULL || *b == '\0';
1499         }
1500
1501         if (a == NULL || b == NULL)
1502                 return FALSE;
1503
1504         return strcmp (a, b) == 0;
1505 }
1506
1507 static gboolean
1508 compare_elements (const GnomeVFSURI *a,
1509                   const GnomeVFSURI *b)
1510 {
1511         if (!string_match (a->text, b->text)
1512                 || !string_match (a->method_string, b->method_string))
1513                 return FALSE;
1514
1515         /* The following should never fail, but we make sure anyway. */
1516         return a->method == b->method;
1517 }
1518
1519 /**
1520  * gnome_vfs_uri_equal:
1521  * @a: A GnomeVFSURI.
1522  * @b: A GnomeVFSURI.
1523  * 
1524  * Compare @a and @b.
1525  * 
1526  * Return value: %TRUE if @a and @b are equal, %FALSE otherwise.
1527  *
1528  * FIXME: This comparison should take into account the possiblity
1529  * that unreserved characters may be escaped.
1530  * ...or perhaps gnome_vfs_uri_new should unescape unreserved characters?
1531  **/
1532 gboolean
1533 gnome_vfs_uri_equal (const GnomeVFSURI *a,
1534                      const GnomeVFSURI *b)
1535 {
1536         const GnomeVFSToplevelURI *toplevel_a;
1537         const GnomeVFSToplevelURI *toplevel_b;
1538
1539         g_return_val_if_fail (a != NULL, FALSE);
1540         g_return_val_if_fail (b != NULL, FALSE);
1541
1542         /* First check non-toplevel elements.  */
1543         while (a->parent != NULL && b->parent != NULL) {
1544                 if (!compare_elements (a, b)) {
1545                         return FALSE;
1546                 }
1547         }
1548
1549         /* Now we should be at toplevel for both.  */
1550         if (a->parent != NULL || b->parent != NULL) {
1551                 return FALSE;
1552         }
1553
1554         if (!compare_elements (a, b)) {
1555                 return FALSE;
1556         }
1557
1558         toplevel_a = (GnomeVFSToplevelURI *) a;
1559         toplevel_b = (GnomeVFSToplevelURI *) b;
1560
1561         /* Finally, compare the extra toplevel members.  */
1562         return toplevel_a->host_port == toplevel_b->host_port
1563             && string_match (toplevel_a->host_name, toplevel_b->host_name)
1564             && string_match (toplevel_a->user_name, toplevel_b->user_name)
1565             && string_match (toplevel_a->password, toplevel_b->password);
1566 }
1567
1568 /* Convenience function that deals with the problem where we distinguish
1569  * uris "foo://bar.com" and "foo://bar.com/" but we do not define
1570  * what a child item of "foo://bar.com" would be -- to work around this,
1571  * we will consider both "foo://bar.com" and "foo://bar.com/" the parent
1572  * of "foo://bar.com/child"
1573  */
1574 static gboolean
1575 uri_matches_as_parent (const GnomeVFSURI *possible_parent, const GnomeVFSURI *parent)
1576 {
1577         GnomeVFSURI *alternate_possible_parent;
1578         gboolean result;
1579
1580         if (possible_parent->text == NULL ||
1581             strlen (possible_parent->text) == 0) {
1582                 alternate_possible_parent = gnome_vfs_uri_append_string (possible_parent,
1583                         GNOME_VFS_URI_PATH_STR);
1584
1585                 result = gnome_vfs_uri_equal (alternate_possible_parent, parent);
1586                 
1587                 gnome_vfs_uri_unref (alternate_possible_parent);
1588                 return result;
1589         }
1590         
1591         return gnome_vfs_uri_equal (possible_parent, parent);
1592 }
1593
1594 /**
1595  * gnome_vfs_uri_is_parent:
1596  * @possible_parent: A GnomeVFSURI.
1597  * @possible_child: A GnomeVFSURI.
1598  * @recursive: a flag to turn recursive check on.
1599  * 
1600  * Check if @possible_child is contained by @possible_parent.
1601  * If @recursive is FALSE, just try the immediate parent directory, else
1602  * search up through the hierarchy.
1603  * 
1604  * Return value: %TRUE if @possible_child is contained in  @possible_child.
1605  **/
1606 gboolean
1607 gnome_vfs_uri_is_parent (const GnomeVFSURI *possible_parent,
1608                          const GnomeVFSURI *possible_child,
1609                          gboolean recursive)
1610 {
1611         gboolean result;
1612         GnomeVFSURI *item_parent_uri;
1613         GnomeVFSURI *item;
1614
1615         if (!recursive) {
1616                 item_parent_uri = gnome_vfs_uri_get_parent (possible_child);
1617
1618                 if (item_parent_uri == NULL) {
1619                         return FALSE;
1620                 }
1621
1622                 result = uri_matches_as_parent (possible_parent, item_parent_uri);      
1623                 gnome_vfs_uri_unref (item_parent_uri);
1624
1625                 return result;
1626         }
1627         
1628         item = gnome_vfs_uri_dup (possible_child);
1629         for (;;) {
1630                 item_parent_uri = gnome_vfs_uri_get_parent (item);
1631                 gnome_vfs_uri_unref (item);
1632                 
1633                 if (item_parent_uri == NULL) {
1634                         return FALSE;
1635                 }
1636
1637                 result = uri_matches_as_parent (possible_parent, item_parent_uri);
1638         
1639                 if (result) {
1640                         gnome_vfs_uri_unref (item_parent_uri);
1641                         break;
1642                 }
1643
1644                 item = item_parent_uri;
1645         }
1646
1647         return result;
1648 }
1649
1650 /**
1651  * gnome_vfs_uri_get_path:
1652  * @uri: A GnomeVFSURI
1653  * 
1654  * Retrieve full path name for @uri.
1655  * 
1656  * Return value: A pointer to the full path name in @uri.  Notice that the
1657  * pointer points to the name store in @uri, so the name returned must not
1658  * be modified nor freed.
1659  **/
1660 const gchar *
1661 gnome_vfs_uri_get_path (const GnomeVFSURI *uri)
1662 {
1663         /* FIXME bugzilla.eazel.com 1472 */
1664         /* this is based on the assumtion that uri->text won't contain the
1665          * query string.
1666          */
1667         g_return_val_if_fail (uri != NULL, NULL);
1668
1669         return uri->text;
1670 }
1671
1672 /**
1673  * gnome_vfs_uri_get_fragment_id:
1674  * @uri: A GnomeVFSURI
1675  * 
1676  * Retrieve the optional fragment identifier for @uri.
1677  * 
1678  * Return value: A pointer to the fragment identifier for the uri or NULL.
1679  **/
1680 const gchar *
1681 gnome_vfs_uri_get_fragment_identifier (const GnomeVFSURI *uri)
1682 {
1683         g_return_val_if_fail (uri != NULL, NULL);
1684
1685         return uri->fragment_id;
1686 }
1687
1688 /**
1689  * gnome_vfs_uri_extract_dirname:
1690  * @uri: A GnomeVFSURI
1691  * 
1692  * Extract the name of the directory in which the file pointed to by @uri is
1693  * stored as a newly allocated string.  The string will end with a
1694  * GNOME_VFS_URI_PATH_CHR.
1695  * 
1696  * Return value: A pointer to the newly allocated string representing the
1697  * parent directory.
1698  **/
1699 gchar *
1700 gnome_vfs_uri_extract_dirname (const GnomeVFSURI *uri)
1701 {
1702         const gchar *base;
1703
1704         g_return_val_if_fail (uri != NULL, NULL);
1705
1706         if (uri->text == NULL) {
1707                 return NULL;
1708         }
1709         
1710         base = strrchr (uri->text, GNOME_VFS_URI_PATH_CHR);
1711
1712         if (base == NULL || base == uri->text) {
1713                 return g_strdup (GNOME_VFS_URI_PATH_STR);
1714         }
1715
1716         return g_strndup (uri->text, base - uri->text);
1717 }
1718
1719 /**
1720  * gnome_vfs_uri_extract_short_name:
1721  * @uri: A GnomeVFSURI
1722  * 
1723  * Retrieve base file name for @uri, ignoring any trailing path separators.
1724  * This matches the XPG definition of basename, but not g_basename. This is
1725  * often useful when you want the name of something that's pointed to by a
1726  * uri, and don't care whether the uri has a directory or file form.
1727  * If @uri points to the root of a domain, returns the host name. If there's
1728  * no host name, returns GNOME_VFS_URI_PATH_STR.
1729  * 
1730  * See also: gnome_vfs_uri_extract_short_path_name.
1731  * 
1732  * Return value: A pointer to the newly allocated string representing the
1733  * unescaped short form of the name.
1734  **/
1735 gchar *
1736 gnome_vfs_uri_extract_short_name (const GnomeVFSURI *uri)
1737 {
1738         gchar *escaped_short_path_name, *short_path_name;
1739         const gchar *host_name;
1740
1741         escaped_short_path_name = gnome_vfs_uri_extract_short_path_name (uri);
1742         short_path_name = gnome_vfs_unescape_string (escaped_short_path_name, "/");
1743         g_free (escaped_short_path_name);
1744
1745         host_name = NULL;
1746         if (short_path_name != NULL
1747                 && strcmp (short_path_name, GNOME_VFS_URI_PATH_STR) == 0) {
1748                 host_name = gnome_vfs_uri_get_host_name (uri);
1749         }
1750
1751         if (host_name == NULL || strlen (host_name) == 0) {
1752                 return short_path_name;
1753         }
1754
1755         g_free (short_path_name);
1756         return g_strdup (host_name);
1757 }
1758
1759 /**
1760  * gnome_vfs_uri_extract_short_path_name:
1761  * @uri: A GnomeVFSURI
1762  * 
1763  * Retrieve base file name for @uri, ignoring any trailing path separators.
1764  * This matches the XPG definition of basename, but not g_basename. This is
1765  * often useful when you want the name of something that's pointed to by a
1766  * uri, and don't care whether the uri has a directory or file form.
1767  * If @uri points to the root (including the root of any domain),
1768  * returns GNOME_VFS_URI_PATH_STR.
1769  * 
1770  * See also: gnome_vfs_uri_extract_short_name.
1771  * 
1772  * Return value: A pointer to the newly allocated string representing the
1773  * escaped short form of the name.
1774  **/
1775 gchar *
1776 gnome_vfs_uri_extract_short_path_name (const GnomeVFSURI *uri)
1777 {
1778         const gchar *p, *short_name_start, *short_name_end;
1779
1780         g_return_val_if_fail (uri != NULL, NULL);
1781
1782         if (uri->text == NULL) {
1783                 return NULL;
1784         }
1785
1786         /* Search for the last run of non-'/' characters. */
1787         p = uri->text;
1788         short_name_start = NULL;
1789         short_name_end = p;
1790         do {
1791                 if (*p == '\0' || *p == GNOME_VFS_URI_PATH_CHR) {
1792                         /* While we are in a run of non-separators, short_name_end is NULL. */
1793                         if (short_name_end == NULL)
1794                                 short_name_end = p;
1795                 } else {
1796                         /* While we are in a run of separators, short_name_end is not NULL. */
1797                         if (short_name_end != NULL) {
1798                                 short_name_start = p;
1799                                 short_name_end = NULL;
1800                         }
1801                 }
1802         } while (*p++ != '\0');
1803         g_assert (short_name_end != NULL);
1804         
1805         /* If we never found a short name, that means that the string is all
1806            directory separators. Since it can't be an empty string, that means
1807            it points to the root, so "/" is a good result.
1808         */
1809         if (short_name_start == NULL) {
1810                 return g_strdup (GNOME_VFS_URI_PATH_STR);
1811         }
1812
1813         /* Return a copy of the short name. */
1814         return g_strndup (short_name_start, short_name_end - short_name_start);
1815 }
1816
1817 /* The following functions are useful for creating URI hash tables.  */
1818
1819 /**
1820  * gnome_vfs_uri_hequal:
1821  * @a: a pointer to a GnomeVFSURI
1822  * @b: a pointer to a GnomeVFSURI
1823  *
1824  * Function intended for use as a hash table "are these two items
1825  * the same" comparison. Useful for creating a hash table of URIs.
1826  *
1827  * Return value: %TRUE if the URIs are the same
1828  **/
1829 gint
1830 gnome_vfs_uri_hequal (gconstpointer a,
1831                       gconstpointer b)
1832 {
1833         return gnome_vfs_uri_equal (a, b);
1834 }
1835
1836 /**
1837  * gnome_vfs_uri_hash:
1838  * @p: a pointer to a GnomeVFSURI
1839  *
1840  * Creates an integer value from a GnomeVFSURI, appropriate
1841  * for using as the key to a hash table entry.
1842  *
1843  * Return value: a hash key corresponding to @p
1844  **/
1845 guint
1846 gnome_vfs_uri_hash (gconstpointer p)
1847 {
1848         const GnomeVFSURI *uri;
1849         const GnomeVFSURI *uri_p;
1850         guint hash_value;
1851
1852 #define HASH_STRING(value, string)              \
1853         if ((string) != NULL)                   \
1854                 (value) ^= g_str_hash (string);
1855
1856 #define HASH_NUMBER(value, number)              \
1857         (value) ^= number;
1858
1859         uri = (const GnomeVFSURI *) p;
1860         hash_value = 0;
1861
1862         for (uri_p = uri; uri_p != NULL; uri_p = uri_p->parent) {
1863                 HASH_STRING (hash_value, uri_p->text);
1864                 HASH_STRING (hash_value, uri_p->method_string);
1865
1866                 if (uri_p->parent != NULL) {
1867                         const GnomeVFSToplevelURI *toplevel;
1868
1869                         toplevel = (const GnomeVFSToplevelURI *) uri_p;
1870
1871                         HASH_STRING (hash_value, toplevel->host_name);
1872                         HASH_NUMBER (hash_value, toplevel->host_port);
1873                         HASH_STRING (hash_value, toplevel->user_name);
1874                         HASH_STRING (hash_value, toplevel->password);
1875                 }
1876         }
1877
1878         return hash_value;
1879
1880 #undef HASH_STRING
1881 #undef HASH_NUMBER
1882 }
1883
1884 /**
1885  * gnome_vfs_uri_list_ref:
1886  * @list: list of GnomeVFSURI elements
1887  *
1888  * Increments the reference count of the items in @list by one.
1889  *
1890  * Return value: @list
1891  **/
1892 GList *
1893 gnome_vfs_uri_list_ref (GList *list)
1894 {
1895         g_list_foreach (list, (GFunc) gnome_vfs_uri_ref, NULL);
1896         return list;
1897 }
1898
1899 /**
1900  * gnome_vfs_uri_list_unref:
1901  * @list: list of GnomeVFSURI elements
1902  *
1903  * Decrements the reference count of the items in @list by one.
1904  * Note that the list is *not freed* even if each member of the list
1905  * is freed.
1906  *
1907  * Return value: @list
1908  **/
1909 GList *
1910 gnome_vfs_uri_list_unref (GList *list)
1911 {
1912         g_list_foreach (list, (GFunc) gnome_vfs_uri_unref, NULL);
1913         return list;
1914 }
1915
1916 /**
1917  * gnome_vfs_uri_list_copy:
1918  * @list: list of GnomeVFSURI elements
1919  *
1920  * Creates a duplicate of @list, and references each member of
1921  * that list.
1922  *
1923  * Return value: a newly referenced duplicate of @list
1924  **/
1925 GList *
1926 gnome_vfs_uri_list_copy (GList *list)
1927 {
1928         return g_list_copy (gnome_vfs_uri_list_ref (list));
1929 }
1930
1931 /**
1932  * gnome_vfs_uri_list_free:
1933  * @list: list of GnomeVFSURI elements
1934  *
1935  * Decrements the reference count of each member of @list by one,
1936  * and frees the list itself.
1937  **/
1938 void
1939 gnome_vfs_uri_list_free (GList *list)
1940 {
1941         g_list_free (gnome_vfs_uri_list_unref (list));
1942 }
1943
1944 /**
1945  * gnome_vfs_uri_make_full_from_relative:
1946  * @base_uri: a string representing the base URI
1947  * @relative_uri: a URI fragment/reference to be appended to @base_uri
1948  * 
1949  * Returns a full URI given a full base URI, and a secondary URI which may
1950  * be relative.
1951  *
1952  * Return value: a newly allocated string containing the URI 
1953  * (NULL for some bad errors).
1954  **/
1955 char *
1956 gnome_vfs_uri_make_full_from_relative (const char *base_uri,
1957                                        const char *relative_uri)
1958 {
1959         char *result = NULL;
1960
1961         /* See section 5.2 in RFC 2396 */
1962
1963         if (base_uri == NULL && relative_uri == NULL) {
1964                 result = NULL;
1965         } else if (base_uri == NULL) {
1966                 result = g_strdup (relative_uri);
1967         } else if (relative_uri == NULL) {
1968                 result = g_strdup (base_uri);
1969         } else if (is_uri_relative (relative_uri)) {
1970                 result = make_full_uri_from_relative (base_uri, relative_uri);
1971         } else {
1972                 result = g_strdup (relative_uri);
1973         }
1974         
1975         return result;
1976 }
1977
1978 /**
1979  * gnome_vfs_uri_list_parse:
1980  * @uri_list:
1981  * 
1982  * Extracts a list of #GnomeVFSURI objects from a standard text/uri-list,
1983  * such as one you would get on a drop operation.  Use
1984  * #gnome_vfs_uri_list_free when you are done with the list.
1985  *
1986  * Return value: A GList of GnomeVFSURIs
1987  **/
1988 GList*
1989 gnome_vfs_uri_list_parse (const gchar* uri_list)
1990 {
1991         /* Note that this is mostly very stolen from old libgnome/gnome-mime.c */
1992
1993         const gchar *p, *q;
1994         gchar *retval;
1995         GnomeVFSURI *uri;
1996         GList *result = NULL;
1997
1998         g_return_val_if_fail (uri_list != NULL, NULL);
1999
2000         p = uri_list;
2001
2002         /* We don't actually try to validate the URI according to RFC
2003          * 2396, or even check for allowed characters - we just ignore
2004          * comments and trim whitespace off the ends.  We also
2005          * allow LF delimination as well as the specified CRLF.
2006          */
2007         while (p != NULL) {
2008                 if (*p != '#') {
2009                         while (g_ascii_isspace (*p))
2010                                 p++;
2011
2012                         q = p;
2013                         while ((*q != '\0')
2014                                && (*q != '\n')
2015                                && (*q != '\r'))
2016                                 q++;
2017
2018                         if (q > p) {
2019                                 q--;
2020                                 while (q > p
2021                                        && g_ascii_isspace (*q))
2022                                         q--;
2023
2024                                 retval = g_malloc (q - p + 2);
2025                                 strncpy (retval, p, q - p + 1);
2026                                 retval[q - p + 1] = '\0';
2027
2028                                 uri = gnome_vfs_uri_new (retval);
2029
2030                                 g_free (retval);
2031
2032                                 if (uri != NULL)
2033                                         result = g_list_prepend (result, uri);
2034                         }
2035                 }
2036                 p = strchr (p, '\n');
2037                 if (p != NULL)
2038                         p++;
2039         }
2040
2041         return g_list_reverse (result);
2042 }