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-utils.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3 /* gnome-vfs-utils.c - Private utility functions for the GNOME Virtual
4    File System.
5
6    Copyright (C) 1999 Free Software Foundation
7    Copyright (C) 2000, 2001 Eazel, Inc.
8
9    The Gnome Library is free software; you can redistribute it and/or
10    modify it under the terms of the GNU Library General Public License as
11    published by the Free Software Foundation; either version 2 of the
12    License, or (at your option) any later version.
13
14    The Gnome Library is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    Library General Public License for more details.
18
19    You should have received a copy of the GNU Library General Public
20    License along with the Gnome Library; see the file COPYING.LIB.  If not,
21    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22    Boston, MA 02111-1307, USA.
23
24    Authors: Ettore Perazzoli <ettore@comm2000.it>
25             John Sullivan <sullivan@eazel.com> 
26             Darin Adler <darin@eazel.com>
27 */
28
29 #include <config.h>
30 #ifdef HAVE_SYS_PARAM_H
31 #include <sys/param.h>
32 #endif
33 #include "gnome-vfs-utils.h"
34
35 #include "gnome-vfs-i18n.h"
36 #include "gnome-vfs-private-utils.h"
37 #include "gnome-vfs-ops.h"
38 #include "gnome-vfs-mime-handlers.h"
39 #include <glib/gstrfuncs.h>
40 #include <glib/gutils.h>
41 #include <gconf/gconf-client.h>
42 #include <pwd.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 #include <unistd.h>
48
49 #if HAVE_SYS_STATVFS_H
50 #include <sys/statvfs.h>
51 #endif
52
53 #if HAVE_SYS_VFS_H
54 #include <sys/vfs.h>
55 #elif HAVE_SYS_MOUNT_H
56 #include <sys/mount.h>
57 #endif
58
59 #define KILOBYTE_FACTOR 1024.0
60 #define MEGABYTE_FACTOR (1024.0 * 1024.0)
61 #define GIGABYTE_FACTOR (1024.0 * 1024.0 * 1024.0)
62
63 #define READ_CHUNK_SIZE 8192
64
65 #define MAX_SYMLINKS_FOLLOWED 32
66
67
68 /**
69  * gnome_vfs_format_file_size_for_display:
70  * @size:
71  * 
72  * Formats the file size passed in @bytes in a way that is easy for
73  * the user to read. Gives the size in bytes, kilobytes, megabytes or
74  * gigabytes, choosing whatever is appropriate.
75  * 
76  * Returns: a newly allocated string with the size ready to be shown.
77  **/
78
79 gchar*
80 gnome_vfs_format_file_size_for_display (GnomeVFSFileSize size)
81 {
82         if (size < (GnomeVFSFileSize) KILOBYTE_FACTOR) {
83                 if (size == 1)
84                         return g_strdup (_("1 byte"));
85                 else
86                         return g_strdup_printf (_("%u bytes"),
87                                                        (guint) size);
88         } else {
89                 gdouble displayed_size;
90
91                 if (size < (GnomeVFSFileSize) MEGABYTE_FACTOR) {
92                         displayed_size = (gdouble) size / KILOBYTE_FACTOR;
93                         return g_strdup_printf (_("%.1f K"),
94                                                        displayed_size);
95                 } else if (size < (GnomeVFSFileSize) GIGABYTE_FACTOR) {
96                         displayed_size = (gdouble) size / MEGABYTE_FACTOR;
97                         return g_strdup_printf (_("%.1f MB"),
98                                                        displayed_size);
99                 } else {
100                         displayed_size = (gdouble) size / GIGABYTE_FACTOR;
101                         return g_strdup_printf (_("%.1f GB"),
102                                                        displayed_size);
103                 }
104         }
105 }
106
107 typedef enum {
108         UNSAFE_ALL        = 0x1,  /* Escape all unsafe characters   */
109         UNSAFE_ALLOW_PLUS = 0x2,  /* Allows '+'  */
110         UNSAFE_PATH       = 0x4,  /* Allows '/' and '?' and '&' and '='  */
111         UNSAFE_DOS_PATH   = 0x8,  /* Allows '/' and '?' and '&' and '=' and ':' */
112         UNSAFE_HOST       = 0x10, /* Allows '/' and ':' and '@' */
113         UNSAFE_SLASHES    = 0x20  /* Allows all characters except for '/' and '%' */
114 } UnsafeCharacterSet;
115
116 static const guchar acceptable[96] =
117 { /* X0   X1   X2   X3   X4   X5   X6   X7   X8   X9   XA   XB   XC   XD   XE   XF */
118     0x00,0x3F,0x20,0x20,0x20,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x22,0x20,0x3F,0x3F,0x1C, /* 2X  !"#$%&'()*+,-./   */
119     0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x2C, /* 3X 0123456789:;<=>?   */
120     0x30,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, /* 4X @ABCDEFGHIJKLMNO   */
121     0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F, /* 5X PQRSTUVWXYZ[\]^_   */
122     0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, /* 6X `abcdefghijklmno   */
123     0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20  /* 7X pqrstuvwxyz{|}~DEL */
124 };
125
126 enum {
127         RESERVED = 1,
128         UNRESERVED,
129         DELIMITERS,
130         UNWISE,
131         CONTROL,
132         SPACE   
133 };
134
135 static const guchar uri_character_kind[128] =
136 {
137     CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   , 
138     CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,
139     CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,
140     CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,CONTROL   ,
141     /* ' '        !          "          #          $          %          &          '      */
142     SPACE     ,UNRESERVED,DELIMITERS,DELIMITERS,RESERVED  ,DELIMITERS,RESERVED  ,UNRESERVED,
143     /*  (         )          *          +          ,          -          .          /      */
144     UNRESERVED,UNRESERVED,UNRESERVED,RESERVED  ,RESERVED  ,UNRESERVED,UNRESERVED,RESERVED  , 
145     /*  0         1          2          3          4          5          6          7      */
146     UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,
147     /*  8         9          :          ;          <          =          >          ?      */
148     UNRESERVED,UNRESERVED,RESERVED  ,RESERVED  ,DELIMITERS,RESERVED  ,DELIMITERS,RESERVED  ,
149     /*  @         A          B          C          D          E          F          G      */
150     RESERVED  ,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,
151     /*  H         I          J          K          L          M          N          O      */
152     UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,
153     /*  P         Q          R          S          T          U          V          W      */
154     UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,
155     /*  X         Y          Z          [          \          ]          ^          _      */
156     UNRESERVED,UNRESERVED,UNRESERVED,UNWISE    ,UNWISE    ,UNWISE    ,UNWISE    ,UNRESERVED,
157     /*  `         a          b          c          d          e          f          g      */
158     UNWISE    ,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,
159     /*  h         i          j          k          l          m          n          o      */
160     UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,
161     /*  p         q          r          s          t          u          v          w      */
162     UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,UNRESERVED,
163     /*  x         y          z         {           |          }          ~         DEL     */
164     UNRESERVED,UNRESERVED,UNRESERVED,UNWISE    ,UNWISE    ,UNWISE    ,UNRESERVED,CONTROL
165 };
166
167
168 /*  Below modified from libwww HTEscape.c */
169
170 #define HEX_ESCAPE '%'
171
172 /*  Escape undesirable characters using %
173  *  -------------------------------------
174  *
175  * This function takes a pointer to a string in which
176  * some characters may be unacceptable unescaped.
177  * It returns a string which has these characters
178  * represented by a '%' character followed by two hex digits.
179  *
180  * This routine returns a g_malloced string.
181  */
182
183 static const gchar hex[16] = "0123456789ABCDEF";
184
185 static gchar *
186 gnome_vfs_escape_string_internal (const gchar *string, 
187                                   UnsafeCharacterSet mask)
188 {
189 #define ACCEPTABLE_CHAR(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask))
190
191         const gchar *p;
192         gchar *q;
193         gchar *result;
194         guchar c;
195         gint unacceptable;
196         UnsafeCharacterSet use_mask;
197
198         g_return_val_if_fail (mask == UNSAFE_ALL
199                               || mask == UNSAFE_ALLOW_PLUS
200                               || mask == UNSAFE_PATH
201                               || mask == UNSAFE_DOS_PATH
202                               || mask == UNSAFE_HOST
203                               || mask == UNSAFE_SLASHES, NULL);
204
205         if (string == NULL) {
206                 return NULL;
207         }
208         
209         unacceptable = 0;
210         use_mask = mask;
211         for (p = string; *p != '\0'; p++) {
212                 c = *p;
213                 if (!ACCEPTABLE_CHAR (c)) {
214                         unacceptable++;
215                 }
216                 if ((use_mask == UNSAFE_HOST) && 
217                     (unacceptable || (c == '/'))) {
218                         /* when escaping a host, if we hit something that needs to be escaped, or we finally
219                          * hit a path separator, revert to path mode (the host segment of the url is over).
220                          */
221                         use_mask = UNSAFE_PATH;
222                 }
223         }
224         
225         result = g_malloc (p - string + unacceptable * 2 + 1);
226
227         use_mask = mask;
228         for (q = result, p = string; *p != '\0'; p++){
229                 c = *p;
230                 
231                 if (!ACCEPTABLE_CHAR (c)) {
232                         *q++ = HEX_ESCAPE; /* means hex coming */
233                         *q++ = hex[c >> 4];
234                         *q++ = hex[c & 15];
235                 } else {
236                         *q++ = c;
237                 }
238                 if ((use_mask == UNSAFE_HOST) &&
239                     (!ACCEPTABLE_CHAR (c) || (c == '/'))) {
240                         use_mask = UNSAFE_PATH;
241                 }
242         }
243         
244         *q = '\0';
245         
246         return result;
247 }
248
249 /**
250  * gnome_vfs_escape_string:
251  * @string: string to be escaped
252  *
253  * Escapes @string, replacing any and all special characters 
254  * with equivalent escape sequences.
255  *
256  * Return value: a newly allocated string equivalent to @string
257  * but with all special characters escaped
258  **/
259 gchar *
260 gnome_vfs_escape_string (const gchar *string)
261 {
262         return gnome_vfs_escape_string_internal (string, UNSAFE_ALL);
263 }
264
265 /**
266  * gnome_vfs_escape_path_string:
267  * @path: string to be escaped
268  *
269  * Escapes @path, replacing only special characters that would not
270  * be found in paths (so '/', '&', '=', and '?' will not be escaped by
271  * this function).
272  *
273  * Return value: a newly allocated string equivalent to @path but
274  * with non-path characters escaped
275  **/
276 gchar *
277 gnome_vfs_escape_path_string (const gchar *path)
278 {
279         return gnome_vfs_escape_string_internal (path, UNSAFE_PATH);
280 }
281
282 /**
283  * gnome_vfs_escape_host_and_path_string:
284  * @path: string to be escaped
285  *
286  * Escapes @path, replacing only special characters that would not
287  * be found in paths or host name (so '/', '&', '=', ':', '@' 
288  * and '?' will not be escaped by this function).
289  *
290  * Return value: a newly allocated string equivalent to @path but
291  * with non-path/host characters escaped
292  **/
293 gchar *
294 gnome_vfs_escape_host_and_path_string (const gchar *path)
295 {
296         return gnome_vfs_escape_string_internal (path, UNSAFE_HOST);
297 }
298
299 /**
300  * gnome_vfs_escape_slashes:
301  * @string: string to be escaped
302  *
303  * Escapes only '/' and '%' characters in @string, replacing
304  * them with their escape sequence equivalents.
305  *
306  * Return value: a newly allocated string equivalent to @string,
307  * but with no unescaped '/' or '%' characters
308  **/
309 gchar *
310 gnome_vfs_escape_slashes (const gchar *string)
311 {
312         return gnome_vfs_escape_string_internal (string, UNSAFE_SLASHES);
313 }
314
315 char *
316 gnome_vfs_escape_set (const char *string,
317                       const char *match_set)
318 {
319         char *result;
320         const char *scanner;
321         char *result_scanner;
322         int escape_count;
323
324         escape_count = 0;
325
326         if (string == NULL) {
327                 return NULL;
328         }
329
330         if (match_set == NULL) {
331                 return g_strdup (string);
332         }
333         
334         for (scanner = string; *scanner != '\0'; scanner++) {
335                 if (strchr(match_set, *scanner) != NULL) {
336                         /* this character is in the set of characters 
337                          * we want escaped.
338                          */
339                         escape_count++;
340                 }
341         }
342         
343         if (escape_count == 0) {
344                 return g_strdup (string);
345         }
346
347         /* allocate two extra characters for every character that
348          * needs escaping and space for a trailing zero
349          */
350         result = g_malloc (scanner - string + escape_count * 2 + 1);
351         for (scanner = string, result_scanner = result; *scanner != '\0'; scanner++) {
352                 if (strchr(match_set, *scanner) != NULL) {
353                         /* this character is in the set of characters 
354                          * we want escaped.
355                          */
356                         *result_scanner++ = HEX_ESCAPE;
357                         *result_scanner++ = hex[*scanner >> 4];
358                         *result_scanner++ = hex[*scanner & 15];
359                         
360                 } else {
361                         *result_scanner++ = *scanner;
362                 }
363         }
364
365         *result_scanner = '\0';
366
367         return result;
368 }
369
370 /**
371  * gnome_vfs_expand_initial_tilde:
372  * @path: a local file path which may start with a '~'
373  *
374  * If @path starts with a ~, representing the user's home
375  * directory, expand it to the actual path location.
376  *
377  * Return value: a newly allocated string with the initial
378  * tilde (if there was one) converted to an actual path
379  **/
380 char *
381 gnome_vfs_expand_initial_tilde (const char *path)
382 {
383         char *slash_after_user_name, *user_name;
384         struct passwd *passwd_file_entry;
385
386         g_return_val_if_fail (path != NULL, NULL);
387
388         if (path[0] != '~') {
389                 return g_strdup (path);
390         }
391         
392         if (path[1] == '/' || path[1] == '\0') {
393                 return g_strconcat (g_get_home_dir (), &path[1], NULL);
394         }
395
396         slash_after_user_name = strchr (&path[1], '/');
397         if (slash_after_user_name == NULL) {
398                 user_name = g_strdup (&path[1]);
399         } else {
400                 user_name = g_strndup (&path[1],
401                                        slash_after_user_name - &path[1]);
402         }
403         passwd_file_entry = getpwnam (user_name);
404         g_free (user_name);
405
406         if (passwd_file_entry == NULL || passwd_file_entry->pw_dir == NULL) {
407                 return g_strdup (path);
408         }
409
410         return g_strconcat (passwd_file_entry->pw_dir,
411                             slash_after_user_name,
412                             NULL);
413 }
414
415 static int
416 hex_to_int (gchar c)
417 {
418         return  c >= '0' && c <= '9' ? c - '0'
419                 : c >= 'A' && c <= 'F' ? c - 'A' + 10
420                 : c >= 'a' && c <= 'f' ? c - 'a' + 10
421                 : -1;
422 }
423
424 static int
425 unescape_character (const char *scanner)
426 {
427         int first_digit;
428         int second_digit;
429
430         first_digit = hex_to_int (*scanner++);
431         if (first_digit < 0) {
432                 return -1;
433         }
434
435         second_digit = hex_to_int (*scanner++);
436         if (second_digit < 0) {
437                 return -1;
438         }
439
440         return (first_digit << 4) | second_digit;
441 }
442
443 /**
444  * gnome_vfs_unescape_string:
445  * @escaped_string: an escaped URI, path, or other string
446  * @illegal_characters: a string containing a sequence of characters
447  * considered "illegal", '\0' is automatically in this list.
448  *
449  * Decodes escaped characters (i.e. PERCENTxx sequences) in @escaped_string.
450  * Characters are encoded in PERCENTxy form, where xy is the ASCII hex code 
451  * for character 16x+y.
452  * 
453  * Return value: a newly allocated string with the unescaped equivalents, 
454  * or %NULL if @escaped_string contained one of the characters 
455  * in @illegal_characters.
456  **/
457 char *
458 gnome_vfs_unescape_string (const gchar *escaped_string, 
459                            const gchar *illegal_characters)
460 {
461         const gchar *in;
462         gchar *out, *result;
463         gint character;
464         
465         if (escaped_string == NULL) {
466                 return NULL;
467         }
468
469         result = g_malloc (strlen (escaped_string) + 1);
470         
471         out = result;
472         for (in = escaped_string; *in != '\0'; in++) {
473                 character = *in;
474                 if (*in == HEX_ESCAPE) {
475                         character = unescape_character (in + 1);
476
477                         /* Check for an illegal character. We consider '\0' illegal here. */
478                         if (character <= 0
479                             || (illegal_characters != NULL
480                                 && strchr (illegal_characters, (char)character) != NULL)) {
481                                 g_free (result);
482                                 return NULL;
483                         }
484                         in += 2;
485                 }
486                 *out++ = (char)character;
487         }
488         
489         *out = '\0';
490         g_assert (out - result <= strlen (escaped_string));
491         return result;
492         
493 }
494
495 /**
496  * gnome_vfs_unescape_for_display:
497  * @escaped: The string encoded with escaped sequences
498  * 
499  * Similar to gnome_vfs_unescape_string, but it returns something
500  * semi-intelligable to a user even upon receiving traumatic input
501  * such as %00 or URIs in bad form.
502  * 
503  * See also: gnome_vfs_unescape_string.
504  * 
505  * Return value: A pointer to a g_malloc'd string with all characters
506  *               replacing their escaped hex values
507  *
508  * WARNING: You should never use this function on a whole URI!  It
509  * unescapes reserved characters, and can result in a mangled URI
510  * that can not be re-entered.  For example, it unescapes "#" "&" and "?",
511  * which have special meanings in URI strings.
512  **/
513 gchar *
514 gnome_vfs_unescape_string_for_display (const gchar *escaped)
515 {
516         const gchar *in, *start_escape;
517         gchar *out, *result;
518         gint i,j;
519         gchar c;
520         gint invalid_escape;
521
522         if (escaped == NULL) {
523                 return NULL;
524         }
525
526         result = g_malloc (strlen (escaped) + 1);
527         
528         out = result;
529         for (in = escaped; *in != '\0'; ) {
530                 start_escape = in;
531                 c = *in++;
532                 invalid_escape = 0;
533                 
534                 if (c == HEX_ESCAPE) {
535                         /* Get the first hex digit. */
536                         i = hex_to_int (*in++);
537                         if (i < 0) {
538                                 invalid_escape = 1;
539                                 in--;
540                         }
541                         c = i << 4;
542                         
543                         if (invalid_escape == 0) {
544                                 /* Get the second hex digit. */
545                                 i = hex_to_int (*in++);
546                                 if (i < 0) {
547                                         invalid_escape = 2;
548                                         in--;
549                                 }
550                                 c |= i;
551                         }
552                         if (invalid_escape == 0) {
553                                 /* Check for an illegal character. */
554                                 if (c == '\0') {
555                                         invalid_escape = 3;
556                                 }
557                         }
558                 }
559                 if (invalid_escape != 0) {
560                         for (j = 0; j < invalid_escape; j++) {
561                                 *out++ = *start_escape++;
562                         }
563                 } else {
564                         *out++ = c;
565                 }
566         }
567         
568         *out = '\0';
569         g_assert (out - result <= strlen (escaped));
570         return result;
571 }
572
573 /**
574  * gnome_vfs_remove_optional_escapes:
575  * @uri: an escaped uri
576  * 
577  * Scans the uri and converts characters that do not have to be 
578  * escaped into an un-escaped form. The characters that get treated this
579  * way are defined as unreserved by the RFC.
580  * 
581  * Return value: an error value if the uri is found to be malformed.
582  **/
583 GnomeVFSResult
584 gnome_vfs_remove_optional_escapes (char *uri)
585 {
586         guchar *scanner;
587         int character;
588         int length;
589
590         if (uri == NULL) {
591                 return GNOME_VFS_OK;
592         }
593         
594         length = strlen (uri);
595
596         for (scanner = uri; *scanner != '\0'; scanner++, length--) {
597                 if (*scanner == HEX_ESCAPE) {
598                         character = unescape_character (scanner + 1);
599                         if (character < 0) {
600                                 /* invalid hexadecimal character */
601                                 return GNOME_VFS_ERROR_INVALID_URI;
602                         }
603
604                         if (uri_character_kind [character] == UNRESERVED) {
605                                 /* This character does not need to be escaped, convert it
606                                  * to a non-escaped form.
607                                  */
608                                 *scanner = (guchar)character;
609                                 g_assert (length >= 3);
610
611                                 /* Shrink the string covering up the two extra digits of the
612                                  * escaped character. Include the trailing '\0' in the copy
613                                  * to keep the string terminated.
614                                  */
615                                 memmove (scanner + 1, scanner + 3, length - 2);
616                         } else {
617                                 /* This character must stay escaped, skip the entire
618                                  * escaped sequence
619                                  */
620                                 scanner += 2;
621                         }
622                         length -= 2;
623
624                 } else if (*scanner > 127
625                         || uri_character_kind [*scanner] == DELIMITERS
626                         || uri_character_kind [*scanner] == UNWISE
627                         || uri_character_kind [*scanner] == CONTROL) {
628                         /* It is illegal for this character to be in an un-escaped form
629                          * in the uri.
630                          */
631                         return GNOME_VFS_ERROR_INVALID_URI;
632                 }
633         }
634         return GNOME_VFS_OK;
635 }
636
637 static char *
638 gnome_vfs_make_uri_canonical_old (const char *original_uri_text)
639 {
640         GnomeVFSURI *uri;
641         char *result;
642
643         uri = gnome_vfs_uri_new_private (original_uri_text, TRUE, TRUE, FALSE);
644         if (uri == NULL) {
645                 return NULL;;
646         } 
647
648         result = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
649         gnome_vfs_uri_unref (uri);
650
651         return result;
652 }
653
654 /**
655  * gnome_vfs_make_path_name_canonical:
656  * @path: a file path, relative or absolute
657  * 
658  * Calls _gnome_vfs_canonicalize_pathname, allocating storage for the 
659  * result and providing for a cleaner memory management.
660  * 
661  * Return value: a canonical version of @path
662  **/
663 gchar *
664 gnome_vfs_make_path_name_canonical (const gchar *path)
665 {
666         char *path_clone;
667         char *result;
668
669         path_clone = g_strdup (path);
670         result = _gnome_vfs_canonicalize_pathname (path_clone);
671         if (result != path_clone) {
672                 g_free (path_clone);
673                 return g_strdup (result);
674         }
675
676         return path_clone;
677 }
678
679 /**
680  * gnome_vfs_list_deep_free:
681  * @list: list to be freed
682  *
683  * Free @list, and call g_free() on all data members.
684  **/
685 void
686 gnome_vfs_list_deep_free (GList *list)
687 {
688         GList *p;
689
690         if (list == NULL)
691                 return;
692
693         for (p = list; p != NULL; p = p->next) {
694                 g_free (p->data);
695         }
696         g_list_free (list);
697 }
698
699 /**
700  * gnome_vfs_get_local_path_from_uri:
701  * @uri: URI to convert to a local path
702  * 
703  * Create a local path for a file:/// URI. Do not use with URIs
704  * of other methods.
705  *
706  * Return value: a newly allocated string containing the local path 
707  * NULL is returned on error or if the uri isn't a file: URI
708  * without a fragment identifier (or chained URI).
709  **/
710 char *
711 gnome_vfs_get_local_path_from_uri (const char *uri)
712 {
713         const char *path_part;
714
715         if (!_gnome_vfs_istr_has_prefix (uri, "file:/")) {
716                 return NULL;
717         }
718         
719         path_part = uri + strlen ("file:");
720         if (strchr (path_part, '#') != NULL) {
721                 return NULL;
722         }
723         
724         if (_gnome_vfs_istr_has_prefix (path_part, "///")) {
725                 path_part += 2;
726         } else if (_gnome_vfs_istr_has_prefix (path_part, "//")) {
727                 return NULL;
728         }
729
730         return gnome_vfs_unescape_string (path_part, "/");
731 }
732
733 /**
734  * gnome_vfs_get_uri_from_local_path:
735  * @local_full_path: a full local filesystem path (i.e. not relative)
736  * 
737  * Returns a file:/// URI for the local path @local_full_path.
738  *
739  * Return value: a newly allocated string containing the URI corresponding 
740  * to @local_full_path (NULL for some bad errors).
741  **/
742 char *
743 gnome_vfs_get_uri_from_local_path (const char *local_full_path)
744 {
745         char *escaped_path, *result;
746         
747         if (local_full_path == NULL) {
748                 return NULL;
749         }
750
751         g_return_val_if_fail (local_full_path[0] == '/', NULL);
752
753         escaped_path = gnome_vfs_escape_path_string (local_full_path);
754         result = g_strconcat ("file://", escaped_path, NULL);
755         g_free (escaped_path);
756         return result;
757 }
758
759 /**
760  * gnome_vfs_get_volume_free_space:
761  * @vfs_uri:
762  * @size:
763  * 
764  * Stores in @size the amount of free space on a volume.
765  * This only works for local file systems with the file: scheme.
766  *
767  * Returns: GNOME_VFS_OK on success, otherwise an error code
768  */
769 GnomeVFSResult
770 gnome_vfs_get_volume_free_space (const GnomeVFSURI *vfs_uri, 
771                                  GnomeVFSFileSize  *size)
772 {       
773         GnomeVFSFileSize free_blocks, block_size;
774         int statfs_result;
775         const char *path, *scheme;
776         char *unescaped_path;
777
778 #if HAVE_STATVFS
779         struct statvfs statfs_buffer;
780 #else
781         struct statfs statfs_buffer;
782 #endif
783
784         *size = 0;
785
786         path = gnome_vfs_uri_get_path (vfs_uri);
787         if (path == NULL) {
788                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
789         }
790
791         scheme = gnome_vfs_uri_get_scheme (vfs_uri);
792         
793         /* We only handle the file scheme for now */
794         if (g_ascii_strcasecmp (scheme, "file") != 0 || !_gnome_vfs_istr_has_prefix (path, "/")) {
795                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
796         }
797
798         unescaped_path = gnome_vfs_unescape_string (path, G_DIR_SEPARATOR_S);
799         
800 #if HAVE_STATVFS
801         statfs_result = statvfs (unescaped_path, &statfs_buffer);
802 #else
803         statfs_result = statfs (unescaped_path, &statfs_buffer);   
804 #endif  
805
806         g_free (unescaped_path);
807
808         if (statfs_result != 0) {
809                 return gnome_vfs_result_from_errno ();
810         }
811         
812         block_size = statfs_buffer.f_bsize; 
813         free_blocks = statfs_buffer.f_bavail;
814
815         *size = block_size * free_blocks;
816         
817         return GNOME_VFS_OK;
818 }
819
820 char *
821 gnome_vfs_icon_path_from_filename (const char *relative_filename)
822 {
823         const char *gnome_var;
824         char *full_filename;
825         char **paths, **temp_paths;
826
827         if (g_path_is_absolute (relative_filename) &&
828             g_file_test (relative_filename, G_FILE_TEST_EXISTS))
829                 return g_strdup (relative_filename);
830
831         gnome_var = g_getenv ("GNOME_PATH");
832
833         if (gnome_var == NULL) {
834                 gnome_var = PREFIX;
835         }
836
837         paths = g_strsplit (gnome_var, ":", 0); 
838
839         for (temp_paths = paths; *temp_paths != NULL; temp_paths++) {
840                 full_filename = g_strconcat (*temp_paths, "/share/pixmaps/", relative_filename, NULL);
841                 if (g_file_test (full_filename, G_FILE_TEST_EXISTS)) {
842                         g_strfreev (paths);
843                         return full_filename;
844                 }
845                 g_free (full_filename);
846                 full_filename = NULL;
847         }
848
849         g_strfreev (paths);
850         return NULL;
851 }
852
853 static char *
854 strdup_to (const char *string, const char *end)
855 {
856         if (end == NULL) {
857                 return g_strdup (string);
858         }
859         return g_strndup (string, end - string);
860 }
861
862 static gboolean
863 is_executable_file (const char *path)
864 {
865         struct stat stat_buffer;
866
867         /* Check that it exists. */
868         if (stat (path, &stat_buffer) != 0) {
869                 return FALSE;
870         }
871
872         /* Check that it is a file. */
873         if (!S_ISREG (stat_buffer.st_mode)) {
874                 return FALSE;
875         }
876
877         /* Check that it's executable. */
878         if (access (path, X_OK) != 0) {
879                 return FALSE;
880         }
881
882         return TRUE;
883 }
884
885 static gboolean
886 executable_in_path (const char *executable_name)
887 {
888         const char *path_list, *piece_start, *piece_end;
889         char *piece, *raw_path, *expanded_path;
890         gboolean is_good;
891
892         path_list = g_getenv ("PATH");
893
894         for (piece_start = path_list; ; piece_start = piece_end + 1) {
895                 /* Find the next piece of PATH. */
896                 piece_end = strchr (piece_start, ':');
897                 piece = strdup_to (piece_start, piece_end);
898                 g_strstrip (piece);
899                 
900                 if (piece[0] == '\0') {
901                         is_good = FALSE;
902                 } else {
903                         /* Try out this path with the executable. */
904                         raw_path = g_strconcat (piece, "/", executable_name, NULL);
905                         expanded_path = gnome_vfs_expand_initial_tilde (raw_path);
906                         g_free (raw_path);
907                         
908                         is_good = is_executable_file (expanded_path);
909                         g_free (expanded_path);
910                 }
911                 
912                 g_free (piece);
913                 
914                 if (is_good) {
915                         return TRUE;
916                 }
917
918                 if (piece_end == NULL) {
919                         return FALSE;
920                 }
921         }
922 }
923
924 static char *
925 get_executable_name_from_command_string (const char *command_string)
926 {
927         /* FIXME bugzilla.eazel.com 2757: 
928          * We need to handle quoting here for the full-path case */
929         return g_strstrip (strdup_to (command_string, strchr (command_string, ' ')));
930 }
931
932 /**
933  * gnome_vfs_is_executable_command_string:
934  * @command_string:
935  * 
936  * Checks if @command_string starts with the full path of an executable file
937  * or an executable in $PATH.
938  *
939  * Returns: TRUE if command_string started with and executable file, 
940  * FALSE otherwise.
941  */
942 gboolean
943 gnome_vfs_is_executable_command_string (const char *command_string)
944 {
945         char *executable_name;
946         char *executable_path;
947         gboolean found;
948
949         /* Check whether command_string is a full path for an executable. */
950         if (command_string[0] == '/') {
951
952                 /* FIXME bugzilla.eazel.com 2757:
953                  * Because we don't handle quoting, we can check for full
954                  * path including spaces, but no parameters, and full path
955                  * with no spaces with or without parameters. But this will
956                  * fail for quoted full path with spaces, and parameters.
957                  */
958
959                 /* This works if command_string contains a space, but not
960                  * if command_string has parameters.
961                  */
962                 if (is_executable_file (command_string)) {
963                         return TRUE;
964                 }
965
966                 /* This works if full path has no spaces, with or without parameters */
967                 executable_path = get_executable_name_from_command_string (command_string);
968                 found = is_executable_file (executable_path);
969                 g_free (executable_path);
970
971                 return found;
972         }
973         
974         executable_name = get_executable_name_from_command_string (command_string);
975         found = executable_in_path (executable_name);
976         g_free (executable_name);
977
978         return found;
979 }
980
981 /**
982  * gnome_vfs_read_entire_file:
983  * @uri: URI of the file to read
984  * @file_size: after reading the file, contains the size of the file read
985  * @file_contents: contains the file_size bytes, the contents of the file at @uri.
986  * 
987  * Reads an entire file into memory for convenience. Beware accidentally
988  * loading large files into memory with this function.
989  *
990  * Return value: An integer representing the result of the operation
991  *
992  * Since: 2.2
993  */
994
995 GnomeVFSResult
996 gnome_vfs_read_entire_file (const char *uri,
997                             int *file_size,
998                             char **file_contents)
999 {
1000         GnomeVFSResult result;
1001         GnomeVFSHandle *handle;
1002         char *buffer;
1003         GnomeVFSFileSize total_bytes_read;
1004         GnomeVFSFileSize bytes_read;
1005
1006         *file_size = 0;
1007         *file_contents = NULL;
1008
1009         /* Open the file. */
1010         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
1011         if (result != GNOME_VFS_OK) {
1012                 return result;
1013         }
1014
1015         /* Read the whole thing. */
1016         buffer = NULL;
1017         total_bytes_read = 0;
1018         do {
1019                 buffer = g_realloc (buffer, total_bytes_read + READ_CHUNK_SIZE);
1020                 result = gnome_vfs_read (handle,
1021                                          buffer + total_bytes_read,
1022                                          READ_CHUNK_SIZE,
1023                                          &bytes_read);
1024                 if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF) {
1025                         g_free (buffer);
1026                         gnome_vfs_close (handle);
1027                         return result;
1028                 }
1029
1030                 /* Check for overflow. */
1031                 if (total_bytes_read + bytes_read < total_bytes_read) {
1032                         g_free (buffer);
1033                         gnome_vfs_close (handle);
1034                         return GNOME_VFS_ERROR_TOO_BIG;
1035                 }
1036
1037                 total_bytes_read += bytes_read;
1038         } while (result == GNOME_VFS_OK);
1039
1040         /* Close the file. */
1041         result = gnome_vfs_close (handle);
1042         if (result != GNOME_VFS_OK) {
1043                 g_free (buffer);
1044                 return result;
1045         }
1046
1047         /* Return the file. */
1048         *file_size = total_bytes_read;
1049         *file_contents = g_realloc (buffer, total_bytes_read);
1050         return GNOME_VFS_OK;
1051 }
1052
1053 static char *
1054 gnome_vfs_make_valid_utf8 (const char *name)
1055 {
1056         GString *string;
1057         const char *remainder, *invalid;
1058         int remaining_bytes, valid_bytes;
1059
1060         string = NULL;
1061         remainder = name;
1062         remaining_bytes = strlen (name);
1063
1064         while (remaining_bytes != 0) {
1065                 if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
1066                         break;
1067                 }
1068                 valid_bytes = invalid - remainder;
1069
1070                 if (string == NULL) {
1071                         string = g_string_sized_new (remaining_bytes);
1072                 }
1073                 g_string_append_len (string, remainder, valid_bytes);
1074                 g_string_append_c (string, '?');
1075
1076                 remaining_bytes -= valid_bytes + 1;
1077                 remainder = invalid + 1;
1078         }
1079
1080         if (string == NULL) {
1081                 return g_strdup (name);
1082         }
1083
1084         g_string_append (string, remainder);
1085         g_string_append (string, _(" (invalid Unicode)"));
1086         g_assert (g_utf8_validate (string->str, -1, NULL));
1087
1088         return g_string_free (string, FALSE);
1089 }
1090
1091 static char *
1092 gnome_vfs_format_uri_for_display_internal (const char *uri, gboolean filenames_are_locale_encoded)
1093 {
1094         char *canonical_uri, *path, *utf8_path;
1095
1096         g_return_val_if_fail (uri != NULL, g_strdup (""));
1097
1098         canonical_uri = gnome_vfs_make_uri_canonical_old (uri);
1099
1100         /* If there's no fragment and it's a local path. */
1101         path = gnome_vfs_get_local_path_from_uri (canonical_uri);
1102         
1103         if (path != NULL) {
1104                 if (filenames_are_locale_encoded) {
1105                         utf8_path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
1106                         if (utf8_path) {
1107                                 g_free (canonical_uri);
1108                                 g_free (path);
1109                                 return utf8_path;
1110                         } 
1111                 } else if (g_utf8_validate (path, -1, NULL)) {
1112                         g_free (canonical_uri);
1113                         return path;
1114                 }
1115         }
1116
1117         if (canonical_uri && !g_utf8_validate (canonical_uri, -1, NULL)) {
1118                 utf8_path = gnome_vfs_make_valid_utf8 (canonical_uri);
1119                 g_free (canonical_uri);
1120                 canonical_uri = utf8_path;
1121         }
1122
1123         g_free (path);
1124         return canonical_uri;
1125 }
1126
1127
1128 /**
1129  * gnome_vfs_format_uri_for_display:
1130  *
1131  * Filter, modify, unescape and change URIs to make them appropriate
1132  * to display to users. The conversion is done such that the roundtrip
1133  * to UTF-8 is reversible.
1134  * 
1135  * Rules:
1136  *      file: URI's without fragments should appear as local paths
1137  *      file: URI's with fragments should appear as file: URI's
1138  *      All other URI's appear as expected
1139  *
1140  * @uri: a URI
1141  *
1142  * Returns: a newly allocated UTF-8 string
1143  *
1144  * Since: 2.2
1145  **/
1146
1147 char *
1148 gnome_vfs_format_uri_for_display (const char *uri) 
1149 {
1150         static gboolean broken_filenames;
1151         
1152         broken_filenames = g_getenv ("G_BROKEN_FILENAMES") != NULL;
1153
1154         return gnome_vfs_format_uri_for_display_internal (uri, broken_filenames);
1155 }
1156
1157 static gboolean
1158 is_valid_scheme_character (char c)
1159 {
1160         return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.';
1161 }
1162
1163 static gboolean
1164 has_valid_scheme (const char *uri)
1165 {
1166         const char *p;
1167
1168         p = uri;
1169
1170         if (!is_valid_scheme_character (*p)) {
1171                 return FALSE;
1172         }
1173
1174         do {
1175                 p++;
1176         } while (is_valid_scheme_character (*p));
1177
1178         return *p == ':';
1179 }
1180
1181 static char *
1182 gnome_vfs_escape_high_chars (const guchar *string)
1183 {
1184         char *result;
1185         const guchar *scanner;
1186         guchar *result_scanner;
1187         int escape_count;
1188         static const gchar hex[16] = "0123456789ABCDEF";
1189
1190 #define ACCEPTABLE(a) ((a)>=32 && (a)<128)
1191         
1192         escape_count = 0;
1193
1194         if (string == NULL) {
1195                 return NULL;
1196         }
1197
1198         for (scanner = string; *scanner != '\0'; scanner++) {
1199                 if (!ACCEPTABLE(*scanner)) {
1200                         escape_count++;
1201                 }
1202         }
1203         
1204         if (escape_count == 0) {
1205                 return g_strdup (string);
1206         }
1207
1208         /* allocate two extra characters for every character that
1209          * needs escaping and space for a trailing zero
1210          */
1211         result = g_malloc (scanner - string + escape_count * 2 + 1);
1212         for (scanner = string, result_scanner = result; *scanner != '\0'; scanner++) {
1213                 if (!ACCEPTABLE(*scanner)) {
1214                         *result_scanner++ = '%';
1215                         *result_scanner++ = hex[*scanner >> 4];
1216                         *result_scanner++ = hex[*scanner & 15];
1217                         
1218                 } else {
1219                         *result_scanner++ = *scanner;
1220                 }
1221         }
1222
1223         *result_scanner = '\0';
1224
1225         return result;
1226 }
1227
1228 /* http uris look like <something>.<2-4 letters>, possibly followed by a slash and some text. */
1229 static gboolean
1230 looks_like_http_uri (const char *str)
1231 {
1232         int len;
1233         int i;
1234         char c;
1235         const char *first_slash;
1236
1237         first_slash = strchr(str, '/');
1238         if (first_slash == NULL) {
1239                 len = strlen (str);
1240         } else {
1241                 len = first_slash - str;
1242         }
1243         for (i = 0; i < 5 && i < len; i++) {
1244                 c = str[len - 1 - i];
1245                 if (i >= 2 && c == '.') {
1246                         return TRUE;
1247                 }
1248                 if (!g_ascii_isalpha (c)) {
1249                         return FALSE;
1250                 }
1251         }
1252         return FALSE;
1253 }
1254
1255 /* The strip_trailing_whitespace option is intended to make copy/paste of
1256  * URIs less error-prone when it is known that trailing whitespace isn't
1257  * part of the uri.
1258  */
1259 static char *
1260 gnome_vfs_make_uri_from_input_internal (const char *text,
1261                                   gboolean filenames_are_locale_encoded,
1262                                   gboolean strip_trailing_whitespace)
1263 {
1264         char *stripped, *path, *uri, *locale_path, *filesystem_path, *escaped;
1265
1266         g_return_val_if_fail (text != NULL, g_strdup (""));
1267
1268         /* Strip off leading whitespaces (since they can't be part of a valid
1269            uri).   Only strip off trailing whitespaces when requested since
1270            they might be part of a valid uri.
1271          */
1272         if (strip_trailing_whitespace) {
1273                 stripped = g_strstrip (g_strdup (text));
1274         } else {
1275                 stripped = g_strchug (g_strdup (text));
1276         }
1277
1278         switch (stripped[0]) {
1279         case '\0':
1280                 uri = g_strdup ("");
1281                 break;
1282         case '/':
1283                 if (filenames_are_locale_encoded) {
1284                         GError *error = NULL;
1285                         locale_path = g_locale_from_utf8 (stripped, -1, NULL, NULL, &error);
1286                         if (locale_path != NULL) {
1287                                 uri = gnome_vfs_get_uri_from_local_path (locale_path);
1288                                 g_free (locale_path);
1289                         } else {
1290                                 /* We couldn't convert to the locale. */
1291                                 /* FIXME: We should probably give a user-visible error here. */
1292                                 uri = g_strdup("");
1293                         }
1294                 } else {
1295                         uri = gnome_vfs_get_uri_from_local_path (stripped);
1296                 }
1297                 break;
1298         case '~':
1299                 if (filenames_are_locale_encoded) {
1300                         filesystem_path = g_locale_from_utf8 (stripped, -1, NULL, NULL, NULL);
1301                 } else {
1302                         filesystem_path = g_strdup (stripped);
1303                 }
1304                 /* deliberately falling into default case on fail */
1305                 if (filesystem_path != NULL) {
1306                         path = gnome_vfs_expand_initial_tilde (filesystem_path);
1307                         g_free (filesystem_path);
1308                         if (*path == '/') {
1309                                 uri = gnome_vfs_get_uri_from_local_path (path);
1310                                 g_free (path);
1311                                 break;
1312                         }
1313                         g_free (path);
1314                 }
1315                 /* don't insert break here, read above comment */
1316         default:
1317                 if (has_valid_scheme (stripped)) {
1318                         uri = gnome_vfs_escape_high_chars (stripped);
1319                 } else if (looks_like_http_uri (stripped)) {
1320                         escaped = gnome_vfs_escape_high_chars (stripped);
1321                         uri = g_strconcat ("http://", escaped, NULL);
1322                         g_free (escaped);
1323                 } else {
1324                         escaped = gnome_vfs_escape_high_chars (stripped);
1325                         uri = g_strconcat ("file://", escaped, NULL);
1326                         g_free (escaped);
1327                 }
1328         }
1329
1330         g_free (stripped);
1331
1332         return uri;
1333         
1334 }
1335
1336 /**
1337  * gnome_vfs_make_uri_from_input:
1338  * @location: a possibly mangled "uri", in UTF8
1339  *
1340  * Takes a user input path/URI and makes a valid URI out of it.
1341  *
1342  * This function is the reverse of gnome_vfs_format_uri_for_display
1343  * but it also handles the fact that the user could have typed
1344  * arbitrary UTF8 in the entry showing the string.
1345  *
1346  * Returns: a newly allocated uri.
1347  *
1348  * Since: 2.2
1349  **/
1350 char *
1351 gnome_vfs_make_uri_from_input (const char *location)
1352 {
1353         static gboolean broken_filenames;
1354
1355         broken_filenames = g_getenv ("G_BROKEN_FILENAMES") != NULL;
1356
1357         return gnome_vfs_make_uri_from_input_internal (location, broken_filenames, TRUE);
1358 }
1359
1360 /**
1361  * gnome_vfs_make_uri_from_input_with_dirs:
1362  * @in: a relative or absolute path
1363  *
1364  * Determines a fully qualified URL from a relative or absolute input path.
1365  * Basically calls gnome_vfs_make_uri_from_input except it specifically
1366  * tries to support paths relative to the specified directories (can be homedir
1367  * and/or current directory).
1368  *
1369  * Return value: a newly allocated string containing the fully qualified URL
1370  *
1371  * Since: 2.4
1372  */
1373 char *
1374 gnome_vfs_make_uri_from_input_with_dirs (const char *in,
1375                                          GnomeVFSMakeURIDirs dirs)
1376 {
1377         char *uri, *path, *dir;
1378
1379         switch (in[0]) {
1380         case '\0':
1381                 uri = g_strdup ("");
1382                 break;
1383                 
1384         case '~':
1385         case '/':
1386                 uri = gnome_vfs_make_uri_from_input (in);
1387                 break;
1388                 
1389         default:
1390                 /* this might be a relative path, check if it exists relative
1391                  * to current dir and home dir.
1392                  */
1393                 uri = NULL;
1394                 if (dirs & GNOME_VFS_MAKE_URI_DIR_CURRENT) {
1395                         dir = g_get_current_dir ();
1396                         path = g_build_filename (dir, in, NULL);
1397                         g_free (dir);
1398                         
1399                         if (g_file_test (path, G_FILE_TEST_EXISTS)) {
1400                                 uri = gnome_vfs_make_uri_from_input (path);
1401                         }
1402                         g_free (path);
1403                 }
1404
1405                 if (uri == NULL &&
1406                     dirs & GNOME_VFS_MAKE_URI_DIR_HOMEDIR) {
1407                         path = g_build_filename (g_get_home_dir (), in, NULL);
1408                 
1409                         if (g_file_test (path, G_FILE_TEST_EXISTS)) {
1410                                 uri = gnome_vfs_make_uri_from_input (path);
1411                         }
1412                         g_free (path);
1413                 }
1414
1415                 if (uri == NULL) {
1416                         uri = gnome_vfs_make_uri_from_input (in);
1417                 }
1418         }
1419         
1420         return uri;
1421 }
1422
1423
1424 /**
1425  * gnome_vfs_make_uri_canonical_strip_fragment:
1426  * @uri:
1427  *
1428  * If the @uri passed contains a fragment (anything after a '#') strips if,
1429  * then makes the URI canonical.
1430  *
1431  * Returns: a newly allocated string containing a canonical URI.
1432  *
1433  * Since: 2.2
1434  **/
1435
1436 char *
1437 gnome_vfs_make_uri_canonical_strip_fragment (const char *uri)
1438 {
1439         const char *fragment;
1440         char *without_fragment, *canonical;
1441
1442         fragment = strchr (uri, '#');
1443         if (fragment == NULL) {
1444                 return gnome_vfs_make_uri_canonical (uri);
1445         }
1446
1447         without_fragment = g_strndup (uri, fragment - uri);
1448         canonical = gnome_vfs_make_uri_canonical (without_fragment);
1449         g_free (without_fragment);
1450         return canonical;
1451 }
1452
1453 static gboolean
1454 uris_match (const char *uri_1, const char *uri_2, gboolean ignore_fragments)
1455 {
1456         char *canonical_1, *canonical_2;
1457         gboolean result;
1458
1459         if (ignore_fragments) {
1460                 canonical_1 = gnome_vfs_make_uri_canonical_strip_fragment (uri_1);
1461                 canonical_2 = gnome_vfs_make_uri_canonical_strip_fragment (uri_2);
1462         } else {
1463                 canonical_1 = gnome_vfs_make_uri_canonical (uri_1);
1464                 canonical_2 = gnome_vfs_make_uri_canonical (uri_2);
1465         }
1466
1467         result = strcmp (canonical_1, canonical_2) ? FALSE : TRUE;
1468
1469         g_free (canonical_1);
1470         g_free (canonical_2);
1471         
1472         return result;
1473 }
1474
1475 /**
1476  * gnome_vfs_uris_match:
1477  * @uri_1: stringified URI to compare with @uri_2.
1478  * @uri_2: stringified URI to compare with @uri_1.
1479  * 
1480  * Compare two URIs.
1481  *
1482  * Return value: TRUE if they are the same, FALSE otherwise.
1483  *
1484  * Since: 2.2
1485  **/
1486
1487 gboolean
1488 gnome_vfs_uris_match (const char *uri_1, const char *uri_2)
1489 {
1490         return uris_match (uri_1, uri_2, FALSE);
1491 }
1492
1493 static gboolean
1494 gnome_vfs_str_has_prefix (const char *haystack, const char *needle)
1495 {
1496         const char *h, *n;
1497
1498         /* Eat one character at a time. */
1499         h = haystack == NULL ? "" : haystack;
1500         n = needle == NULL ? "" : needle;
1501         do {
1502                 if (*n == '\0') {
1503                         return TRUE;
1504                 }
1505                 if (*h == '\0') {
1506                         return FALSE;
1507                 }
1508         } while (*h++ == *n++);
1509         return FALSE;
1510 }
1511
1512
1513 static gboolean
1514 gnome_vfs_uri_is_local_scheme (const char *uri)
1515 {
1516         gboolean is_local_scheme;
1517         char *temp_scheme;
1518         int i;
1519         char *local_schemes[] = {"file:", "help:", "ghelp:", "gnome-help:",
1520                                  "trash:", "man:", "info:", 
1521                                  "hardware:", "search:", "pipe:",
1522                                  "gnome-trash:", NULL};
1523
1524         is_local_scheme = FALSE;
1525         for (temp_scheme = *local_schemes, i = 0; temp_scheme != NULL; i++, temp_scheme = local_schemes[i]) {
1526                 is_local_scheme = _gnome_vfs_istr_has_prefix (uri, temp_scheme);
1527                 if (is_local_scheme) {
1528                         break;
1529                 }
1530         }
1531         
1532
1533         return is_local_scheme;
1534 }
1535
1536 static char *
1537 gnome_vfs_handle_trailing_slashes (const char *uri)
1538 {
1539         char *temp, *uri_copy;
1540         gboolean previous_char_is_column, previous_chars_are_slashes_without_column;
1541         gboolean previous_chars_are_slashes_with_column;
1542         gboolean is_local_scheme;
1543
1544         g_assert (uri != NULL);
1545
1546         uri_copy = g_strdup (uri);
1547         if (strlen (uri_copy) <= 2) {
1548                 return uri_copy;
1549         }
1550
1551         is_local_scheme = gnome_vfs_uri_is_local_scheme (uri);
1552
1553         previous_char_is_column = FALSE;
1554         previous_chars_are_slashes_without_column = FALSE;
1555         previous_chars_are_slashes_with_column = FALSE;
1556
1557         /* remove multiple trailing slashes */
1558         for (temp = uri_copy; *temp != '\0'; temp++) {
1559                 if (*temp == '/' && !previous_char_is_column) {
1560                         previous_chars_are_slashes_without_column = TRUE;
1561                 } else if (*temp == '/' && previous_char_is_column) {
1562                         previous_chars_are_slashes_without_column = FALSE;
1563                         previous_char_is_column = TRUE;
1564                         previous_chars_are_slashes_with_column = TRUE;
1565                 } else {
1566                         previous_chars_are_slashes_without_column = FALSE;
1567                         previous_char_is_column = FALSE;
1568                         previous_chars_are_slashes_with_column = FALSE;
1569                 }
1570
1571                 if (*temp == ':') {
1572                         previous_char_is_column = TRUE;
1573                 }
1574         }
1575
1576         if (*temp == '\0' && previous_chars_are_slashes_without_column) {
1577                 if (is_local_scheme) {
1578                         /* go back till you remove them all. */
1579                         for (temp--; *(temp) == '/'; temp--) {
1580                                 *temp = '\0';
1581                         }
1582                 } else {
1583                         /* go back till you remove them all but one. */
1584                         for (temp--; *(temp - 1) == '/'; temp--) {
1585                                 *temp = '\0';
1586                         }                       
1587                 }
1588         }
1589
1590         if (*temp == '\0' && previous_chars_are_slashes_with_column) {
1591                 /* go back till you remove them all but three. */
1592                 for (temp--; *(temp - 3) != ':' && *(temp - 2) != ':' && *(temp - 1) != ':'; temp--) {
1593                         *temp = '\0';
1594                 }
1595         }
1596
1597
1598         return uri_copy;
1599 }
1600
1601 /**
1602  * gnome_vfs_make_uri_canonical:
1603  * @uri: and absolute or relative URI, it might have scheme.
1604  *
1605  * Standarizes the format of the uri being passed, so that it can be used
1606  * later in other functions that expect a canonical URI.
1607  *
1608  * Returns: a newly allocated string that contains the canonical 
1609  * representation of @uri.
1610  *
1611  * Since: 2.2
1612  **/
1613
1614 char *
1615 gnome_vfs_make_uri_canonical (const char *uri)
1616 {
1617         char *canonical_uri, *old_uri, *p;
1618         gboolean relative_uri;
1619
1620         relative_uri = FALSE;
1621
1622         if (uri == NULL) {
1623                 return NULL;
1624         }
1625
1626         /* FIXME bugzilla.eazel.com 648: 
1627          * This currently ignores the issue of two uris that are not identical but point
1628          * to the same data except for the specific cases of trailing '/' characters,
1629          * file:/ and file:///, and "lack of file:".
1630          */
1631
1632         canonical_uri = gnome_vfs_handle_trailing_slashes (uri);
1633
1634         /* Note: In some cases, a trailing slash means nothing, and can
1635          * be considered equivalent to no trailing slash. But this is
1636          * not true in every case; specifically not for web addresses passed
1637          * to a web-browser. So we don't have the trailing-slash-equivalence
1638          * logic here, but we do use that logic in EelDirectory where
1639          * the rules are more strict.
1640          */
1641
1642         /* Add file: if there is no scheme. */
1643         if (strchr (canonical_uri, ':') == NULL) {
1644                 old_uri = canonical_uri;
1645
1646                 if (old_uri[0] != '/') {
1647                         /* FIXME bugzilla.eazel.com 5069: 
1648                          *  bandaid alert. Is this really the right thing to do?
1649                          * 
1650                          * We got what really is a relative path. We do a little bit of
1651                          * a stretch here and assume it was meant to be a cryptic absolute path,
1652                          * and convert it to one. Since we can't call gnome_vfs_uri_new and
1653                          * gnome_vfs_uri_to_string to do the right make-canonical conversion,
1654                          * we have to do it ourselves.
1655                          */
1656                         relative_uri = TRUE;
1657                         canonical_uri = gnome_vfs_make_path_name_canonical (old_uri);
1658                         g_free (old_uri);
1659                         old_uri = canonical_uri;
1660                         canonical_uri = g_strconcat ("file:///", old_uri, NULL);
1661                 } else {
1662                         canonical_uri = g_strconcat ("file:", old_uri, NULL);
1663                 }
1664                 g_free (old_uri);
1665         }
1666
1667         /* Lower-case the scheme. */
1668         for (p = canonical_uri; *p != ':'; p++) {
1669                 g_assert (*p != '\0');
1670                 *p = g_ascii_tolower (*p);
1671         }
1672
1673         if (!relative_uri) {
1674                 old_uri = canonical_uri;
1675                 canonical_uri = gnome_vfs_make_uri_canonical_old (canonical_uri);
1676                 if (canonical_uri != NULL) {
1677                         g_free (old_uri);
1678                 } else {
1679                         canonical_uri = old_uri;
1680                 }
1681         }
1682         
1683         /* FIXME bugzilla.eazel.com 2802:
1684          * Work around gnome-vfs's desire to convert file:foo into file://foo
1685          * by converting to file:///foo here. When you remove this, check that
1686          * typing "foo" into location bar does not crash and returns an error
1687          * rather than displaying the contents of /
1688          */
1689         if (gnome_vfs_str_has_prefix (canonical_uri, "file://")
1690             && !gnome_vfs_str_has_prefix (canonical_uri, "file:///")) {
1691                 old_uri = canonical_uri;
1692                 canonical_uri = g_strconcat ("file:/", old_uri + 5, NULL);
1693                 g_free (old_uri);
1694         }
1695
1696         return canonical_uri;
1697 }
1698
1699 /**
1700  * gnome_vfs_get_uri_scheme:
1701  * @uri: a stringified URI
1702  *
1703  * Retrieve the scheme used in @uri 
1704  *
1705  * Return value: A newly allocated string containing the scheme, NULL
1706  * if @uri it doesn't seem to contain a scheme
1707  *
1708  * Since: 2.2
1709  **/
1710
1711 char *
1712 gnome_vfs_get_uri_scheme (const char *uri)
1713 {
1714         char *colon;
1715
1716         g_return_val_if_fail (uri != NULL, NULL);
1717
1718         colon = strchr (uri, ':');
1719         
1720         if (colon == NULL) {
1721                 return NULL;
1722         }
1723         
1724         return g_strndup (uri, colon - uri);
1725 }
1726
1727 /* Note that NULL's and full paths are also handled by this function.
1728  * A NULL location will return the current working directory
1729  */
1730 static char *
1731 file_uri_from_local_relative_path (const char *location)
1732 {
1733         char *current_dir;
1734         char *base_uri, *base_uri_slash;
1735         char *location_escaped;
1736         char *uri;
1737
1738         current_dir = g_get_current_dir ();
1739         base_uri = gnome_vfs_get_uri_from_local_path (current_dir);
1740         /* g_get_current_dir returns w/o trailing / */
1741         base_uri_slash = g_strconcat (base_uri, "/", NULL);
1742
1743         location_escaped = gnome_vfs_escape_path_string (location);
1744
1745         uri = gnome_vfs_uri_make_full_from_relative (base_uri_slash, location_escaped);
1746
1747         g_free (location_escaped);
1748         g_free (base_uri_slash);
1749         g_free (base_uri);
1750         g_free (current_dir);
1751
1752         return uri;
1753 }
1754
1755 /**
1756  * gnome_vfs_make_uri_from_shell_arg:
1757  * @location: a possibly mangled "uri"
1758  *
1759  * Similar to gnome_vfs_make_uri_from_input, except that:
1760  * 
1761  * 1) guesses relative paths instead of http domains
1762  * 2) doesn't bother stripping leading/trailing white space
1763  * 3) doesn't bother with ~ expansion--that's done by the shell
1764  *
1765  * Returns: a newly allocated uri
1766  *
1767  * Since: 2.2
1768  **/
1769
1770 char *
1771 gnome_vfs_make_uri_from_shell_arg (const char *location)
1772 {
1773         char *uri;
1774
1775         g_return_val_if_fail (location != NULL, g_strdup (""));
1776
1777         switch (location[0]) {
1778         case '\0':
1779                 uri = g_strdup ("");
1780                 break;
1781         case '/':
1782                 uri = gnome_vfs_get_uri_from_local_path (location);
1783                 break;
1784         default:
1785                 if (has_valid_scheme (location)) {
1786                         uri = g_strdup (location);
1787                 } else {
1788                         uri = file_uri_from_local_relative_path (location);
1789                 }
1790         }
1791
1792         return uri;
1793 }
1794
1795 /**
1796  * gnome_vfs_make_uri_full_from_relative:
1797  * 
1798  * Returns a full URI given a full base URI, and a secondary URI which may
1799  * be relative.
1800  *
1801  * This function is deprecated, please use 
1802  * gnome_vfs_uri_make_full_from_relative from gnome-vfs-uri.h
1803  *
1804  * Return value: the URI (NULL for some bad errors).
1805  *
1806  * Since: 2.2
1807  **/
1808
1809 char *
1810 gnome_vfs_make_uri_full_from_relative (const char *base_uri, const char *relative_uri)
1811 {
1812         return gnome_vfs_uri_make_full_from_relative (base_uri, relative_uri);
1813 }
1814
1815 GnomeVFSResult
1816 _gnome_vfs_uri_resolve_all_symlinks_uri (GnomeVFSURI *uri,
1817                                          GnomeVFSURI **result_uri)
1818 {
1819         GnomeVFSURI *new_uri, *resolved_uri;
1820         GnomeVFSFileInfo *info;
1821         GnomeVFSResult res;
1822         char *p;
1823         int n_followed_symlinks;
1824
1825         /* Ref the original uri so we don't lose it */
1826         uri = gnome_vfs_uri_ref (uri);
1827
1828         *result_uri = NULL;
1829
1830         info = gnome_vfs_file_info_new ();
1831
1832         p = uri->text;
1833         n_followed_symlinks = 0;
1834         while (*p != 0) {
1835                 while (*p == GNOME_VFS_URI_PATH_CHR)
1836                         p++;
1837                 while (*p != 0 && *p != GNOME_VFS_URI_PATH_CHR)
1838                         p++;
1839
1840                 new_uri = gnome_vfs_uri_dup (uri);
1841                 g_free (new_uri->text);
1842                 new_uri->text = g_strndup (uri->text, p - uri->text);
1843                 
1844                 gnome_vfs_file_info_clear (info);
1845                 res = gnome_vfs_get_file_info_uri (new_uri, info, GNOME_VFS_FILE_INFO_DEFAULT);
1846                 if (res != GNOME_VFS_OK) {
1847                         gnome_vfs_uri_unref (new_uri);
1848                         goto out;
1849                 }
1850                 if (info->type == GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK &&
1851                     info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SYMLINK_NAME) {
1852                         n_followed_symlinks++;
1853                         if (n_followed_symlinks > MAX_SYMLINKS_FOLLOWED) {
1854                                 res = GNOME_VFS_ERROR_TOO_MANY_LINKS;
1855                                 gnome_vfs_uri_unref (new_uri);
1856                                 goto out;
1857                         }
1858                         resolved_uri = gnome_vfs_uri_resolve_relative (new_uri,
1859                                                                        info->symlink_name);
1860                         if (*p != 0) {
1861                                 gnome_vfs_uri_unref (uri);
1862                                 uri = gnome_vfs_uri_append_path (resolved_uri, p);
1863                                 gnome_vfs_uri_unref (resolved_uri);
1864                         } else {
1865                                 gnome_vfs_uri_unref (uri);
1866                                 uri = resolved_uri;
1867                         }
1868
1869                         p = uri->text;
1870                 } 
1871                 gnome_vfs_uri_unref (new_uri);
1872         }
1873
1874         res = GNOME_VFS_OK;
1875         *result_uri = gnome_vfs_uri_dup (uri);
1876  out:
1877         gnome_vfs_file_info_unref (info);
1878         gnome_vfs_uri_unref (uri);
1879         return res;
1880 }
1881
1882 GnomeVFSResult
1883 _gnome_vfs_uri_resolve_all_symlinks (const char *text_uri,
1884                                      char **resolved_text_uri)
1885 {
1886         GnomeVFSURI *uri, *resolved_uri;
1887         GnomeVFSResult res;
1888
1889         *resolved_text_uri = NULL;
1890
1891         uri = gnome_vfs_uri_new (text_uri);
1892         if (uri == NULL || uri->text == NULL) {
1893                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
1894         }
1895
1896         res = _gnome_vfs_uri_resolve_all_symlinks_uri (uri, &resolved_uri);
1897
1898         if (res == GNOME_VFS_OK) {
1899                 *resolved_text_uri = gnome_vfs_uri_to_string (resolved_uri, GNOME_VFS_URI_HIDE_NONE);
1900                 gnome_vfs_uri_unref (resolved_uri);
1901         }
1902         return res;
1903 }
1904
1905 gboolean 
1906 _gnome_vfs_uri_is_in_subdir (GnomeVFSURI *uri, GnomeVFSURI *dir)
1907 {
1908         GnomeVFSFileInfo *dirinfo, *info;
1909         GnomeVFSURI *resolved_dir, *parent, *tmp;
1910         GnomeVFSResult res;
1911         gboolean is_in_dir;
1912
1913         resolved_dir = NULL;
1914         parent = NULL;
1915
1916         is_in_dir = FALSE;
1917         
1918         dirinfo = gnome_vfs_file_info_new ();
1919         info = gnome_vfs_file_info_new ();
1920
1921         res = gnome_vfs_get_file_info_uri (dir, dirinfo, GNOME_VFS_FILE_INFO_DEFAULT);
1922         if (res != GNOME_VFS_OK || dirinfo->type != GNOME_VFS_FILE_TYPE_DIRECTORY) {
1923                 goto out;
1924         }
1925
1926         res = _gnome_vfs_uri_resolve_all_symlinks_uri (dir, &resolved_dir);
1927         if (res != GNOME_VFS_OK) {
1928                 goto out;
1929         }
1930         
1931         res = _gnome_vfs_uri_resolve_all_symlinks_uri (uri, &tmp);
1932         if (res != GNOME_VFS_OK) {
1933                 goto out;
1934         }
1935         
1936         parent = gnome_vfs_uri_get_parent (tmp);
1937         gnome_vfs_uri_unref (tmp);
1938
1939         while (parent != NULL) {
1940                 res = gnome_vfs_get_file_info_uri (parent, info, GNOME_VFS_FILE_INFO_DEFAULT);
1941                 if (res != GNOME_VFS_OK) {
1942                         break;
1943                 }
1944
1945                 if (dirinfo->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_DEVICE &&
1946                     dirinfo->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_INODE &&
1947                     info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_DEVICE &&
1948                     info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_INODE) {
1949                         if (dirinfo->device == info->device &&
1950                             dirinfo->inode == info->inode) {
1951                                 is_in_dir = TRUE;
1952                                 break;
1953                         }
1954                 } else {
1955                         if (gnome_vfs_uri_equal (dir, parent)) {
1956                                 is_in_dir = TRUE;
1957                                 break;
1958                         }
1959                 }
1960                 
1961                 tmp = gnome_vfs_uri_get_parent (parent);
1962                 gnome_vfs_uri_unref (parent);
1963                 parent = tmp;
1964         }
1965
1966  out:
1967         if (resolved_dir != NULL) {
1968                 gnome_vfs_uri_unref (resolved_dir);
1969         }
1970         if (parent != NULL) {
1971                 gnome_vfs_uri_unref (parent);
1972         }
1973         gnome_vfs_file_info_unref (info);
1974         gnome_vfs_file_info_unref (dirinfo);
1975         return is_in_dir;
1976 }
1977
1978 /**
1979  * gnome_vfs_url_show:
1980  * 
1981  * Launches the default application or component associated with the given url.
1982  *
1983  * Return value: GNOME_VFS_OK if the default action was launched,
1984  * GNOME_VFS_ERROR_BAD_PARAMETERS for an invalid or non-existant url,
1985  * GNOME_VFS_ERROR_NOT_SUPPORTED if no default action is associated with the URL.
1986  * Also error codes from gnome_vfs_mime_action_launch and
1987  * gnome_vfs_url_show_using_handler for other errors.
1988  *
1989  * Since: 2.4
1990  */
1991 GnomeVFSResult
1992 gnome_vfs_url_show (const char *url)
1993 {
1994         return gnome_vfs_url_show_with_env (url, NULL);
1995 }
1996
1997 /**
1998  * gnome_vfs_url_show_with_env:
1999  * 
2000  * Like gnome_vfs_url_show except that the default action will be launched
2001  * with the given environment.
2002  *
2003  * Return value: GNOME_VFS_OK if the default action was launched.
2004  *
2005  * Since: 2.4
2006  */
2007 GnomeVFSResult
2008 gnome_vfs_url_show_with_env (const char  *url,
2009                              char       **envp)
2010 {
2011         GnomeVFSMimeApplication *app;
2012         GnomeVFSMimeAction *action;
2013         GnomeVFSResult result;
2014         GList params;
2015         char *type;
2016         char *scheme;
2017
2018         g_return_val_if_fail (url != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
2019
2020         scheme = gnome_vfs_get_uri_scheme (url);
2021         if (scheme == NULL) {
2022                 return GNOME_VFS_ERROR_BAD_PARAMETERS;
2023         }
2024         
2025         /* check if this scheme requires special handling */
2026         if (_gnome_vfs_use_handler_for_scheme (scheme)) {
2027                 result = _gnome_vfs_url_show_using_handler_with_env (url, envp);
2028                 g_free (scheme);
2029                 return result;
2030         }
2031         
2032         g_free (scheme);
2033
2034         type = gnome_vfs_get_mime_type (url);
2035
2036         if (type == NULL) {
2037                 return GNOME_VFS_ERROR_BAD_PARAMETERS;
2038         }
2039
2040         params.data = (char *) url;
2041         params.prev = NULL;
2042         params.next = NULL;
2043         
2044         app = gnome_vfs_mime_get_default_application (type);
2045         
2046         if (app != NULL) {
2047                 result = gnome_vfs_mime_application_launch_with_env (app, &params, envp);
2048                 gnome_vfs_mime_application_free (app);
2049                 g_free (type);
2050                 return result;
2051         }
2052         
2053         action = gnome_vfs_mime_get_default_action (type);
2054         
2055         if (action != NULL) {
2056                 result = gnome_vfs_mime_action_launch_with_env (action, &params, envp);
2057                 gnome_vfs_mime_action_free (action);
2058                 g_free (type);
2059                 return result;
2060         }
2061         
2062         g_free (type);
2063         return GNOME_VFS_ERROR_NO_DEFAULT;      
2064 }         
2065