Skip to content

Commit

Permalink
Merge pull request #11584 from OSGeo/backport-11581-to-release/3.10
Browse files Browse the repository at this point in the history
[Backport release/3.10] Fix CPLFormFilename(absolute_path, ../something, NULL) to strip the relative path
  • Loading branch information
rouault authored Jan 6, 2025
2 parents ca1fbc5 + 8cfa7ed commit 197dab9
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 24 deletions.
15 changes: 15 additions & 0 deletions autotest/cpp/test_cpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,21 @@ TEST_F(test_cpl, CPLFormFilename)
EXPECT_TRUE(
EQUAL(CPLFormFilename("\\\\$\\c:", "..", nullptr), "\\\\$\\c:/..") ||
EQUAL(CPLFormFilename("\\\\$\\c:", "..", nullptr), "\\\\$\\c:\\.."));
EXPECT_STREQ(CPLFormFilename("/a", "../", nullptr), "/");
EXPECT_STREQ(CPLFormFilename("/a/", "../", nullptr), "/");
EXPECT_STREQ(CPLFormFilename("/a", "../b", nullptr), "/b");
EXPECT_STREQ(CPLFormFilename("/a/", "../b", nullptr), "/b");
EXPECT_STREQ(CPLFormFilename("/a", "../b/c", nullptr), "/b/c");
EXPECT_STREQ(CPLFormFilename("/a/", "../b/c/d", nullptr), "/b/c/d");
EXPECT_STREQ(CPLFormFilename("/a/b", "../../c", nullptr), "/c");
EXPECT_STREQ(CPLFormFilename("/a/b/", "../../c/d", nullptr), "/c/d");
EXPECT_STREQ(CPLFormFilename("/a/b", "../..", nullptr), "/");
EXPECT_STREQ(CPLFormFilename("/a/b", "../../", nullptr), "/");
EXPECT_STREQ(CPLFormFilename("/a/b/c", "../../d", nullptr), "/a/d");
EXPECT_STREQ(CPLFormFilename("/a/b/c/", "../../d", nullptr), "/a/d");
// we could also just error out, but at least this preserves the original
// semantics
EXPECT_STREQ(CPLFormFilename("/a", "../../b", nullptr), "/a/../../b");
EXPECT_STREQ(
CPLFormFilename("/vsicurl/http://example.com?foo", "bar", nullptr),
"/vsicurl/http://example.com/bar?foo");
Expand Down
81 changes: 58 additions & 23 deletions port/cpl_path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ const char *CPLResetExtension(const char *pszPath, const char *pszExt)
* CPLFormFilename(NULL,"def", NULL ) == "def"
* CPLFormFilename(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
* CPLFormFilename("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
* CPLFormFilename("/a/b/c", "../d", NULL ) == "/a/b/d" (since 3.10.1)
* \endcode
*
* @param pszPath directory path to the directory containing the file. This
Expand Down Expand Up @@ -631,33 +632,67 @@ const char *CPLFormFilename(const char *pszPath, const char *pszBasename,
pszAddedPathSep = "/";
}

if (!CPLIsFilenameRelative(pszPath) && strcmp(pszBasename, "..") == 0)
if (!CPLIsFilenameRelative(pszPath) && pszBasename[0] == '.' &&
pszBasename[1] == '.' &&
(pszBasename[2] == 0 || pszBasename[2] == '\\' ||
pszBasename[2] == '/'))
{
// /a/b + .. --> /a
// "/a/b/" + "..[/something]" --> "/a[/something]"
// "/a/b" + "..[/something]" --> "/a[/something]"
if (pszPath[nLenPath - 1] == '\\' || pszPath[nLenPath - 1] == '/')
nLenPath--;
size_t nLenPathOri = nLenPath;
while (nLenPath > 0 && pszPath[nLenPath - 1] != '\\' &&
pszPath[nLenPath - 1] != '/')
while (true)
{
nLenPath--;
}
if (nLenPath == 1 && pszPath[0] == '/')
{
pszBasename = "";
}
else if ((nLenPath > 1 && pszPath[0] == '/') ||
(nLenPath > 2 && pszPath[1] == ':') ||
(nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
{
nLenPath--;
pszBasename = "";
}
else
{
nLenPath = nLenPathOri;
if (pszAddedPathSep[0] == 0)
pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
const char *pszBasenameOri = pszBasename;
const size_t nLenPathOri = nLenPath;
while (nLenPath > 0 && pszPath[nLenPath - 1] != '\\' &&
pszPath[nLenPath - 1] != '/')
{
nLenPath--;
}
if (nLenPath == 1 && pszPath[0] == '/')
{
pszBasename += 2;
if (pszBasename[0] == '/' || pszBasename[0] == '\\')
pszBasename++;
if (*pszBasename == '.')
{
pszBasename = pszBasenameOri;
nLenPath = nLenPathOri;
if (pszAddedPathSep[0] == 0)
pszAddedPathSep =
pszPath[0] == '/'
? "/"
: VSIGetDirectorySeparator(pszPath);
}
break;
}
else if ((nLenPath > 1 && pszPath[0] == '/') ||
(nLenPath > 2 && pszPath[1] == ':') ||
(nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
{
nLenPath--;
pszBasename += 2;
if ((pszBasename[0] == '/' || pszBasename[0] == '\\') &&
pszBasename[1] == '.' && pszBasename[2] == '.')
{
pszBasename++;
}
else
{
break;
}
}
else
{
pszBasename = pszBasenameOri;
nLenPath = nLenPathOri;
if (pszAddedPathSep[0] == 0)
pszAddedPathSep = pszPath[0] == '/'
? "/"
: VSIGetDirectorySeparator(pszPath);
break;
}
}
}
else if (nLenPath > 0 && pszPath[nLenPath - 1] != '/' &&
Expand Down
2 changes: 1 addition & 1 deletion port/cpl_vsil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ char **VSISiblingFiles(const char *pszFilename)
/** Return the directory separator for the specified path.
*
* Default is forward slash. The only exception currently is the Windows
* file system which returns anti-slash, unless the specified path is of the
* file system which returns backslash, unless the specified path is of the
* form "{drive_letter}:/{rest_of_the_path}".
*
* @since 3.9
Expand Down

0 comments on commit 197dab9

Please sign in to comment.