Commit 4e11203c authored by Alexander Larsson's avatar Alexander Larsson Committed by Alexander Larsson

New functions to convert between local pahtnames and file: uris.

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.
parent a7a76cfa
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]:
......
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]:
......
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]:
......
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]:
......
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]:
......
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]:
......
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]:
......
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]:
......
......@@ -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;
}
......@@ -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__ */
......@@ -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
......
/* 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;
}