/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* gnome-vfs-utils.c - Utility gnome-vfs methods. Will use gnome-vfs HEAD in time. Copyright (C) 1999 Free Software Foundation Copyright (C) 2000, 2001 Eazel, Inc. The Gnome Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The Gnome Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the Gnome Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Authors: Ettore Perazzoli John Sullivan Darin Adler */ #include #include "egg-recent-vfs-utils.h" #include #include #include #ifdef ENABLE_NLS #include #include #define _(String) gettext(String) #ifdef gettext_noop #define N_(String) gettext_noop(String) #else #define N_(String) (String) #endif #else /* NLS is disabled */ #define _(String) (String) #define N_(String) (String) #define textdomain(String) (String) #define gettext(String) (String) #define dgettext(Domain,String) (String) #define dcgettext(Domain,String,Type) (String) #define bindtextdomain(Domain,Directory) (Domain) #endif static char * make_valid_utf8 (const char *name) { GString *string; const char *remainder, *invalid; int remaining_bytes, valid_bytes; string = NULL; remainder = name; remaining_bytes = strlen (name); while (remaining_bytes != 0) { if (g_utf8_validate (remainder, remaining_bytes, &invalid)) { break; } valid_bytes = invalid - remainder; if (string == NULL) { string = g_string_sized_new (remaining_bytes); } g_string_append_len (string, remainder, valid_bytes); g_string_append_c (string, '?'); remaining_bytes -= valid_bytes + 1; remainder = invalid + 1; } if (string == NULL) { return g_strdup (name); } g_string_append (string, remainder); g_string_append (string, _(" (invalid Unicode)")); g_assert (g_utf8_validate (string->str, -1, NULL)); return g_string_free (string, FALSE); } static gboolean istr_has_prefix (const char *haystack, const char *needle) { const char *h, *n; char hc, nc; /* Eat one character at a time. */ h = haystack == NULL ? "" : haystack; n = needle == NULL ? "" : needle; do { if (*n == '\0') { return TRUE; } if (*h == '\0') { return FALSE; } hc = *h++; nc = *n++; hc = g_ascii_tolower (hc); nc = g_ascii_tolower (nc); } while (hc == nc); return FALSE; } static gboolean str_has_prefix (const char *haystack, const char *needle) { const char *h, *n; /* Eat one character at a time. */ h = haystack == NULL ? "" : haystack; n = needle == NULL ? "" : needle; do { if (*n == '\0') { return TRUE; } if (*h == '\0') { return FALSE; } } while (*h++ == *n++); return FALSE; } static gboolean uri_is_local_scheme (const char *uri) { gboolean is_local_scheme; char *temp_scheme; int i; char *local_schemes[] = {"file:", "help:", "ghelp:", "gnome-help:", "trash:", "man:", "info:", "hardware:", "search:", "pipe:", "gnome-trash:", NULL}; is_local_scheme = FALSE; for (temp_scheme = *local_schemes, i = 0; temp_scheme != NULL; i++, temp_scheme = local_schemes[i]) { is_local_scheme = istr_has_prefix (uri, temp_scheme); if (is_local_scheme) { break; } } return is_local_scheme; } static char * handle_trailing_slashes (const char *uri) { char *temp, *uri_copy; gboolean previous_char_is_column, previous_chars_are_slashes_without_column; gboolean previous_chars_are_slashes_with_column; gboolean is_local_scheme; g_assert (uri != NULL); uri_copy = g_strdup (uri); if (strlen (uri_copy) <= 2) { return uri_copy; } is_local_scheme = uri_is_local_scheme (uri); previous_char_is_column = FALSE; previous_chars_are_slashes_without_column = FALSE; previous_chars_are_slashes_with_column = FALSE; /* remove multiple trailing slashes */ for (temp = uri_copy; *temp != '\0'; temp++) { if (*temp == '/' && !previous_char_is_column) { previous_chars_are_slashes_without_column = TRUE; } else if (*temp == '/' && previous_char_is_column) { previous_chars_are_slashes_without_column = FALSE; previous_char_is_column = TRUE; previous_chars_are_slashes_with_column = TRUE; } else { previous_chars_are_slashes_without_column = FALSE; previous_char_is_column = FALSE; previous_chars_are_slashes_with_column = FALSE; } if (*temp == ':') { previous_char_is_column = TRUE; } } if (*temp == '\0' && previous_chars_are_slashes_without_column) { if (is_local_scheme) { /* go back till you remove them all. */ for (temp--; *(temp) == '/'; temp--) { *temp = '\0'; } } else { /* go back till you remove them all but one. */ for (temp--; *(temp - 1) == '/'; temp--) { *temp = '\0'; } } } if (*temp == '\0' && previous_chars_are_slashes_with_column) { /* go back till you remove them all but three. */ for (temp--; *(temp - 3) != ':' && *(temp - 2) != ':' && *(temp - 1) != ':'; temp--) { *temp = '\0'; } } return uri_copy; } static char * make_uri_canonical (const char *uri) { char *canonical_uri, *old_uri, *p; gboolean relative_uri; relative_uri = FALSE; if (uri == NULL) { return NULL; } /* FIXME bugzilla.eazel.com 648: * This currently ignores the issue of two uris that are not identical but point * to the same data except for the specific cases of trailing '/' characters, * file:/ and file:///, and "lack of file:". */ canonical_uri = handle_trailing_slashes (uri); /* Note: In some cases, a trailing slash means nothing, and can * be considered equivalent to no trailing slash. But this is * not true in every case; specifically not for web addresses passed * to a web-browser. So we don't have the trailing-slash-equivalence * logic here, but we do use that logic in EelDirectory where * the rules are more strict. */ /* Add file: if there is no scheme. */ if (strchr (canonical_uri, ':') == NULL) { old_uri = canonical_uri; if (old_uri[0] != '/') { /* FIXME bugzilla.eazel.com 5069: * bandaid alert. Is this really the right thing to do? * * We got what really is a relative path. We do a little bit of * a stretch here and assume it was meant to be a cryptic absolute path, * and convert it to one. Since we can't call gnome_vfs_uri_new and * gnome_vfs_uri_to_string to do the right make-canonical conversion, * we have to do it ourselves. */ relative_uri = TRUE; canonical_uri = gnome_vfs_make_path_name_canonical (old_uri); g_free (old_uri); old_uri = canonical_uri; canonical_uri = g_strconcat ("file:///", old_uri, NULL); } else { canonical_uri = g_strconcat ("file:", old_uri, NULL); } g_free (old_uri); } /* Lower-case the scheme. */ for (p = canonical_uri; *p != ':'; p++) { g_assert (*p != '\0'); *p = g_ascii_tolower (*p); } if (!relative_uri) { old_uri = canonical_uri; canonical_uri = gnome_vfs_make_uri_canonical (canonical_uri); if (canonical_uri != NULL) { g_free (old_uri); } else { canonical_uri = old_uri; } } /* FIXME bugzilla.eazel.com 2802: * Work around gnome-vfs's desire to convert file:foo into file://foo * by converting to file:///foo here. When you remove this, check that * typing "foo" into location bar does not crash and returns an error * rather than displaying the contents of / */ if (str_has_prefix (canonical_uri, "file://") && !str_has_prefix (canonical_uri, "file:///")) { old_uri = canonical_uri; canonical_uri = g_strconcat ("file:/", old_uri + 5, NULL); g_free (old_uri); } return canonical_uri; } static char * format_uri_for_display (const char *uri, gboolean filenames_are_locale_encoded) { char *canonical_uri, *path, *utf8_path; g_return_val_if_fail (uri != NULL, g_strdup ("")); canonical_uri = make_uri_canonical (uri); /* If there's no fragment and it's a local path. */ path = gnome_vfs_get_local_path_from_uri (canonical_uri); if (path != NULL) { if (filenames_are_locale_encoded) { utf8_path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL); if (utf8_path) { g_free (canonical_uri); g_free (path); return utf8_path; } } else if (g_utf8_validate (path, -1, NULL)) { g_free (canonical_uri); return path; } } if (canonical_uri && !g_utf8_validate (canonical_uri, -1, NULL)) { utf8_path = make_valid_utf8 (canonical_uri); g_free (canonical_uri); canonical_uri = utf8_path; } g_free (path); return canonical_uri; } char * egg_recent_vfs_format_uri_for_display (const char *uri) { static gboolean broken_filenames; broken_filenames = g_getenv ("G_BROKEN_FILENAMES") != NULL; return format_uri_for_display (uri, broken_filenames); } static gboolean is_valid_scheme_character (char c) { return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.'; } static gboolean has_valid_scheme (const char *uri) { const char *p; p = uri; if (!is_valid_scheme_character (*p)) { return FALSE; } do { p++; } while (is_valid_scheme_character (*p)); return *p == ':'; } static char * escape_high_chars (const guchar *string) { char *result; const guchar *scanner; guchar *result_scanner; int escape_count; static const gchar hex[16] = "0123456789ABCDEF"; #define ACCEPTABLE(a) ((a)>=32 && (a)<128) escape_count = 0; if (string == NULL) { return NULL; } for (scanner = string; *scanner != '\0'; scanner++) { if (!ACCEPTABLE(*scanner)) { escape_count++; } } if (escape_count == 0) { return g_strdup (string); } /* allocate two extra characters for every character that * needs escaping and space for a trailing zero */ result = g_malloc (scanner - string + escape_count * 2 + 1); for (scanner = string, result_scanner = result; *scanner != '\0'; scanner++) { if (!ACCEPTABLE(*scanner)) { *result_scanner++ = '%'; *result_scanner++ = hex[*scanner >> 4]; *result_scanner++ = hex[*scanner & 15]; } else { *result_scanner++ = *scanner; } } *result_scanner = '\0'; return result; } static char * make_uri_from_input_internal (const char *text, gboolean filenames_are_locale_encoded, gboolean strip_trailing_whitespace) { char *stripped, *path, *uri, *locale_path, *filesystem_path, *escaped; g_return_val_if_fail (text != NULL, g_strdup ("")); /* Strip off leading whitespaces (since they can't be part of a valid uri). Only strip off trailing whitespaces when requested since they might be part of a valid uri. */ if (strip_trailing_whitespace) { stripped = g_strstrip (g_strdup (text)); } else { stripped = g_strchug (g_strdup (text)); } switch (stripped[0]) { case '\0': uri = g_strdup (""); break; case '/': if (filenames_are_locale_encoded) { GError *error = NULL; locale_path = g_locale_from_utf8 (stripped, -1, NULL, NULL, &error); if (locale_path != NULL) { uri = gnome_vfs_get_uri_from_local_path (locale_path); g_free (locale_path); } else { /* We couldn't convert to the locale. */ /* FIXME: We should probably give a user-visible error here. */ uri = g_strdup(""); } } else { uri = gnome_vfs_get_uri_from_local_path (stripped); } break; case '~': if (filenames_are_locale_encoded) { filesystem_path = g_locale_from_utf8 (stripped, -1, NULL, NULL, NULL); } else { filesystem_path = g_strdup (stripped); } /* deliberately falling into default case on fail */ if (filesystem_path != NULL) { path = gnome_vfs_expand_initial_tilde (filesystem_path); g_free (filesystem_path); if (*path == '/') { uri = gnome_vfs_get_uri_from_local_path (path); g_free (path); break; } g_free (path); } /* don't insert break here, read above comment */ default: if (has_valid_scheme (stripped)) { uri = escape_high_chars (stripped); } else { escaped = escape_high_chars (stripped); uri = g_strconcat ("http://", escaped, NULL); g_free (escaped); } } g_free (stripped); return uri; } char * egg_recent_vfs_make_uri_from_input (const char *uri) { static gboolean broken_filenames; broken_filenames = g_getenv ("G_BROKEN_FILENAMES") != NULL; return make_uri_from_input_internal (uri, broken_filenames, TRUE); } static char * make_uri_canonical_strip_fragment (const char *uri) { const char *fragment; char *without_fragment, *canonical; fragment = strchr (uri, '#'); if (fragment == NULL) { return make_uri_canonical (uri); } without_fragment = g_strndup (uri, fragment - uri); canonical = make_uri_canonical (without_fragment); g_free (without_fragment); return canonical; } static gboolean uris_match (const char *uri_1, const char *uri_2, gboolean ignore_fragments) { char *canonical_1, *canonical_2; gboolean result; if (ignore_fragments) { canonical_1 = make_uri_canonical_strip_fragment (uri_1); canonical_2 = make_uri_canonical_strip_fragment (uri_2); } else { canonical_1 = make_uri_canonical (uri_1); canonical_2 = make_uri_canonical (uri_2); } result = strcmp (canonical_1, canonical_2) == 0; g_free (canonical_1); g_free (canonical_2); return result; } gboolean egg_recent_vfs_uris_match (const char *uri_1, const char *uri_2) { return uris_match (uri_1, uri_2, FALSE); } char * egg_recent_vfs_get_uri_scheme (const char *uri) { char *colon; g_return_val_if_fail (uri != NULL, NULL); colon = strchr (uri, ':'); if (colon == NULL) { return NULL; } return g_strndup (uri, colon - uri); }