Commit 92a22882 authored by Mark Olesen's avatar Mark Olesen
Browse files

ENH: add fileName::validate static method (issue #628)

- similar to word::validate to allow stripping of invalid characters
  without triggering a FatalError.

- use this validated fileName in Foam::readDir to avoid problems when
  a directory contains files with invalid characters in their names

- adjust rmDir to handle filenames with invalid characters

- fileName::equals() static method to compare strings while ignoring
  any differences that are solely due to duplicate slashes
parent 03532891
......@@ -3,7 +3,7 @@
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2011-2016 OpenFOAM Foundation
\\/ M anipulation | Copyright (C) 2016 OpenCFD Ltd.
\\/ M anipulation | Copyright (C) 2016-2017 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
......@@ -39,15 +39,135 @@ Description
#include "POSIX.H"
#include "Switch.H"
#include "etcFiles.H"
#include "Pair.H"
#include "Tuple2.H"
#include <fstream>
using namespace Foam;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
unsigned testStrip
(
const bool doClean,
std::initializer_list
<
Tuple2<bool, std::string>
> tests
)
{
Info<< nl << "Checking with clean=" << Switch(doClean) << nl << endl;
unsigned nFail = 0;
for (const Tuple2<bool, std::string>& test : tests)
{
const bool expected = test.first();
const std::string& input = test.second();
fileName output(fileName::validate(input, doClean));
// Check for real failure (invalid chars) vs.
// spurious failure (removed double slashes with 'doClean').
const bool same =
(
doClean
? fileName::equals(input, output)
: (input == output)
);
if (same)
{
if (expected)
{
Info<< "(pass) validated " << input << " = " << output << nl;
}
else
{
++nFail;
Info<< "(fail) unexpected success for " << input << nl;
}
}
else
{
if (expected)
{
++nFail;
Info<< "(fail) unexpected";
}
else
{
Info<< "(pass) expected";
}
Info<< " failure for " << input << nl;
}
}
return nFail;
}
unsigned testEquals
(
std::initializer_list
<
Tuple2<bool, Pair<std::string>>
> tests
)
{
Info<< nl << "Checking fileName::equals()" << nl << endl;
unsigned nFail = 0;
for (const Tuple2<bool, Pair<std::string>>& test : tests)
{
const bool expected = test.first();
const std::string& s1 = test.second().first();
const std::string& s2 = test.second().second();
const bool same = fileName::equals(s1, s2);
if (same)
{
if (expected)
{
Info<< "(pass) success";
}
else
{
++nFail;
Info<< "(fail) unexpected success";
}
}
else
{
if (expected)
{
++nFail;
Info<< "(fail) unexpected failure";
}
else
{
Info<< "(pass) expected failure";
}
}
Info<< " for " << s1 << " == " << s2 << nl;
}
return nFail;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Main program:
int main(int argc, char *argv[])
{
argList::noParallel();
argList::addBoolOption("validate", "test fileName::validate");
argList::addBoolOption("ext", "test handing of file extensions");
argList::addBoolOption("construct", "test constructors");
argList::addBoolOption("default", "reinstate default tests");
......@@ -235,6 +355,69 @@ int main(int argc, char *argv[])
Info<< nl;
}
if (args.optionFound("validate"))
{
unsigned nFail = 0;
Info<< nl << "Test fileName::validate" << nl;
// Without clean
nFail += testEquals
(
{
{ true, { "abc", "abc/" } },
{ true, { "///abc/", "//abc///" } },
{ false, { " ab //c/", "ab/c" } },
}
);
Info<< nl << "Test fileName::validate" << nl;
// Without clean
nFail += testStrip
(
false,
{
{ true, "abc/" },
{ true, "/", },
{ true, "//", },
{ true, "/abc/def", },
{ true, "/abc/def/", },
{ false, "/abc def" },
{ true, "/abc////def///", },
{ false, "/abc//// def///" },
}
);
// With clean
nFail += testStrip
(
true,
{
{ true, "abc/" },
{ true, "/" },
{ true, "//" },
{ true, "/abc/def" },
{ true, "/abc/def/" },
{ false, "/abc def" },
{ true, "/abc////def///" },
{ false, "/abc//// def///" },
}
);
Info<< nl;
if (nFail)
{
Info<< "failed " << nFail;
}
else
{
Info<< "passed all";
}
Info<< " fileName::validate tests" << nl;
}
if (!defaultTests)
{
return 0;
......@@ -312,10 +495,32 @@ int main(int argc, char *argv[])
Foam::rm(lnB);
Foam::rmDir(dirB);
Info<< nl << "=========================" << nl
<< "Test some copying and deletion" << endl;
Info<< "Creating directory " << dirA << endl;
Foam::mkDir(dirA);
Info<< "Populating with various files" << endl;
for
(
const std::string name
: { "file-1", "file-2", "bad name one", "bad name 2" }
)
{
// Full path, but without any stripping
const fileName file
(
(static_cast<const std::string&>(dirA) + "/" + name),
false
);
Info<<" create: " << file << endl;
std::ofstream os(file);
os << "file=<" << file << ">" << nl;
}
const int oldPosix = POSIX::debug;
POSIX::debug = 1;
......@@ -362,7 +567,7 @@ int main(int argc, char *argv[])
<< " but is " << lnB.type(true) << exit(FatalError);
}
// Delete
// Delete (link)
Foam::rm(lnB);
}
......@@ -379,12 +584,13 @@ int main(int argc, char *argv[])
<< " but is " << lnB.type(false) << exit(FatalError);
}
// Delete
Foam::rm(lnB);
// Delete (directory, not link)
Foam::rmDir(lnB);
}
POSIX::debug = oldPosix;
// Verify that rmDir works with bad names too
Foam::rmDir(dirA);
Foam::rm(lnA);
}
......
......@@ -51,7 +51,10 @@ template<class TYPE>
unsigned testParsing
(
TYPE (*function)(const std::string&),
const List<Tuple2<std::string, bool>>& tests
std::initializer_list
<
Tuple2<bool, std::string>
> tests
)
{
unsigned nFail = 0;
......@@ -60,10 +63,10 @@ unsigned testParsing
// Expect some failures
const bool prev = FatalIOError.throwExceptions();
for (const Tuple2<std::string, bool>& test : tests)
for (const Tuple2<bool, std::string>& test : tests)
{
const std::string& str = test.first();
const bool expected = test.second();
const bool expected = test.first();
const std::string& str = test.second();
bool parsed = true;
......@@ -124,18 +127,18 @@ int main(int argc, char *argv[])
(
&readDouble,
{
{ "", false },
{ " ", false },
{ " xxx ", false },
{ " 1234E-", false },
{ " 1234E junk", false },
{ " 3.14159 ", true },
{ " 31.4159E-1 " , true },
{ " 100E1000 " , false },
{ " 1E-40 " , true },
{ " 1E-305 " , true },
{ " 1E-37 " , true },
{ " 1E-300 " , true },
{ false, "" },
{ false, " " },
{ false, " xxx " },
{ false, " 1234E-" },
{ false, " 1234E junk" },
{ true, " 3.14159 " },
{ true, " 31.4159E-1 " },
{ false, " 100E1000 " },
{ true, " 1E-40 " },
{ true, " 1E-305 " },
{ true, " 1E-37 " },
{ true, " 1E-300 " },
}
);
}
......@@ -148,14 +151,14 @@ int main(int argc, char *argv[])
(
&readFloat,
{
{ " 3.14159 ", true },
{ " 31.4159E-1 " , true },
{ " 31.4159E200 " , false },
{ " 31.4159E20 " , true },
{ " 1E-40 " , true },
{ " 1E-305 " , true },
{ " 1E-37 " , true },
{ " 1E-300 " , true },
{ true, " 3.14159 " },
{ true, " 31.4159E-1 " },
{ false, " 31.4159E200 " },
{ true, " 31.4159E20 " },
{ true, " 1E-40 " },
{ true, " 1E-305 " },
{ true, " 1E-37 " },
{ true, " 1E-300 " },
}
);
}
......@@ -166,15 +169,15 @@ int main(int argc, char *argv[])
(
&readNasScalar,
{
{ " 3.14159 ", true },
{ " 31.4159E-1 " , true },
{ " 314.159-2 " , true },
{ " 31.4159E200 " , true },
{ " 31.4159E20 " , true },
{ " 1E-40 " , true },
{ " 1E-305 " , true },
{ " 1E-37 " , true },
{ " 1E-300 " , true },
{ true, " 3.14159 " },
{ true, " 31.4159E-1 " },
{ true, " 314.159-2 " },
{ true, " 31.4159E200 " },
{ true, " 31.4159E20 " },
{ true, " 1E-40 " },
{ true, " 1E-305 " },
{ true, " 1E-37 " },
{ true, " 1E-300 " },
}
);
}
......@@ -185,12 +188,12 @@ int main(int argc, char *argv[])
(
&readInt32,
{
{ " 3.14159 ", false },
{ " 31E1 ", false },
{ " 31.4159E-1 " , false },
{ "100" , true },
{ " 2147483644" , true },
{ " 2147483700 " , false },
{ false, " 3.14159 " },
{ false, " 31E1 " },
{ false, " 31.4159E-1 " },
{ true, "100" },
{ true, " 2147483644" },
{ false, " 2147483700 " },
}
);
}
......@@ -202,10 +205,10 @@ int main(int argc, char *argv[])
(
&readUint32,
{
{ " 2147483644" , true },
{ " 2147483700 " , true },
{ " 4294967295 " , true },
{ " 4294968000 " , false },
{ true, "\t2147483644" },
{ true, " 2147483700 " },
{ true, " 4294967295 " },
{ false, " 4294968000 " },
}
);
}
......
......@@ -109,6 +109,35 @@ static inline bool isBackupName(const Foam::fileName& name)
);
}
// Like fileName "/" global operator, but retain any invalid characters
static inline Foam::fileName fileNameConcat
(
const std::string& a,
const std::string& b
)
{
if (a.size())
{
if (b.size())
{
// Two non-empty strings: can concatenate
return Foam::fileName((a + '/' + b), false);
}
return Foam::fileName(a, false);
}
// Or, if the first string is empty
if (b.size())
{
return Foam::fileName(b, false);
}
// Both strings are empty
return Foam::fileName();
}
//! \endcond
......@@ -148,12 +177,10 @@ Foam::string Foam::getEnv(const std::string& envName)
{
return string(env);
}
else
{
// Return null-constructed string rather than string::null
// to avoid cyclic dependencies in the construction of globals
return string();
}
// Return null-constructed string rather than string::null
// to avoid cyclic dependencies in the construction of globals
return string();
}
......@@ -220,10 +247,8 @@ Foam::string Foam::userName()
{
return pw->pw_name;
}
else
{
return string();
}
return string();
}
......@@ -246,10 +271,8 @@ Foam::fileName Foam::home()
{
return pw->pw_dir;
}
else
{
return fileName();
}
return fileName();
}
......@@ -266,10 +289,8 @@ Foam::fileName Foam::home(const std::string& userName)
{
return pw->pw_dir;
}
else
{
return fileName();
}
return fileName();
}
......@@ -708,14 +729,15 @@ Foam::fileNameList Foam::readDir
const bool followLink
)
{
// Initial filename list size
// also used as increment if initial size found to be insufficient
// Initial filename list size and the increment when resizing the list
static const int maxNnames = 100;
// Basic sanity: cannot strip '.gz' from directory names
const bool stripgz = filtergz && (type != fileName::DIRECTORY);
const word extgz("gz");
fileNameList dirEntries;
// Open directory and set the structure pointer
// Do not attempt to open an empty directory name
DIR *source;
......@@ -731,12 +753,12 @@ Foam::fileNameList Foam::readDir
<< "cannot open directory " << directory << endl;
}
return fileNameList();
return dirEntries;
}
if (POSIX::debug)
{
//InfoInFunction
// InfoInFunction
Pout<< FUNCTION_NAME << " : reading directory " << directory << endl;
if ((POSIX::debug & 2) && !Pstream::master())
{
......@@ -744,22 +766,31 @@ Foam::fileNameList Foam::readDir
}
}
label nEntries = 0;
fileNameList dirEntries(maxNnames);
label nFailed = 0; // Entries with invalid characters
label nEntries = 0; // Number of selected entries
dirEntries.setSize(maxNnames);
// Read and parse all the entries in the directory
for (struct dirent *list; (list = ::readdir(source)) != nullptr; /*nil*/)
{
const fileName name(list->d_name);
const std::string item(list->d_name);
// Ignore files/directories beginning with "."
// These are the ".", ".." directories and any hidden files/dirs
if (name.empty() || name[0] == '.')
if (item.empty() || item[0] == '.')
{
continue;
}
if
// Validate filename without spaces, quotes, etc in the name.
// No duplicate slashes to strip - dirent will not have them anyhow.
const fileName name(fileName::validate(item));
if (name != item)
{
++nFailed;
}
else if
(
(type == fileName::DIRECTORY)
|| (type == fileName::FILE && !isBackupName(name))
......@@ -783,10 +814,18 @@ Foam::fileNameList Foam::readDir
}
}
}
::closedir(source);
// Reset the length of the entries list
// Finalize the length of the entries list
dirEntries.setSize(nEntries);
::closedir(source);
if (nFailed && POSIX::debug)
{
std::cerr
<< "Foam::readDir() : reading directory " << directory << nl
<< nFailed << " entries with invalid characters in their name"
<< std::endl;
}
return dirEntries;
}
......@@ -911,22 +950,22 @@ bool Foam::cp(const fileName& src, const fileName& dest, const bool followLink)
}
// Copy files
fileNameList contents = readDir(src, fileName::FILE, false, followLink);
forAll(contents, i)
fileNameList files = readDir(src, fileName::FILE, false, followLink);
for (const fileName& item : files)
{
if (POSIX::debug)
{
InfoInFunction
<< "Copying : " << src/contents[i]
<< " to " << destFile/contents[i] << endl;
<< "Copying : " << src/item
<< " to " << destFile/item << endl;
}
// File to file.
cp(src/contents[i], destFile/contents[i], followLink);
cp(src/item, destFile/item, followLink);
}
// Copy sub directories.
fileNameList subdirs = readDir
fileNameList dirs = readDir
(
src,
fileName::DIRECTORY,
......@@ -934,17 +973,17 @@ bool Foam::cp(const fileName& src, const fileName& dest, const bool followLink)
followLink
);