New functions to convert between local pahtnames and file: uris.
authorAlex Larsson <alexl@redhat.com>
Sun, 26 Aug 2001 21:39:47 +0000 (21:39 +0000)
committerAlexander Larsson <alexl@src.gnome.org>
Sun, 26 Aug 2001 21:39:47 +0000 (21:39 +0000)
2001-08-26  Alex Larsson  <alexl@redhat.com>

* glib/gconvert.[ch] (g_filename_from_uri,
g_filename_to_uri): New functions to convert
between local pahtnames and file: uris.

* tests/Makefile.am:
* tests/uri-test.c:
Tests for the new functions.

12 files changed:
ChangeLog
ChangeLog.pre-2-0
ChangeLog.pre-2-10
ChangeLog.pre-2-12
ChangeLog.pre-2-2
ChangeLog.pre-2-4
ChangeLog.pre-2-6
ChangeLog.pre-2-8
glib/gconvert.c
glib/gconvert.h
tests/Makefile.am
tests/uri-test.c [new file with mode: 0644]

index 3e5cb97..ab29d02 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2001-08-26  Alex Larsson  <alexl@redhat.com>
+
+       * glib/gconvert.[ch] (g_filename_from_uri,
+       g_filename_to_uri): New functions to convert
+       between local pahtnames and file: uris.
+
+       * tests/Makefile.am:
+       * tests/uri-test.c:
+       Tests for the new functions.
+
 2001-08-25  Alexander Larsson  <alla@lysator.liu.se>
 
        * glib/gstrfuncs.[ch]:
index 3e5cb97..ab29d02 100644 (file)
@@ -1,3 +1,13 @@
+2001-08-26  Alex Larsson  <alexl@redhat.com>
+
+       * glib/gconvert.[ch] (g_filename_from_uri,
+       g_filename_to_uri): New functions to convert
+       between local pahtnames and file: uris.
+
+       * tests/Makefile.am:
+       * tests/uri-test.c:
+       Tests for the new functions.
+
 2001-08-25  Alexander Larsson  <alla@lysator.liu.se>
 
        * glib/gstrfuncs.[ch]:
index 3e5cb97..ab29d02 100644 (file)
@@ -1,3 +1,13 @@
+2001-08-26  Alex Larsson  <alexl@redhat.com>
+
+       * glib/gconvert.[ch] (g_filename_from_uri,
+       g_filename_to_uri): New functions to convert
+       between local pahtnames and file: uris.
+
+       * tests/Makefile.am:
+       * tests/uri-test.c:
+       Tests for the new functions.
+
 2001-08-25  Alexander Larsson  <alla@lysator.liu.se>
 
        * glib/gstrfuncs.[ch]:
index 3e5cb97..ab29d02 100644 (file)
@@ -1,3 +1,13 @@
+2001-08-26  Alex Larsson  <alexl@redhat.com>
+
+       * glib/gconvert.[ch] (g_filename_from_uri,
+       g_filename_to_uri): New functions to convert
+       between local pahtnames and file: uris.
+
+       * tests/Makefile.am:
+       * tests/uri-test.c:
+       Tests for the new functions.
+
 2001-08-25  Alexander Larsson  <alla@lysator.liu.se>
 
        * glib/gstrfuncs.[ch]:
index 3e5cb97..ab29d02 100644 (file)
@@ -1,3 +1,13 @@
+2001-08-26  Alex Larsson  <alexl@redhat.com>
+
+       * glib/gconvert.[ch] (g_filename_from_uri,
+       g_filename_to_uri): New functions to convert
+       between local pahtnames and file: uris.
+
+       * tests/Makefile.am:
+       * tests/uri-test.c:
+       Tests for the new functions.
+
 2001-08-25  Alexander Larsson  <alla@lysator.liu.se>
 
        * glib/gstrfuncs.[ch]:
index 3e5cb97..ab29d02 100644 (file)
@@ -1,3 +1,13 @@
+2001-08-26  Alex Larsson  <alexl@redhat.com>
+
+       * glib/gconvert.[ch] (g_filename_from_uri,
+       g_filename_to_uri): New functions to convert
+       between local pahtnames and file: uris.
+
+       * tests/Makefile.am:
+       * tests/uri-test.c:
+       Tests for the new functions.
+
 2001-08-25  Alexander Larsson  <alla@lysator.liu.se>
 
        * glib/gstrfuncs.[ch]:
index 3e5cb97..ab29d02 100644 (file)
@@ -1,3 +1,13 @@
+2001-08-26  Alex Larsson  <alexl@redhat.com>
+
+       * glib/gconvert.[ch] (g_filename_from_uri,
+       g_filename_to_uri): New functions to convert
+       between local pahtnames and file: uris.
+
+       * tests/Makefile.am:
+       * tests/uri-test.c:
+       Tests for the new functions.
+
 2001-08-25  Alexander Larsson  <alla@lysator.liu.se>
 
        * glib/gstrfuncs.[ch]:
index 3e5cb97..ab29d02 100644 (file)
@@ -1,3 +1,13 @@
+2001-08-26  Alex Larsson  <alexl@redhat.com>
+
+       * glib/gconvert.[ch] (g_filename_from_uri,
+       g_filename_to_uri): New functions to convert
+       between local pahtnames and file: uris.
+
+       * tests/Makefile.am:
+       * tests/uri-test.c:
+       Tests for the new functions.
+
 2001-08-25  Alexander Larsson  <alla@lysator.liu.se>
 
        * glib/gstrfuncs.[ch]:
index ae072e3..1246ea4 100644 (file)
@@ -563,11 +563,24 @@ static gchar *
 strdup_len (const gchar *string,
            gssize       len,
            gsize       *bytes_written,
-           gsize       *bytes_read)
+           gsize       *bytes_read,
+           GError      **error)
         
 {
   gsize real_len;
 
+  if (!g_utf8_validate (string, -1, NULL))
+    {
+      if (bytes_read)
+       *bytes_read = 0;
+      if (bytes_written)
+       *bytes_written = 0;
+
+      g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE,
+                  _("Invalid byte sequence in conversion input"));
+      return NULL;
+    }
+  
   if (len < 0)
     real_len = strlen (string);
   else
@@ -718,7 +731,7 @@ g_locale_to_utf8 (const gchar  *opsysstring,
   const char *charset;
 
   if (g_get_charset (&charset))
-    return strdup_len (opsysstring, len, bytes_read, bytes_written);
+    return strdup_len (opsysstring, len, bytes_read, bytes_written, error);
   else
     return g_convert (opsysstring, len, 
                      "UTF-8", charset, bytes_read, bytes_written, error);
@@ -864,7 +877,7 @@ g_locale_from_utf8 (const gchar *utf8string,
   const gchar *charset;
 
   if (g_get_charset (&charset))
-    return strdup_len (utf8string, len, bytes_read, bytes_written);
+    return strdup_len (utf8string, len, bytes_read, bytes_written, error);
   else
     return g_convert (utf8string, len,
                      charset, "UTF-8", bytes_read, bytes_written, error);
@@ -907,12 +920,13 @@ g_filename_to_utf8 (const gchar *opsysstring,
                           bytes_read, bytes_written,
                           error);
 #else  /* !G_PLATFORM_WIN32 */
+      
   if (getenv ("G_BROKEN_FILENAMES"))
     return g_locale_to_utf8 (opsysstring, len,
                             bytes_read, bytes_written,
                             error);
   else
-    return strdup_len (opsysstring, len, bytes_read, bytes_written);
+    return strdup_len (opsysstring, len, bytes_read, bytes_written, error);
 #endif /* !G_PLATFORM_WIN32 */
 }
 
@@ -955,6 +969,353 @@ g_filename_from_utf8 (const gchar *utf8string,
                               bytes_read, bytes_written,
                               error);
   else
-    return strdup_len (utf8string, len, bytes_read, bytes_written);
+    return strdup_len (utf8string, len, bytes_read, bytes_written, error);
 #endif /* !G_PLATFORM_WIN32 */
 }
+
+/* Test of haystack has the needle prefix, comparing case
+ * insensitive. haystack may be UTF-8, but needle must
+ * contain only ascii. */
+static gboolean
+has_case_prefix (const gchar *haystack, const gchar *needle)
+{
+  const gchar *h, *n;
+  
+  /* Eat one character at a time. */
+  h = haystack;
+  n = needle;
+
+  while (*n && *h &&
+        g_ascii_tolower (*n) == g_ascii_tolower (*h))
+    {
+      n++;
+      h++;
+    }
+  
+  return *n == '\0';
+}
+
+typedef enum {
+  UNSAFE_ALL        = 0x1,  /* Escape all unsafe characters   */
+  UNSAFE_ALLOW_PLUS = 0x2,  /* Allows '+'  */
+  UNSAFE_PATH       = 0x4,  /* Allows '/' and '?' and '&' and '='  */
+  UNSAFE_DOS_PATH   = 0x8,  /* Allows '/' and '?' and '&' and '=' and ':' */
+  UNSAFE_HOST       = 0x10, /* Allows '/' and ':' and '@' */
+  UNSAFE_SLASHES    = 0x20  /* Allows all characters except for '/' and '%' */
+} UnsafeCharacterSet;
+
+static const guchar acceptable[96] = {
+ /* X0   X1   X2   X3   X4   X5   X6   X7   X8   X9   XA   XB   XC   XD   XE   XF */
+  0x00,0x3F,0x20,0x20,0x20,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x22,0x20,0x3F,0x3F,0x1C, /* 2X  !"#$%&'()*+,-./   */
+  0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x2C, /* 3X 0123456789:;<=>?   */
+  0x30,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, /* 4X @ABCDEFGHIJKLMNO   */
+  0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F, /* 5X PQRSTUVWXYZ[\]^_   */
+  0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, /* 6X `abcdefghijklmno   */
+  0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20  /* 7X pqrstuvwxyz{|}~DEL */
+};
+
+static const gchar hex[16] = "0123456789ABCDEF";
+
+/* Note: This escape function works on file: URIs, but if you want to
+ * escape something else, please read RFC-2396 */
+static gchar *
+g_escape_uri_string (const gchar *string, 
+                    UnsafeCharacterSet mask)
+{
+#define ACCEPTABLE(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask))
+
+  const gchar *p;
+  gchar *q;
+  gchar *result;
+  int c;
+  gint unacceptable;
+  UnsafeCharacterSet use_mask;
+  
+  g_return_val_if_fail (mask == UNSAFE_ALL
+                       || mask == UNSAFE_ALLOW_PLUS
+                       || mask == UNSAFE_PATH
+                       || mask == UNSAFE_DOS_PATH
+                       || mask == UNSAFE_HOST
+                       || mask == UNSAFE_SLASHES, NULL);
+  
+  unacceptable = 0;
+  use_mask = mask;
+  for (p = string; *p != '\0'; p++)
+    {
+      c = *p;
+      if (!ACCEPTABLE (c)) 
+       unacceptable++;
+    }
+  
+  result = g_malloc (p - string + unacceptable * 2 + 1);
+  
+  use_mask = mask;
+  for (q = result, p = string; *p != '\0'; p++)
+    {
+      c = (unsigned char)*p;
+      
+      if (!ACCEPTABLE (c))
+       {
+         *q++ = '%'; /* means hex coming */
+         *q++ = hex[c >> 4];
+         *q++ = hex[c & 15];
+       }
+      else
+       *q++ = *p;
+    }
+  
+  *q = '\0';
+  
+  return result;
+}
+
+
+static gchar *
+g_escape_file_uri (const gchar *hostname,
+                  const gchar *pathname)
+{
+  char *escaped_hostname = NULL;
+  char *escaped_path;
+  char *res;
+
+  if (hostname && *hostname != '\0')
+    {
+      escaped_hostname = g_escape_uri_string (hostname, UNSAFE_HOST);
+    }
+
+  escaped_path = g_escape_uri_string (pathname, UNSAFE_DOS_PATH);
+
+  res = g_strconcat ("file://",
+                    (escaped_hostname) ? escaped_hostname : "",
+                    (*escaped_path != '/') ? "/" : "",
+                    escaped_path,
+                    NULL);
+
+  g_free (escaped_hostname);
+  g_free (escaped_path);
+  
+  return res;
+}
+
+static int
+unescape_character (const char *scanner)
+{
+  int first_digit;
+  int second_digit;
+
+  first_digit = g_ascii_xdigit_value (*scanner++);
+  
+  if (first_digit < 0) 
+    return -1;
+  
+  second_digit = g_ascii_xdigit_value (*scanner++);
+  if (second_digit < 0) 
+    return -1;
+  
+  return (first_digit << 4) | second_digit;
+}
+
+static gchar *
+g_unescape_uri_string (const gchar *escaped,
+                      const gchar *illegal_characters,
+                      int          len)
+{
+  const gchar *in, *in_end;
+  gchar *out, *result;
+  int character;
+  
+  if (escaped == NULL)
+    return NULL;
+
+  if (len < 0)
+    len = strlen (escaped);
+
+    result = g_malloc (len + 1);
+  
+  out = result;
+  for (in = escaped, in_end = escaped + len; in < in_end && *in != '\0'; in++)
+    {
+      character = *in;
+      if (character == '%')
+       {
+         character = unescape_character (in + 1);
+      
+         /* Check for an illegal character. We consider '\0' illegal here. */
+         if (character == 0
+             || (illegal_characters != NULL
+                 && strchr (illegal_characters, (char)character) != NULL))
+           {
+             g_free (result);
+             return NULL;
+           }
+         in += 2;
+       }
+      *out++ = character;
+    }
+  
+  *out = '\0';
+  
+  g_assert (out - result <= strlen (escaped));
+
+  if (!g_utf8_validate (result, -1, NULL))
+    {
+      g_free (result);
+      return NULL;
+    }
+  
+  return result;
+}
+
+/**
+ * g_filename_from_uri:
+ * @uri: a uri describing a filename (escaped, encoded in UTF-8)
+ * @hostname: Location to store hostname for the URI, or %NULL.
+ *            If there is no hostname in the URI, %NULL will be
+ *            stored in this location.
+ * @error: location to store the error occuring, or %NULL to ignore
+ *         errors. Any of the errors in #GConvertError may occur.
+ * 
+ * Converts an escaped UTF-8 encoded URI to a local filename in the
+ * encoding used for filenames. 
+ * 
+ * Return value: a newly allocated string holding the resulting
+ *               filename, or %NULL on an error.
+ **/
+gchar *
+g_filename_from_uri (const char *uri,
+                    char      **hostname,
+                    GError    **error)
+{
+  const char *path_part;
+  const char *host_part;
+  char *unescaped_hostname;
+  char *result;
+  char *filename;
+  int offs;
+
+  if (hostname)
+    *hostname = NULL;
+
+  if (!has_case_prefix (uri, "file:/"))
+    {
+      g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_NOT_LOCAL_FILE,
+                  _("The URI `%s' does not specify a local file"),
+                  uri);
+      return NULL;
+    }
+  
+  path_part = uri + strlen ("file:");
+  
+  if (strchr (path_part, '#') != NULL)
+    {
+      g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_INVALID_URI,
+                  _("The local file URI `%s' may not include a `#'"),
+                  uri);
+      return NULL;
+    }
+       
+  if (has_case_prefix (path_part, "///")) 
+    path_part += 2;
+  else if (has_case_prefix (path_part, "//"))
+    {
+      path_part += 2;
+      host_part = path_part;
+
+      path_part = strchr (path_part, '/');
+
+      if (path_part == NULL)
+       {
+         g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_INVALID_URI,
+                      _("The URI `%s' is invalid"),
+                      uri);
+         return NULL;
+       }
+
+      unescaped_hostname = g_unescape_uri_string (host_part, "", path_part - host_part);
+      if (unescaped_hostname == NULL)
+       {
+         g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_INVALID_URI,
+                      _("The hostname of the URI `%s' is contains invalidly escaped characters"),
+                      uri);
+         return NULL;
+       }
+      
+      if (hostname)
+       *hostname = unescaped_hostname;
+      else
+       g_free (unescaped_hostname);
+    }
+
+  filename = g_unescape_uri_string (path_part, "/", -1);
+
+  if (filename == NULL)
+    {
+      g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_INVALID_URI,
+                  _("The URI `%s' is contains invalidly escaped characters"),
+                  uri);
+      return NULL;
+    }
+
+  /* DOS uri's are like "file://host/c:\foo", so we need to check if we need to
+   * drop the initial slash */
+  offs = 0;
+  if (g_path_is_absolute (filename+1))
+    offs = 1;
+  
+  result = g_filename_from_utf8 (filename + offs, -1, NULL, NULL, error);
+  g_free (filename);
+  
+  return result;
+}
+
+/**
+ * g_filename_to_uri:
+ * @filename: an absolute filename specified in the encoding
+ *            used for filenames by the operating system.
+ * @hostname: A UTF-8 encoded hostname, or %NULL for none.
+ * @error: location to store the error occuring, or %NULL to ignore
+ *         errors. Any of the errors in #GConvertError may occur.
+ * 
+ * Converts an absolute filename to an escaped UTF-8 encoded URI.
+ * 
+ * Return value: a newly allocated string holding the resulting
+ *               URI, or %NULL on an error.
+ **/
+gchar *
+g_filename_to_uri   (const char *filename,
+                    char       *hostname,
+                    GError    **error)
+{
+  char *escaped_uri;
+  char *utf8_filename;
+
+  g_return_val_if_fail (filename != NULL, NULL);
+
+  if (!g_path_is_absolute (filename))
+    {
+      g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_NOT_ABSOLUTE_PATH,
+                  _("The pathname '%s' is not an absolute path"),
+                  filename);
+      return NULL;
+    }
+
+  utf8_filename = g_filename_to_utf8 (filename, -1, NULL, NULL, error);
+  if (utf8_filename == NULL)
+    return NULL;
+  
+  if (hostname &&
+      !g_utf8_validate (hostname, -1, NULL))
+    {
+      g_free (utf8_filename);
+      g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE,
+                  _("Invalid byte sequence in hostname"));
+      return NULL;
+    }
+  
+  escaped_uri = g_escape_file_uri (hostname,
+                                  utf8_filename);
+  g_free (utf8_filename);
+  
+  return escaped_uri;
+}
+
index 4f515d3..280feda 100644 (file)
@@ -37,7 +37,10 @@ typedef enum
   G_CONVERT_ERROR_NO_CONVERSION,
   G_CONVERT_ERROR_ILLEGAL_SEQUENCE,
   G_CONVERT_ERROR_FAILED,
-  G_CONVERT_ERROR_PARTIAL_INPUT
+  G_CONVERT_ERROR_PARTIAL_INPUT,
+  G_CONVERT_ERROR_NOT_LOCAL_FILE,
+  G_CONVERT_ERROR_INVALID_URI,
+  G_CONVERT_ERROR_NOT_ABSOLUTE_PATH
 } GConvertError;
 
 #define G_CONVERT_ERROR g_convert_error_quark()
@@ -107,6 +110,15 @@ gchar* g_filename_from_utf8 (const gchar  *utf8string,
                             gsize        *bytes_written,  
                             GError      **error);
 
+gchar *g_filename_from_uri (const char *uri,
+                           char      **hostname,
+                           GError    **error);
+  
+gchar *g_filename_to_uri   (const char *filename,
+                           char       *hostname,
+                           GError    **error);
+
+
 G_END_DECLS
 
 #endif /* __G_CONVERT_H__ */
index 59e96e3..d2fdf5e 100644 (file)
@@ -75,7 +75,8 @@ test_programs =                                       \
        tree-test                               \
        type-test                               \
        unicode-caseconv                        \
-       unicode-encoding
+       unicode-encoding                        \
+       uri-test
 
 test_scripts = run-markup-tests.sh
 
@@ -117,6 +118,7 @@ tree_test_LDADD = $(progs_LDADD)
 type_test_LDADD = $(progs_LDADD)
 unicode_encoding_LDADD = $(progs_LDADD)
 unicode_caseconv_LDADD = $(progs_LDADD)
+uri_test_LDADD = $(progs_LDADD)
 
 lib_LTLIBRARIES = libmoduletestplugin_a.la libmoduletestplugin_b.la
 
diff --git a/tests/uri-test.c b/tests/uri-test.c
new file mode 100644 (file)
index 0000000..87b2a62
--- /dev/null
@@ -0,0 +1,224 @@
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GLib Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GLib at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#undef G_DISABLE_ASSERT
+#undef G_LOG_DOMAIN
+
+#include <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct
+{
+  char *filename;
+  char *hostname;
+  char *expected_result;
+  GConvertError expected_error; /* If failed */
+}  ToUriTest;
+
+ToUriTest
+to_uri_tests[] = {
+  { "/etc", NULL, "file:///etc"},
+  { "/etc", "", "file:///etc"},
+  { "/etc", "localhost", "file://localhost/etc"},
+#ifdef G_OS_WIN32
+  { "c:\\windows", NULL, "file:///c:\\windows"},
+  { "c:\\windows", "localhost", "file://localhost/c:\\windows"},
+#endif
+  { "etc", "localhost", NULL, G_CONVERT_ERROR_NOT_ABSOLUTE_PATH},
+  { "/etc/öäå", NULL, NULL, G_CONVERT_ERROR_ILLEGAL_SEQUENCE},
+  { "/etc/öäå", NULL, "file:///etc/%C3%B6%C3%A4%C3%A5"},
+  { "/etc", "öäå", "file://%C3%B6%C3%A4%C3%A5/etc"},
+  { "/etc", "åäö", NULL, G_CONVERT_ERROR_ILLEGAL_SEQUENCE},
+  { "/etc/file with #%", NULL, "file:///etc/file%20with%20%23%25"},
+};
+
+
+typedef struct
+{
+  char *uri;
+  char *expected_filename;
+  char *expected_hostname;
+  GConvertError expected_error; /* If failed */
+}  FromUriTest;
+
+FromUriTest
+from_uri_tests[] = {
+  { "file:///etc", "/etc", NULL},
+  { "file:/etc", "/etc", NULL},
+  { "file://localhost/etc", "/etc", "localhost", },
+  { "file://localhost/etc/%23%25%20file", "/etc/#% file", "localhost", },
+  { "file://%C3%B6%C3%A4%C3%A5/etc", "/etc", "öäå", },
+  { "file:////etc/%C3%B6%C3%C3%C3%A5", NULL, NULL, G_CONVERT_ERROR_INVALID_URI},
+  { "file://localhost/åäö", NULL, NULL, G_CONVERT_ERROR_INVALID_URI},
+  { "file://åäö/etc", NULL, NULL, G_CONVERT_ERROR_INVALID_URI},
+  { "file:///some/file#bad", NULL, NULL, G_CONVERT_ERROR_INVALID_URI},
+  { "file://some", NULL, NULL, G_CONVERT_ERROR_INVALID_URI},
+};
+
+
+static gboolean any_failed = FALSE;
+
+static void
+run_to_uri_tests (void)
+{
+  int i;
+  gchar *res;
+  GError *error;
+  
+  for (i = 0; i < G_N_ELEMENTS (to_uri_tests); i++)
+    {
+      error = NULL;
+      res = g_filename_to_uri (to_uri_tests[i].filename,
+                              to_uri_tests[i].hostname,
+                              &error);
+
+      if (to_uri_tests[i].expected_result == NULL)
+       {
+         if (res != NULL)
+           {
+             g_print ("\ng_filename_to_uri() test %d failed, expected to return NULL, actual result: %s\n", i, res);
+             any_failed = TRUE;
+           }
+         else
+           {
+             if (error == NULL)
+               {
+                 g_print ("\ng_filename_to_uri() test %d failed, returned NULL, but didn't set error\n", i);
+                 any_failed = TRUE;
+               }
+             else if (error->domain != G_CONVERT_ERROR)
+               {
+                 g_print ("\ng_filename_to_uri() test %d failed, returned NULL, set non G_CONVERT_ERROR error\n", i);
+                 any_failed = TRUE;
+               }
+             else if (error->code != to_uri_tests[i].expected_error)
+               {
+                 g_print ("\ng_filename_to_uri() test %d failed as expected, but set wrong errorcode %d instead of expected %d \n",
+                          i, error->code, to_uri_tests[i].expected_error);
+                 any_failed = TRUE;
+               }
+           }
+       }
+      else if (res == NULL || strcmp (res, to_uri_tests[i].expected_result) != 0)
+       {
+         g_print ("\ng_filename_to_uri() test %d failed, expected result: %s, actual result: %s\n",
+                  i, to_uri_tests[i].expected_result, (res) ? res : "NULL");
+         if (error)
+           g_print ("Error message: %s\n", error->message);
+         any_failed = TRUE;
+       }
+      
+      /* Give some output */
+      g_print (".");
+    }
+}
+
+static void
+run_from_uri_tests (void)
+{
+  int i;
+  gchar *res;
+  gchar *hostname;
+  GError *error;
+  
+  for (i = 0; i < G_N_ELEMENTS (from_uri_tests); i++)
+    {
+      error = NULL;
+      res = g_filename_from_uri (from_uri_tests[i].uri,
+                                &hostname,
+                                &error);
+
+      if (from_uri_tests[i].expected_filename == NULL)
+       {
+         if (res != NULL)
+           {
+             g_print ("\ng_filename_from_uri() test %d failed, expected to return NULL, actual result: %s\n", i, res);
+             any_failed = TRUE;
+           }
+         else
+           {
+             if (error == NULL)
+               {
+                 g_print ("\ng_filename_from_uri() test %d failed, returned NULL, but didn't set error\n", i);
+                 any_failed = TRUE;
+               }
+             else if (error->domain != G_CONVERT_ERROR)
+               {
+                 g_print ("\ng_filename_from_uri() test %d failed, returned NULL, set non G_CONVERT_ERROR error\n", i);
+                 any_failed = TRUE;
+               }
+             else if (error->code != from_uri_tests[i].expected_error)
+               {
+                 g_print ("\ng_filename_from_uri() test %d failed as expected, but set wrong errorcode %d instead of expected %d \n",
+                          i, error->code, from_uri_tests[i].expected_error);
+                 any_failed = TRUE;
+               }
+           }
+       }
+      else
+       {
+         if (res == NULL || strcmp (res, from_uri_tests[i].expected_filename) != 0)
+           {
+             g_print ("\ng_filename_from_uri() test %d failed, expected result: %s, actual result: %s\n",
+                      i, from_uri_tests[i].expected_filename, (res) ? res : "NULL");
+             any_failed = TRUE;
+           }
+
+         if (from_uri_tests[i].expected_hostname == NULL)
+           {
+             if (hostname != NULL)
+               {
+                 g_print ("\ng_filename_from_uri() test %d failed, expected no hostname, got: %s\n",
+                          i, hostname);
+                 any_failed = TRUE;
+               }
+           }
+         else if (hostname == NULL ||
+                  strcmp (hostname, from_uri_tests[i].expected_hostname) != 0)
+           {
+             g_print ("\ng_filename_from_uri() test %d failed, expected hostname: %s, actual result: %s\n",
+                      i, from_uri_tests[i].expected_hostname, (hostname) ? hostname : "NULL");
+             any_failed = TRUE;
+           }
+       }
+      
+      /* Give some output */
+      g_print (".");
+    }
+  g_print ("\n");
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  run_to_uri_tests ();
+  run_from_uri_tests ();
+
+  return any_failed ? 1 : 0;
+}