(mc_realpath): ignore path encoding.

Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
This commit is contained in:
Andrew Borodin 2017-12-30 15:29:27 +03:00
parent 074008025b
commit a38accd389
4 changed files with 284 additions and 113 deletions

View File

@ -214,11 +214,7 @@ char *tilde_expand (const char *);
void custom_canonicalize_pathname (char *, CANON_PATH_FLAGS);
void canonicalize_pathname (char *);
#ifdef HAVE_REALPATH
#define mc_realpath realpath
#else
char *mc_realpath (const char *path, char *resolved_path);
#endif
/* Looks for "magic" bytes at the start of the VFS file to guess the
* compression type. Side effect: modifies the file position. */

View File

@ -1093,150 +1093,179 @@ gettimeofday (struct timeval *tp, void *tzp)
/* --------------------------------------------------------------------------------------------- */
#ifndef HAVE_REALPATH
char *
mc_realpath (const char *path, char *resolved_path)
{
char copy_path[PATH_MAX];
char got_path[PATH_MAX];
char *new_path = got_path;
char *max_path;
#ifdef HAVE_CHARSET
const char *p = path;
gboolean absolute_path = FALSE;
if (IS_PATH_SEP (*p))
{
absolute_path = TRUE;
p++;
}
/* ignore encoding: skip "#enc:" */
if (g_str_has_prefix (p, VFS_ENCODING_PREFIX))
{
p += strlen (VFS_ENCODING_PREFIX);
p = strchr (p, PATH_SEP);
if (p != NULL)
{
if (!absolute_path && p[1] != '\0')
p++;
path = p;
}
}
#endif /* HAVE_CHARSET */
#ifdef HAVE_REALPATH
return realpath (path, resolved_path);
#else
{
char copy_path[PATH_MAX];
char got_path[PATH_MAX];
char *new_path = got_path;
char *max_path;
#ifdef S_IFLNK
char link_path[PATH_MAX];
int readlinks = 0;
int n;
char link_path[PATH_MAX];
int readlinks = 0;
int n;
#endif /* S_IFLNK */
/* Make a copy of the source path since we may need to modify it. */
if (strlen (path) >= PATH_MAX - 2)
{
errno = ENAMETOOLONG;
return NULL;
}
strcpy (copy_path, path);
path = copy_path;
max_path = copy_path + PATH_MAX - 2;
/* If it's a relative pathname use getwd for starters. */
if (!IS_PATH_SEP (*path))
{
new_path = g_get_current_dir ();
if (new_path == NULL)
/* Make a copy of the source path since we may need to modify it. */
if (strlen (path) >= PATH_MAX - 2)
{
strcpy (got_path, "");
errno = ENAMETOOLONG;
return NULL;
}
strcpy (copy_path, path);
path = copy_path;
max_path = copy_path + PATH_MAX - 2;
/* If it's a relative pathname use getwd for starters. */
if (!IS_PATH_SEP (*path))
{
new_path = g_get_current_dir ();
if (new_path == NULL)
{
strcpy (got_path, "");
}
else
{
g_snprintf (got_path, sizeof (got_path), "%s", new_path);
g_free (new_path);
new_path = got_path;
}
new_path += strlen (got_path);
if (!IS_PATH_SEP (new_path[-1]))
*new_path++ = PATH_SEP;
}
else
{
g_snprintf (got_path, sizeof (got_path), "%s", new_path);
g_free (new_path);
new_path = got_path;
}
new_path += strlen (got_path);
if (!IS_PATH_SEP (new_path[-1]))
*new_path++ = PATH_SEP;
}
else
{
*new_path++ = PATH_SEP;
path++;
}
/* Expand each slash-separated pathname component. */
while (*path != '\0')
{
/* Ignore stray "/". */
if (IS_PATH_SEP (*path))
{
path++;
continue;
}
if (*path == '.')
/* Expand each slash-separated pathname component. */
while (*path != '\0')
{
/* Ignore ".". */
if (path[1] == '\0' || IS_PATH_SEP (path[1]))
/* Ignore stray "/". */
if (IS_PATH_SEP (*path))
{
path++;
continue;
}
if (path[1] == '.')
if (*path == '.')
{
if (path[2] == '\0' || IS_PATH_SEP (path[2]))
/* Ignore ".". */
if (path[1] == '\0' || IS_PATH_SEP (path[1]))
{
path += 2;
/* Ignore ".." at root. */
if (new_path == got_path + 1)
continue;
/* Handle ".." by backing up. */
while (!IS_PATH_SEP ((--new_path)[-1]))
;
path++;
continue;
}
if (path[1] == '.')
{
if (path[2] == '\0' || IS_PATH_SEP (path[2]))
{
path += 2;
/* Ignore ".." at root. */
if (new_path == got_path + 1)
continue;
/* Handle ".." by backing up. */
while (!IS_PATH_SEP ((--new_path)[-1]))
;
continue;
}
}
}
}
/* Safely copy the next pathname component. */
while (*path != '\0' && !IS_PATH_SEP (*path))
{
if (path > max_path)
/* Safely copy the next pathname component. */
while (*path != '\0' && !IS_PATH_SEP (*path))
{
errno = ENAMETOOLONG;
return NULL;
if (path > max_path)
{
errno = ENAMETOOLONG;
return NULL;
}
*new_path++ = *path++;
}
*new_path++ = *path++;
}
#ifdef S_IFLNK
/* Protect against infinite loops. */
if (readlinks++ > MAXSYMLINKS)
{
errno = ELOOP;
return NULL;
}
/* See if latest pathname component is a symlink. */
*new_path = '\0';
n = readlink (got_path, link_path, PATH_MAX - 1);
if (n < 0)
{
/* EINVAL means the file exists but isn't a symlink. */
if (errno != EINVAL)
/* Protect against infinite loops. */
if (readlinks++ > MAXSYMLINKS)
{
/* Make sure it's null terminated. */
*new_path = '\0';
strcpy (resolved_path, got_path);
errno = ELOOP;
return NULL;
}
}
else
{
/* Note: readlink doesn't add the null byte. */
link_path[n] = '\0';
if (IS_PATH_SEP (*link_path))
/* Start over for an absolute symlink. */
new_path = got_path;
/* See if latest pathname component is a symlink. */
*new_path = '\0';
n = readlink (got_path, link_path, PATH_MAX - 1);
if (n < 0)
{
/* EINVAL means the file exists but isn't a symlink. */
if (errno != EINVAL)
{
/* Make sure it's null terminated. */
*new_path = '\0';
strcpy (resolved_path, got_path);
return NULL;
}
}
else
/* Otherwise back up over this component. */
while (!IS_PATH_SEP (*(--new_path)))
;
/* Safe sex check. */
if (strlen (path) + n >= PATH_MAX - 2)
{
errno = ENAMETOOLONG;
return NULL;
/* Note: readlink doesn't add the null byte. */
link_path[n] = '\0';
if (IS_PATH_SEP (*link_path))
/* Start over for an absolute symlink. */
new_path = got_path;
else
/* Otherwise back up over this component. */
while (!IS_PATH_SEP (*(--new_path)))
;
/* Safe sex check. */
if (strlen (path) + n >= PATH_MAX - 2)
{
errno = ENAMETOOLONG;
return NULL;
}
/* Insert symlink contents into path. */
strcat (link_path, path);
strcpy (copy_path, link_path);
path = copy_path;
}
/* Insert symlink contents into path. */
strcat (link_path, path);
strcpy (copy_path, link_path);
path = copy_path;
}
#endif /* S_IFLNK */
*new_path++ = PATH_SEP;
*new_path++ = PATH_SEP;
}
/* Delete trailing slash but don't whomp a lone slash. */
if (new_path != got_path + 1 && IS_PATH_SEP (new_path[-1]))
new_path--;
/* Make sure it's null terminated. */
*new_path = '\0';
strcpy (resolved_path, got_path);
return resolved_path;
}
/* Delete trailing slash but don't whomp a lone slash. */
if (new_path != got_path + 1 && IS_PATH_SEP (new_path[-1]))
new_path--;
/* Make sure it's null terminated. */
*new_path = '\0';
strcpy (resolved_path, got_path);
return resolved_path;
}
#endif /* HAVE_REALPATH */
}
/* --------------------------------------------------------------------------------------------- */
/**

View File

@ -25,6 +25,10 @@ TESTS = \
utilunix__my_system_fork_child \
x_basename
if CHARSET
TESTS += mc_realpath
endif
check_PROGRAMS = $(TESTS)
library_independ_SOURCES = \
@ -33,6 +37,9 @@ library_independ_SOURCES = \
mc_build_filename_SOURCES = \
mc_build_filename.c
mc_realpath_SOURCES = \
mc_realpath.c
name_quote_SOURCES = \
name_quote.c

139
tests/lib/mc_realpath.c Normal file
View File

@ -0,0 +1,139 @@
/*
lib - realpath
Copyright (C) 2011-2017
Free Software Foundation, Inc.
Written by:
Andrew Borodin <aborodin@vmail.ru>, 2017
This file is part of the Midnight Commander.
The Midnight Commander is free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
The Midnight Commander 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define TEST_SUITE_NAME "/lib/util"
#include "tests/mctest.h"
#include "lib/strutil.h"
#include "lib/vfs/vfs.h" /* VFS_ENCODING_PREFIX, vfs_init(), vfs_shut() */
#include "src/vfs/local/local.c"
#include "lib/util.h" /* mc_realpath() */
/* --------------------------------------------------------------------------------------------- */
static char resolved_path[PATH_MAX];
/* --------------------------------------------------------------------------------------------- */
/* @Before */
static void
setup (void)
{
str_init_strings (NULL);
vfs_init ();
init_localfs ();
vfs_setup_work_dir ();
}
/* @After */
static void
teardown (void)
{
vfs_shut ();
str_uninit_strings ();
}
/* --------------------------------------------------------------------------------------------- */
/* @DataSource("data_source") */
/* *INDENT-OFF* */
static const struct data_source
{
const char *input_string;
const char *expected_string;
} data_source[] =
{
/* absolute paths */
{ "/", "/"},
{ "/" VFS_ENCODING_PREFIX "UTF-8/", "/" },
{ "/usr/bin", "/usr/bin" },
{ "/" VFS_ENCODING_PREFIX "UTF-8/usr/bin", "/usr/bin" },
/* relative paths are relative to / */
{ VFS_ENCODING_PREFIX "UTF-8/", "/" },
{ "usr/bin", "/usr/bin" },
{ VFS_ENCODING_PREFIX "UTF-8/usr/bin", "/usr/bin" }
};
/* *INDENT-ON* */
/* @Test(dataSource = "data_source") */
/* *INDENT-OFF* */
START_PARAMETRIZED_TEST (realpath_test, data_source)
/* *INDENT-ON* */
{
int ret;
/* realpath(3) produces a canonicalized absolute pathname using curent directory.
* Change the current directory to produce correct pathname. */
ret = chdir ("/");
/* when */
if (mc_realpath (data->input_string, resolved_path) == NULL)
resolved_path[0] = '\0';
/* then */
mctest_assert_str_eq (resolved_path, data->expected_string);
(void) ret;
}
/* *INDENT-OFF* */
END_PARAMETRIZED_TEST
/* *INDENT-ON* */
/* --------------------------------------------------------------------------------------------- */
int
main (void)
{
int number_failed;
char *cwd, *logname;
Suite *s = suite_create (TEST_SUITE_NAME);
TCase *tc_core = tcase_create ("Core");
SRunner *sr;
cwd = g_get_current_dir ();
logname = g_strconcat (cwd, "realpath.log", (char *) NULL);
g_free (cwd);
tcase_add_checked_fixture (tc_core, setup, teardown);
/* Add new tests here: *************** */
mctest_add_parameterized_test (tc_core, realpath_test, data_source);
/* *********************************** */
suite_add_tcase (s, tc_core);
sr = srunner_create (s);
srunner_set_log (sr, logname);
srunner_run_all (sr, CK_ENV);
number_failed = srunner_ntests_failed (sr);
srunner_free (sr);
g_free (logname);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
/* --------------------------------------------------------------------------------------------- */