Merge pull request #11688 from ethereum/fix-and-document-allow-paths

Fix and document `--allow-paths`
This commit is contained in:
chriseth 2021-09-27 15:39:14 +02:00 committed by GitHub
commit 39eb182ccc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 963 additions and 224 deletions

View File

@ -21,6 +21,7 @@ Bugfixes:
* Code Generator: Fix ICE on assigning to calldata structs and statically-sized calldata arrays in inline assembly.
* Code Generator: Use stable source order for ABI functions.
* Commandline Interface: Disallow the ``--experimental-via-ir`` option in Standard JSON, Assembler and Linker modes.
* Commandline Interface: Fix resolution of paths whitelisted with ``--allowed-paths`` or implicitly due to base path, remappings and files being compiled. Correctly handle paths that do not match imports exactly due to being relative, non-normalized or empty.
* Commandline Interface: Report optimizer options as invalid in Standard JSON and linker modes instead of ignoring them.
* Name Resolver: Fix that when importing an aliased symbol using ``import {AliasedName} from "a.sol"`` it would use the original name of the symbol and not the aliased one.
* Opcode Optimizer: Prevent the optimizer from running multiple times to avoid potential bytecode differences for referenced code.

View File

@ -297,7 +297,7 @@ Here are some examples of what you can expect if they are not:
The same effect can be achieved in a more reliable way by using direct imports with
:ref:`base path <base-path>` and :ref:`import remapping <import-remapping>`.
.. index:: ! base path, --base-path
.. index:: ! base path, ! --base-path
.. _base-path:
Base Path
@ -373,6 +373,72 @@ The resulting file path becomes the source unit name.
When working with older versions of the compiler it is recommended to invoke the compiler from
the base path and to only use relative paths on the command line.
.. index:: ! allowed paths, ! --allow-paths, remapping; target
.. _allowed-paths:
Allowed Paths
=============
As a security measure, the Host Filesystem Loader will refuse to load files from outside of a few
locations that are considered safe by default:
- Outside of Standard JSON mode:
- The directories containing input files listed on the command line.
- The directories used as :ref:`remapping <import-remapping>` targets.
If the target is not a directory (i.e does not end with ``/``, ``/.`` or ``/..``) the directory
containing the target is used instead.
- Base path.
- In Standard JSON mode:
- Base path.
Additional directories can be whitelisted using the ``--allow-paths`` option.
The option accepts a comma-separated list of paths:
.. code-block:: bash
cd /home/user/project/
solc token/contract.sol \
lib/util.sol=libs/util.sol \
--base-path=token/ \
--allow-paths=../utils/,/tmp/libraries
When the compiler is invoked with the command shown above, the Host Filesystem Loader will allow
importing files from the following directories:
- ``/home/user/project/token/`` (because ``token/`` contains the input file and also because it is
the base path),
- ``/home/user/project/libs/`` (because ``libs/`` is a directory containing a remapping target),
- ``/home/user/utils/`` (because of ``../utils/`` passed to ``--allow-paths``),
- ``/tmp/libraries/`` (because of ``/tmp/libraries`` passed to ``--allow-paths``),
.. note::
The working directory of the compiler is one of the paths allowed by default only if it
happens to be the base path (or the base path is not specified or has an empty value).
.. note::
The compiler does not check if allowed paths actually exist and whether they are directories.
Non-existent or empty paths are simply ignored.
If an allowed path matches a file rather than a directory, the file is considered whitelisted, too.
.. note::
Allowed paths are case-sensitive even if the filesystem is not.
The case must exactly match the one used in your imports.
For example ``--allow-paths tokens`` will not match ``import "Tokens/IERC20.sol"``.
.. warning::
Files and directories only reachable through symbolic links from allowed directories are not
automatically whitelisted.
For example if ``token/contract.sol`` in the example above was actually a symlink pointing at
``/etc/passwd`` the compiler would refuse to load it unless ``/etc/`` was one of the allowed
paths too.
.. index:: ! remapping; import, ! import; remapping, ! remapping; context, ! remapping; prefix, ! remapping; target
.. _import-remapping:

View File

@ -47,16 +47,13 @@ it is also possible to provide :ref:`path redirects <import-remapping>` using ``
This essentially instructs the compiler to search for anything starting with
``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin``.
``solc`` will not read files from the filesystem that lie outside of
the remapping targets and outside of the directories where explicitly specified source
files reside, so things like ``import "/etc/passwd";`` only work if you add ``/=/`` as a remapping.
When accessing the filesystem to search for imports, :ref:`paths that do not start with ./
or ../ <relative-imports>` are treated as relative to the directory specified using
or ../ <direct-imports>` are treated as relative to the directory specified using
``--base-path`` option (or the current working directory if base path is not specified).
Furthermore, the part added via ``--base-path`` will not appear in the contract metadata.
For security reasons the compiler has restrictions on what directories it can access.
For security reasons the compiler has :ref:`restrictions on what directories it can access <allowed-paths>`.
Directories of source files specified on the command line and target paths of
remappings are automatically allowed to be accessed by the file reader, but everything
else is rejected by default.

View File

@ -33,11 +33,29 @@ using std::string;
namespace solidity::frontend
{
FileReader::FileReader(
boost::filesystem::path _basePath,
FileSystemPathSet _allowedDirectories
):
m_allowedDirectories(std::move(_allowedDirectories)),
m_sourceCodes()
{
setBasePath(_basePath);
for (boost::filesystem::path const& allowedDir: m_allowedDirectories)
solAssert(!allowedDir.empty(), "");
}
void FileReader::setBasePath(boost::filesystem::path const& _path)
{
m_basePath = (_path.empty() ? "" : normalizeCLIPathForVFS(_path));
}
void FileReader::allowDirectory(boost::filesystem::path _path)
{
solAssert(!_path.empty(), "");
m_allowedDirectories.insert(std::move(_path));
}
void FileReader::setSource(boost::filesystem::path const& _path, SourceCode _source)
{
boost::filesystem::path normalizedPath = normalizeCLIPathForVFS(_path);
@ -69,20 +87,17 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so
if (strippedSourceUnitName.find("file://") == 0)
strippedSourceUnitName.erase(0, 7);
auto canonicalPath = boost::filesystem::weakly_canonical(m_basePath / strippedSourceUnitName);
auto canonicalPath = normalizeCLIPathForVFS(m_basePath / strippedSourceUnitName, SymlinkResolution::Enabled);
FileSystemPathSet extraAllowedPaths = {m_basePath.empty() ? "." : m_basePath};
bool isAllowed = false;
for (auto const& allowedDir: m_allowedDirectories)
{
// If dir is a prefix of boostPath, we are fine.
if (
std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) &&
std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin())
)
for (boost::filesystem::path const& allowedDir: m_allowedDirectories + extraAllowedPaths)
if (isPathPrefix(normalizeCLIPathForVFS(allowedDir, SymlinkResolution::Enabled), canonicalPath))
{
isAllowed = true;
break;
}
}
if (!isAllowed)
return ReadCallback::Result{false, "File outside of allowed directories."};
@ -101,13 +116,20 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so
{
return ReadCallback::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)};
}
catch (std::exception const& _exception)
{
return ReadCallback::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)};
}
catch (...)
{
return ReadCallback::Result{false, "Unknown exception in read callback."};
}
}
boost::filesystem::path FileReader::normalizeCLIPathForVFS(boost::filesystem::path const& _path)
boost::filesystem::path FileReader::normalizeCLIPathForVFS(
boost::filesystem::path const& _path,
SymlinkResolution _symlinkResolution
)
{
// Detailed normalization rules:
// - Makes the path either be absolute or have slash as root (note that on Windows paths with
@ -121,7 +143,8 @@ boost::filesystem::path FileReader::normalizeCLIPathForVFS(boost::filesystem::pa
// path to the current working directory.
//
// Also note that this function:
// - Does NOT resolve symlinks (except for symlinks in the path to the current working directory).
// - Does NOT resolve symlinks (except for symlinks in the path to the current working directory)
// unless explicitly requested.
// - Does NOT check if the path refers to a file or a directory. If the path ends with a slash,
// the slash is preserved even if it's a file.
// - The only exception are paths where the file name is a dot (e.g. '.' or 'a/b/.'). These
@ -135,9 +158,27 @@ boost::filesystem::path FileReader::normalizeCLIPathForVFS(boost::filesystem::pa
// Windows it does not. To get consistent results we resolve them on all platforms.
boost::filesystem::path absolutePath = boost::filesystem::absolute(_path, canonicalWorkDir);
// NOTE: boost path preserves certain differences that are ignored by its operator ==.
// E.g. "a//b" vs "a/b" or "a/b/" vs "a/b/.". lexically_normal() does remove these differences.
boost::filesystem::path normalizedPath = absolutePath.lexically_normal();
boost::filesystem::path normalizedPath;
if (_symlinkResolution == SymlinkResolution::Enabled)
{
// NOTE: weakly_canonical() will not convert a relative path into an absolute one if no
// directory included in the path actually exists.
normalizedPath = boost::filesystem::weakly_canonical(absolutePath);
// The three corner cases in which lexically_normal() includes a trailing slash in the
// normalized path but weakly_canonical() does not. Note that the trailing slash is not
// ignored when comparing paths with ==.
if ((_path == "." || _path == "./" || _path == "../") && !boost::ends_with(normalizedPath.generic_string(), "/"))
normalizedPath = normalizedPath.parent_path() / (normalizedPath.filename().string() + "/");
}
else
{
solAssert(_symlinkResolution == SymlinkResolution::Disabled, "");
// NOTE: boost path preserves certain differences that are ignored by its operator ==.
// E.g. "a//b" vs "a/b" or "a/b/" vs "a/b/.". lexically_normal() does remove these differences.
normalizedPath = absolutePath.lexically_normal();
}
solAssert(normalizedPath.is_absolute() || normalizedPath.root_path() == "/", "");
// If the path is on the same drive as the working dir, for portability we prefer not to

View File

@ -39,22 +39,19 @@ public:
using PathMap = std::map<SourceUnitName, boost::filesystem::path>;
using FileSystemPathSet = std::set<boost::filesystem::path>;
enum SymlinkResolution {
Disabled, ///< Do not resolve symbolic links in the path.
Enabled, ///< Follow symbolic links. The path should contain no symlinks.
};
/// Constructs a FileReader with a base path and a set of allowed directories that
/// will be used when requesting files from this file reader instance.
explicit FileReader(
boost::filesystem::path _basePath = {},
FileSystemPathSet _allowedDirectories = {}
):
m_allowedDirectories(std::move(_allowedDirectories)),
m_sourceCodes()
{
setBasePath(_basePath);
}
explicit FileReader(boost::filesystem::path _basePath = {}, FileSystemPathSet _allowedDirectories = {});
void setBasePath(boost::filesystem::path const& _path);
boost::filesystem::path const& basePath() const noexcept { return m_basePath; }
void allowDirectory(boost::filesystem::path _path) { m_allowedDirectories.insert(std::move(_path)); }
void allowDirectory(boost::filesystem::path _path);
FileSystemPathSet const& allowedDirectories() const noexcept { return m_allowedDirectories; }
StringMap const& sourceCodes() const noexcept { return m_sourceCodes; }
@ -90,11 +87,16 @@ public:
/// Normalizes a filesystem path to make it include all components up to the filesystem root,
/// remove small, inconsequential differences that do not affect the meaning and make it look
/// the same on all platforms (if possible). Symlinks in the path are not resolved.
/// the same on all platforms (if possible).
/// The resulting path uses forward slashes as path separators, has no redundant separators,
/// has no redundant . or .. segments and has no root name if removing it does not change the meaning.
/// The path does not have to actually exist.
static boost::filesystem::path normalizeCLIPathForVFS(boost::filesystem::path const& _path);
/// @param _path Path to normalize.
/// @param _symlinkResolution If @a Disabled, any symlinks present in @a _path are preserved.
static boost::filesystem::path normalizeCLIPathForVFS(
boost::filesystem::path const& _path,
SymlinkResolution _symlinkResolution = SymlinkResolution::Disabled
);
/// @returns true if all the path components of @a _prefix are present at the beginning of @a _path.
/// Both paths must be absolute (or have slash as root) and normalized (no . or .. segments, no

View File

@ -20,10 +20,12 @@
#include <liblangutil/Exceptions.h>
using std::equal;
using std::find;
using std::move;
using std::nullopt;
using std::optional;
using std::string;
using std::string;
using std::string_view;
using std::vector;
namespace solidity::frontend
@ -77,24 +79,29 @@ SourceUnitName ImportRemapper::apply(ImportPath const& _path, string const& _con
return path;
}
optional<ImportRemapper::Remapping> ImportRemapper::parseRemapping(string const& _remapping)
bool ImportRemapper::isRemapping(string_view _input)
{
auto eq = find(_remapping.begin(), _remapping.end(), '=');
if (eq == _remapping.end())
return {};
return _input.find("=") != string::npos;
}
auto colon = find(_remapping.begin(), eq, ':');
optional<ImportRemapper::Remapping> ImportRemapper::parseRemapping(string_view _input)
{
auto equals = find(_input.cbegin(), _input.cend(), '=');
if (equals == _input.end())
return nullopt;
Remapping r;
auto const colon = find(_input.cbegin(), equals, ':');
r.context = colon == eq ? string() : string(_remapping.begin(), colon);
r.prefix = colon == eq ? string(_remapping.begin(), eq) : string(colon + 1, eq);
r.target = string(eq + 1, _remapping.end());
Remapping remapping{
(colon == equals ? "" : string(_input.cbegin(), colon)),
(colon == equals ? string(_input.cbegin(), equals) : string(colon + 1, equals)),
string(equals + 1, _input.cend()),
};
if (r.prefix.empty())
return {};
if (remapping.prefix.empty())
return nullopt;
return r;
return remapping;
}
}

View File

@ -56,8 +56,11 @@ public:
SourceUnitName apply(ImportPath const& _path, std::string const& _context) const;
// Parses a remapping of the format "context:prefix=target".
static std::optional<Remapping> parseRemapping(std::string const& _remapping);
/// @returns true if the string can be parsed as a remapping
static bool isRemapping(std::string_view _input);
/// Parses a remapping of the format "context:prefix=target".
static std::optional<Remapping> parseRemapping(std::string_view _input);
private:
/// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum

View File

@ -338,11 +338,17 @@ bool CommandLineParser::parseInputPathsAndRemappings()
m_options.input.ignoreMissingFiles = (m_args.count(g_strIgnoreMissingFiles) > 0);
if (m_args.count(g_strInputFile))
for (string path: m_args[g_strInputFile].as<vector<string>>())
for (string const& positionalArg: m_args[g_strInputFile].as<vector<string>>())
{
auto eq = find(path.begin(), path.end(), '=');
if (eq != path.end())
if (ImportRemapper::isRemapping(positionalArg))
{
optional<ImportRemapper::Remapping> remapping = ImportRemapper::parseRemapping(positionalArg);
if (!remapping.has_value())
{
serr() << "Invalid remapping: \"" << positionalArg << "\"." << endl;
return false;
}
if (m_options.input.mode == InputMode::StandardJson)
{
serr() << "Import remappings are not accepted on the command line in Standard JSON mode." << endl;
@ -350,21 +356,24 @@ bool CommandLineParser::parseInputPathsAndRemappings()
return false;
}
if (auto r = ImportRemapper::parseRemapping(path))
m_options.input.remappings.emplace_back(std::move(*r));
else
if (!remapping->target.empty())
{
serr() << "Invalid remapping: \"" << path << "\"." << endl;
return false;
// If the target is a directory, whitelist it. Otherwise whitelist containing dir.
// NOTE: /a/b/c/ is a directory while /a/b/c is not.
boost::filesystem::path remappingDir = remapping->target;
if (remappingDir.filename() != "..")
// As an exception we'll treat /a/b/c/.. as a directory too. It would be
// unintuitive to whitelist /a/b/c when the target is equivalent to /a/b/.
remappingDir.remove_filename();
m_options.input.allowedDirectories.insert(remappingDir.empty() ? "." : remappingDir);
}
string remappingTarget(eq + 1, path.end());
m_options.input.allowedDirectories.insert(boost::filesystem::path(remappingTarget).remove_filename());
m_options.input.remappings.emplace_back(move(remapping.value()));
}
else if (path == "-")
else if (positionalArg == "-")
m_options.input.addStdin = true;
else
m_options.input.paths.insert(path);
m_options.input.paths.insert(positionalArg);
}
if (m_options.input.mode == InputMode::StandardJson)
@ -977,17 +986,9 @@ bool CommandLineParser::processArgs()
if (m_args.count(g_strAllowPaths))
{
vector<string> paths;
for (string const& path: boost::split(paths, m_args[g_strAllowPaths].as<string>(), boost::is_any_of(",")))
{
auto filesystem_path = boost::filesystem::path(path);
// If the given path had a trailing slash, the Boost filesystem
// path will have it's last component set to '.'. This breaks
// path comparison in later parts of the code, so we need to strip
// it.
if (filesystem_path.filename() == ".")
filesystem_path.remove_filename();
m_options.input.allowedDirectories.insert(filesystem_path);
}
for (string const& allowedPath: boost::split(paths, m_args[g_strAllowPaths].as<string>(), boost::is_any_of(",")))
if (!allowedPath.empty())
m_options.input.allowedDirectories.insert(allowedPath);
}
if (m_args.count(g_strStopAfter))

View File

@ -160,6 +160,7 @@ set(solcli_sources
solc/Common.cpp
solc/Common.h
solc/CommandLineInterface.cpp
solc/CommandLineInterfaceAllowPaths.cpp
solc/CommandLineParser.cpp
)
detect_stray_source_files("${solcli_sources}" "solc/")

View File

@ -53,13 +53,17 @@ void solidity::test::createFileWithContent(boost::filesystem::path const& _path,
}
bool solidity::test::createSymlinkIfSupportedByFilesystem(
boost::filesystem::path const& _targetPath,
boost::filesystem::path _targetPath,
boost::filesystem::path const& _linkName,
bool _directorySymlink
)
{
boost::system::error_code symlinkCreationError;
// NOTE: On Windows / works as a separator in a symlink target only if the target is absolute.
// Convert path separators to native ones to avoid this problem.
_targetPath.make_preferred();
if (_directorySymlink)
boost::filesystem::create_directory_symlink(_targetPath, _linkName, symlinkCreationError);
else

View File

@ -46,7 +46,7 @@ void createFileWithContent(boost::filesystem::path const& _path, std::string con
/// support symlinks.
/// Throws an exception of the operation fails for a different reason.
bool createSymlinkIfSupportedByFilesystem(
boost::filesystem::path const& _targetPath,
boost::filesystem::path _targetPath,
boost::filesystem::path const& _linkName,
bool _directorySymlink
);

View File

@ -1 +0,0 @@
-

View File

@ -1,11 +0,0 @@
Error: Source "too_long_line/input.sol" not found: File outside of allowed directories.
--> <stdin>:4:1:
|
4 | import "../too_long_line/input.sol";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: Source "error_codes/input.sol" not found: File outside of allowed directories.
--> stdin_allowed_paths/input.sol:4:1:
|
4 | import "../error_codes/input.sol";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -1 +0,0 @@
1

View File

@ -1,4 +0,0 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
import "../error_codes/input.sol";

View File

@ -1,4 +0,0 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
import "../too_long_line/input.sol";

View File

@ -36,31 +36,39 @@ using namespace solidity::test;
namespace solidity::frontend::test
{
using SymlinkResolution = FileReader::SymlinkResolution;
BOOST_AUTO_TEST_SUITE(FileReaderTest)
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_absolute_path)
{
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./."), "/");
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/.", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./.", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/"), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/."), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a/"), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a/."), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b"), "/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a", resolveSymlinks), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/", resolveSymlinks), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/.", resolveSymlinks), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a", resolveSymlinks), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a/", resolveSymlinks), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a/.", resolveSymlinks), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b", resolveSymlinks), "/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/", resolveSymlinks), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/./b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../a/b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/.."), "/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/..", resolveSymlinks), "/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../", resolveSymlinks), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/./b/", resolveSymlinks), "/a/b/");
#if !defined(_WIN32) || BOOST_VERSION > 107600
// This throws on Windows due to a bug in Boost: https://github.com/boostorg/filesystem/issues/201
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../a/b/", resolveSymlinks), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../../../"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../../..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../../../", resolveSymlinks), "/");
#endif
}
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_relative_path)
@ -75,54 +83,64 @@ BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_relative_path)
expectedPrefix = "/" / expectedPrefix.relative_path();
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("."), expectedPrefix / "x/y/z/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./"), expectedPrefix / "x/y/z/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".//"), expectedPrefix / "x/y/z/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".."), expectedPrefix / "x/y");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../"), expectedPrefix / "x/y/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("..//"), expectedPrefix / "x/y/");
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".", resolveSymlinks), expectedPrefix / "x/y/z/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./", resolveSymlinks), expectedPrefix / "x/y/z/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".//", resolveSymlinks), expectedPrefix / "x/y/z/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("..", resolveSymlinks), expectedPrefix / "x/y");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../", resolveSymlinks), expectedPrefix / "x/y/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("..//", resolveSymlinks), expectedPrefix / "x/y/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a"), expectedPrefix / "x/y/z/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/."), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a"), expectedPrefix / "x/y/z/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/."), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/./"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/.//"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/./."), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/././"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/././/"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b"), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a", resolveSymlinks), expectedPrefix / "x/y/z/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/.", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a", resolveSymlinks), expectedPrefix / "x/y/z/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/.", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/./", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/.//", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/./.", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/././", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/././/", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b", resolveSymlinks), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/", resolveSymlinks), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/b"), expectedPrefix / "x/y/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/b"), expectedPrefix / "x/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/b"), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("././a/b"), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/b", resolveSymlinks), expectedPrefix / "x/y/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/b", resolveSymlinks), expectedPrefix / "x/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/b", resolveSymlinks), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("././a/b", resolveSymlinks), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/./b/"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../a/b/"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/.."), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/..//"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../.."), expectedPrefix / "x/y/z/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../../"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../..//"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/.././../p/../q/../a/b"), expectedPrefix / "a/b");
#if !defined(_WIN32) || BOOST_VERSION > 107600
// This throws on Windows due to a bug in Boost: https://github.com/boostorg/filesystem/issues/201
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/.././../p/../q/../a/b", resolveSymlinks), expectedPrefix / "a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../a/b/", resolveSymlinks), expectedPrefix / "x/y/z/a/b/");
#endif
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/./b/", resolveSymlinks), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/..", resolveSymlinks), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../", resolveSymlinks), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/..//", resolveSymlinks), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../..", resolveSymlinks), expectedPrefix / "x/y/z/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../../", resolveSymlinks), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../..//", resolveSymlinks), expectedPrefix / "x/y/z/a/");
}
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_redundant_slashes)
{
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("///"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("////"), "/");
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("///", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("////", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("////a/b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a//b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a////b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b//"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b////"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("////a/b/", resolveSymlinks), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a//b/", resolveSymlinks), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a////b/", resolveSymlinks), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b//", resolveSymlinks), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b////", resolveSymlinks), "/a/b/");
}
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_unc_path)
@ -134,23 +152,26 @@ BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_unc_path)
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
// UNC paths start with // or \\ followed by a name. They are used for network shares on Windows.
// On UNIX systems they are not supported but still treated in a special way.
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/"), "//host/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/a/b"), "//host/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/a/b/"), "//host/a/b/");
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
// UNC paths start with // or \\ followed by a name. They are used for network shares on Windows.
// On UNIX systems they are not supported but still treated in a special way.
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/", resolveSymlinks), "//host/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/a/b", resolveSymlinks), "//host/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/a/b/", resolveSymlinks), "//host/a/b/");
#if defined(_WIN32)
// On Windows an UNC path can also start with \\ instead of //
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/"), "//host/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b"), "//host/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b/"), "//host/a/b/");
// On Windows an UNC path can also start with \\ instead of //
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/", resolveSymlinks), "//host/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b", resolveSymlinks), "//host/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b/", resolveSymlinks), "//host/a/b/");
#else
// On UNIX systems it's just a fancy relative path instead
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/"), expectedWorkDir / "\\\\host/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b"), expectedWorkDir / "\\\\host/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b/"), expectedWorkDir / "\\\\host/a/b/");
// On UNIX systems it's just a fancy relative path instead
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/", resolveSymlinks), expectedWorkDir / "\\\\host/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b", resolveSymlinks), expectedWorkDir / "\\\\host/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b/", resolveSymlinks), expectedWorkDir / "\\\\host/a/b/");
#endif
}
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_root_name_only)
@ -167,20 +188,23 @@ BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_root_name_only)
// C:\ represents the root directory of drive C: but C: on its own refers to the current working
// directory.
// UNC paths
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//"), "//" / expectedWorkDir);
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host"), "//host" / expectedWorkDir);
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
// UNC paths
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//", resolveSymlinks), "//" / expectedWorkDir);
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host", resolveSymlinks), "//host" / expectedWorkDir);
// On UNIX systems root name is empty.
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(""), expectedWorkDir);
// On UNIX systems root name is empty.
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("", resolveSymlinks), expectedWorkDir);
#if defined(_WIN32)
boost::filesystem::path driveLetter = boost::filesystem::current_path().root_name();
solAssert(!driveLetter.empty(), "");
solAssert(driveLetter.is_relative(), "");
boost::filesystem::path driveLetter = boost::filesystem::current_path().root_name();
solAssert(!driveLetter.empty(), "");
solAssert(driveLetter.is_relative(), "");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(driveLetter), expectedWorkDir);
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(driveLetter, resolveSymlinks), expectedWorkDir);
#endif
}
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_stripping_root_name)
@ -193,41 +217,55 @@ BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_stripping_root_name)
soltestAssert(!boost::filesystem::current_path().root_name().empty(), "");
#endif
boost::filesystem::path normalizedPath = FileReader::normalizeCLIPathForVFS(boost::filesystem::current_path());
BOOST_CHECK_EQUAL(normalizedPath, "/" / boost::filesystem::current_path().relative_path());
BOOST_TEST(normalizedPath.root_name().empty());
BOOST_CHECK_EQUAL(normalizedPath.root_directory(), "/");
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
boost::filesystem::path normalizedPath = FileReader::normalizeCLIPathForVFS(
boost::filesystem::current_path(),
resolveSymlinks
);
BOOST_CHECK_EQUAL(normalizedPath, "/" / boost::filesystem::current_path().relative_path());
BOOST_TEST(normalizedPath.root_name().empty());
BOOST_CHECK_EQUAL(normalizedPath.root_directory(), "/");
}
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_path_beyond_root)
{
TemporaryWorkingDirectory tempWorkDir("/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../../b/../.."), "/");
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../.", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a", resolveSymlinks), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a", resolveSymlinks), "/a");
#if !defined(_WIN32) || BOOST_VERSION > 107600
// This throws on Windows due to a bug in Boost: https://github.com/boostorg/filesystem/issues/201
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a/..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a/../..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a/..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a/../..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../../b/../..", resolveSymlinks), "/");
#endif
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../../b/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../.", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a", resolveSymlinks), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a", resolveSymlinks), "/a");
#if !defined(_WIN32) || BOOST_VERSION > 107600
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/../..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/../..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../..", resolveSymlinks), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../../b/../..", resolveSymlinks), "/");
#endif
}
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_case_sensitivity)
@ -235,22 +273,31 @@ BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_case_sensitivity)
TemporaryDirectory tempDir(TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir);
boost::filesystem::path expectedPrefix = "/" / tempDir.path().relative_path();
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
boost::filesystem::path workDirNoSymlinks = boost::filesystem::weakly_canonical(tempDir);
boost::filesystem::path expectedPrefix = "/" / workDirNoSymlinks.relative_path();
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc") == expectedPrefix / "abc");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc") != expectedPrefix / "ABC");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "ABC") != expectedPrefix / "abc");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "ABC") == expectedPrefix / "ABC");
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
BOOST_TEST(FileReader::normalizeCLIPathForVFS(workDirNoSymlinks / "abc", resolveSymlinks) == expectedPrefix / "abc");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(workDirNoSymlinks / "abc", resolveSymlinks) != expectedPrefix / "ABC");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(workDirNoSymlinks / "ABC", resolveSymlinks) != expectedPrefix / "abc");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(workDirNoSymlinks / "ABC", resolveSymlinks) == expectedPrefix / "ABC");
}
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_path_separators)
{
// Even on Windows we want / as a separator.
BOOST_TEST((FileReader::normalizeCLIPathForVFS("/a/b/c").native() == boost::filesystem::path("/a/b/c").native()));
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
// Even on Windows we want / as a separator.
BOOST_TEST((
FileReader::normalizeCLIPathForVFS("/a/b/c", resolveSymlinks).native() ==
boost::filesystem::path("/a/b/c").native()
));
}
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_should_not_resolve_symlinks)
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_should_not_resolve_symlinks_unless_requested)
{
TemporaryDirectory tempDir({"abc/"}, TEST_CASE_NAME);
soltestAssert(tempDir.path().is_absolute(), "");
@ -258,11 +305,26 @@ BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_should_not_resolve_symlinks)
if (!createSymlinkIfSupportedByFilesystem(tempDir.path() / "abc", tempDir.path() / "sym", true))
return;
boost::filesystem::path expectedPrefix = "/" / tempDir.path().relative_path();
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
boost::filesystem::path expectedPrefixWithSymlinks = "/" / tempDir.path().relative_path();
boost::filesystem::path expectedPrefixWithoutSymlinks = "/" / boost::filesystem::weakly_canonical(tempDir).relative_path();
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol"), expectedPrefix / "sym/contract.sol");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol"), expectedPrefix / "abc/contract.sol");
BOOST_CHECK_EQUAL(
FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol", SymlinkResolution::Disabled),
expectedPrefixWithSymlinks / "sym/contract.sol"
);
BOOST_CHECK_EQUAL(
FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol", SymlinkResolution::Disabled),
expectedPrefixWithSymlinks / "abc/contract.sol"
);
BOOST_CHECK_EQUAL(
FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol", SymlinkResolution::Enabled),
expectedPrefixWithoutSymlinks / "abc/contract.sol"
);
BOOST_CHECK_EQUAL(
FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol", SymlinkResolution::Enabled),
expectedPrefixWithoutSymlinks / "abc/contract.sol"
);
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_should_resolve_symlinks_in_workdir_when_path_is_relative)
@ -280,9 +342,31 @@ BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_should_resolve_symlinks_in_workdir_w
boost::filesystem::path expectedPrefix = "/" / tempDir.path().relative_path();
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("contract.sol"), expectedWorkDir / "contract.sol");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol"), expectedPrefix / "sym/contract.sol");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol"), expectedPrefix / "abc/contract.sol");
for (SymlinkResolution resolveSymlinks: {SymlinkResolution::Enabled, SymlinkResolution::Disabled})
{
BOOST_CHECK_EQUAL(
FileReader::normalizeCLIPathForVFS("contract.sol", resolveSymlinks),
expectedWorkDir / "contract.sol"
);
}
BOOST_CHECK_EQUAL(
FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol", SymlinkResolution::Disabled),
expectedPrefix / "sym/contract.sol"
);
BOOST_CHECK_EQUAL(
FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol", SymlinkResolution::Disabled),
expectedPrefix / "abc/contract.sol"
);
BOOST_CHECK_EQUAL(
FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol", SymlinkResolution::Enabled),
expectedWorkDir / "contract.sol"
);
BOOST_CHECK_EQUAL(
FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol", SymlinkResolution::Enabled),
expectedWorkDir / "contract.sol"
);
}
BOOST_AUTO_TEST_CASE(isPathPrefix_file_prefix)

View File

@ -786,12 +786,7 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_symlinks)
TemporaryWorkingDirectory tempWorkDir(tempDir.path() / "r");
if (
#if !defined(_WIN32)
!createSymlinkIfSupportedByFilesystem("../x/y", tempDir.path() / "r/sym", true) ||
#else
// NOTE: On Windows / works as a separator in a symlink target only if the target is absolute
!createSymlinkIfSupportedByFilesystem("..\\x\\y", tempDir.path() / "r/sym", true) ||
#endif
!createSymlinkIfSupportedByFilesystem("contract.sol", tempDir.path() / "x/y/z/contract_symlink.sol", false)
)
return;

View File

@ -0,0 +1,539 @@
/*
This file is part of solidity.
solidity 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.
solidity 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 solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/// Unit tests for solc/CommandLineInterface.h
#include <solc/CommandLineInterface.h>
#include <test/solc/Common.h>
#include <test/Common.h>
#include <test/FilesystemUtils.h>
#include <test/TemporaryDirectory.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <boost/algorithm/string/predicate.hpp>
#include <fstream>
#include <regex>
#include <string>
#include <vector>
using namespace std;
using namespace solidity::frontend;
using namespace solidity::test;
#define TEST_CASE_NAME (boost::unit_test::framework::current_test_case().p_name)
namespace
{
struct ImportCheck
{
enum class Result
{
Unknown, ///< Status is unknown due to a failure of the status check.
OK, ///< Passed compilation without errors.
FileNotFound, ///< Error was reported: file not found.
PathDisallowed, ///< Error was reported: file not allowed paths.
};
bool operator==(ImportCheck const& _other) const { return result == _other.result && message == _other.message; }
bool operator!=(ImportCheck const& _other) const { return !(*this == _other); }
operator bool() const { return this->result == Result::OK; }
static ImportCheck const OK() { return {Result::OK, ""}; }
static ImportCheck const FileNotFound() { return {Result::FileNotFound, ""}; }
static ImportCheck const PathDisallowed() { return {Result::PathDisallowed, ""}; }
static ImportCheck const Unknown(const string& _message) { return {Result::Unknown, _message}; }
Result result;
std::string message;
};
ImportCheck checkImport(
string const& _import,
vector<string> const& _cliOptions
)
{
soltestAssert(regex_match(_import, regex{R"(import '[^']*')"}), "");
for (string const& option: _cliOptions)
soltestAssert(
boost::starts_with(option, "--base-path") ||
boost::starts_with(option, "--allow-paths") ||
!boost::starts_with(option, "--"),
""
);
vector<string> commandLine = {
"solc",
"-",
"--no-color",
"--error-codes",
};
commandLine += _cliOptions;
string standardInputContent =
"// SPDX-License-Identifier: GPL-3.0\n"
"pragma solidity >=0.0;\n" +
_import + ";";
test::OptionsReaderAndMessages cliResult = test::parseCommandLineAndReadInputFiles(
commandLine,
standardInputContent,
true /* processInput */
);
if (cliResult.success)
return ImportCheck::OK();
static regex const sourceNotFoundErrorRegex{
R"(^Error \(6275\): Source ".+" not found: (.*)\.\n)"
R"(\s*--> .*<stdin>:\d+:\d+:\n)"
R"(\s*\|\n)"
R"(\d+\s*\| import '.+';\n)"
R"(\s*\| \^+\n\s*$)"
};
smatch submatches;
if (!regex_match(cliResult.stderrContent, submatches, sourceNotFoundErrorRegex))
return ImportCheck::Unknown("Unexpected stderr content: '" + cliResult.stderrContent + "'");
if (submatches[1] != "File not found" && submatches[1] != "File outside of allowed directories")
return ImportCheck::Unknown("Unexpected error message: '" + cliResult.stderrContent + "'");
if (submatches[1] == "File not found")
return ImportCheck::FileNotFound();
else if (submatches[1] == "File outside of allowed directories")
return ImportCheck::PathDisallowed();
else
return ImportCheck::Unknown("Unexpected error message '" + submatches[1].str() + "'");
}
class AllowPathsFixture
{
protected:
AllowPathsFixture():
m_tempDir({"code/", "work/"}, TEST_CASE_NAME),
m_tempWorkDir(m_tempDir.path() / "work"),
m_codeDir(m_tempDir.path() / "code"),
m_workDir(m_tempDir.path() / "work"),
m_portablePrefix(("/" / boost::filesystem::canonical(m_codeDir).relative_path()).generic_string())
{
createFilesWithParentDirs(
{
m_codeDir / "a/b/c/d.sol",
m_codeDir / "a/b/c.sol",
m_codeDir / "a/b/X.sol",
m_codeDir / "a/X/c.sol",
m_codeDir / "X/b/c.sol",
m_codeDir / "a/bc/d.sol",
m_codeDir / "X/bc/d.sol",
m_codeDir / "x/y/z.sol",
m_codeDir / "contract.sol",
m_workDir / "a/b/c/d.sol",
m_workDir / "a/b/c.sol",
m_workDir / "a/b/X.sol",
m_workDir / "a/X/c.sol",
m_workDir / "X/b/c.sol",
m_workDir / "contract.sol",
},
"// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.0;"
);
if (
!createSymlinkIfSupportedByFilesystem("b", m_codeDir / "a/b_sym", true) ||
!createSymlinkIfSupportedByFilesystem("../x/y", m_codeDir / "a/y_sym", true) ||
!createSymlinkIfSupportedByFilesystem("../../a/b/c.sol", m_codeDir / "a/b/c_sym.sol", false) ||
!createSymlinkIfSupportedByFilesystem("../../x/y/z.sol", m_codeDir / "a/b/z_sym.sol", false)
)
return;
m_caseSensitiveFilesystem = boost::filesystem::create_directories(m_codeDir / "A/B/C");
soltestAssert(boost::filesystem::equivalent(m_codeDir / "a/b/c", m_codeDir / "A/B/C") != m_caseSensitiveFilesystem, "");
}
TemporaryDirectory m_tempDir;
TemporaryWorkingDirectory m_tempWorkDir;
boost::filesystem::path const m_codeDir;
boost::filesystem::path const m_workDir;
string m_portablePrefix;
bool m_caseSensitiveFilesystem = true;
};
ostream& operator<<(ostream& _out, ImportCheck const& _value)
{
switch (_value.result)
{
case ImportCheck::Result::Unknown: _out << "Unknown"; break;
case ImportCheck::Result::OK: _out << "OK"; break;
case ImportCheck::Result::FileNotFound: _out << "FileNotFound"; break;
case ImportCheck::Result::PathDisallowed: _out << "PathDisallowed"; break;
}
if (_value.message != "")
_out << "(" << _value.message << ")";
return _out;
}
} // namespace
namespace boost::test_tools::tt_detail
{
// Boost won't find the << operator unless we put it in the std namespace which is illegal.
// The recommended solution is to overload print_log_value<> struct and make it use our operator.
template<>
struct print_log_value<ImportCheck>
{
void operator()(std::ostream& _out, ImportCheck const& _value) { ::operator<<(_out, _value); }
};
} // namespace boost::test_tools::tt_detail
namespace solidity::frontend::test
{
BOOST_AUTO_TEST_SUITE(CommandLineInterfaceAllowPathsTest)
BOOST_FIXTURE_TEST_CASE(allow_path_multiple_paths, AllowPathsFixture)
{
string allowedPaths =
m_codeDir.generic_string() + "/a/b/X.sol," +
m_codeDir.generic_string() + "/X/," +
m_codeDir.generic_string() + "/z," +
m_codeDir.generic_string() + "/a/b";
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", allowedPaths}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", allowedPaths}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", allowedPaths}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", allowedPaths}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_should_work_with_various_path_forms, AllowPathsFixture)
{
string import = "import '" + m_portablePrefix + "/a/b/c.sol'";
// Without --allow-path
BOOST_TEST(checkImport(import, {}) == ImportCheck::PathDisallowed());
// Absolute paths allowed
BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string()}));
BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a"}));
BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a/"}));
BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a/b"}));
BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a/b/c.sol"}));
// Relative paths allowed
BOOST_TEST(checkImport(import, {"--allow-paths=../code/a"}));
BOOST_TEST(checkImport(import, {"--allow-paths=../code/a/"}));
BOOST_TEST(checkImport(import, {"--allow-paths=../code/a/b"}));
BOOST_TEST(checkImport(import, {"--allow-paths=../code/a/b/c.sol"}));
// Non-normalized paths allowed
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/."}));
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/./"}));
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/.."}));
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/../"}));
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/b"}));
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/./b"}));
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/../a/b"}));
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a///b"}));
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/b//"}));
BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/b///"}));
// Root path allowed
BOOST_TEST(checkImport(import, {"--allow-paths=/"}));
BOOST_TEST(checkImport(import, {"--allow-paths=///"}));
// UNC paths should be treated differently from normal paths
soltestAssert(FileReader::isUNCPath("/" + m_portablePrefix), "");
BOOST_TEST(checkImport(import, {"--allow-paths=//"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport(import, {"--allow-paths=/" + m_portablePrefix}) == ImportCheck::PathDisallowed());
// Paths going beyond root allowed
BOOST_TEST(checkImport(import, {"--allow-paths=/../../"}));
BOOST_TEST(checkImport(import, {"--allow-paths=/../.."}));
#if !defined(_WIN32) || BOOST_VERSION > 107600
// This throws on Windows due to a bug in Boost: https://github.com/boostorg/filesystem/issues/201
BOOST_TEST(checkImport(import, {"--allow-paths=/../../a/../"}));
#endif
BOOST_TEST(checkImport(import, {"--allow-paths=/../../" + m_portablePrefix}));
// File named like a directory
BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a/b/c.sol/"}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_should_handle_empty_paths, AllowPathsFixture)
{
// Work dir is base path
BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", ""}));
BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", "x,,y"}));
BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", ""}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", "x,,y"}) == ImportCheck::PathDisallowed());
// Work dir is not base path
BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", "", "--base-path=../code/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", "x,,y", "--base-path=../code/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", "", "--base-path=../code/"}));
BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", "x,,y", "--base-path=../code/"}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_case_sensitive, AllowPathsFixture)
{
// Allowed paths are case-sensitive even on case-insensitive filesystems
BOOST_TEST(
checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", m_codeDir.string() + "/A/B/"}) ==
ImportCheck::PathDisallowed()
);
}
BOOST_FIXTURE_TEST_CASE(allow_path_should_work_with_various_import_forms, AllowPathsFixture)
{
// Absolute import paths
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a/b/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", "../code/a/b/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", "../code/a/b/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", "../code/a/b/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", "../code/a/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", "../code/a"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", "../code/a"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", "../code/a"}));
// Relative import paths
// NOTE: Base path is whitelisted by default so we need the 'a/../../code/' part to get
// outside of it. And it can't be just '../code/' because that would not be a direct import.
BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", "../code/a/b"}));
BOOST_TEST(checkImport("import 'a/../../code/X/b/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import 'a/../../code/a/X/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import 'a/../../code/a/b/X.sol'", {"--allow-paths", "../code/a/b"}));
// Non-normalized relative import paths
BOOST_TEST(checkImport("import 'a/../../code/a/./b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}));
BOOST_TEST(checkImport("import 'a/../../code/a/../a/b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}));
BOOST_TEST(checkImport("import 'a/../../code/a///b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}));
// UNC paths in imports
string uncImportPath = "/" + m_portablePrefix + "/a/b/c.sol";
soltestAssert(FileReader::isUNCPath(uncImportPath), "");
BOOST_TEST(checkImport("import '" + uncImportPath + "'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed());
}
BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_input_files, AllowPathsFixture)
{
// By default none of the files is whitelisted
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {}) == ImportCheck::PathDisallowed());
// Compiling a file whitelists its directory and subdirectories
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_codeDir.string() + "/a/b/c.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {m_codeDir.string() + "/a/b/c.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {m_codeDir.string() + "/a/b/c.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {m_codeDir.string() + "/a/b/c.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {m_codeDir.string() + "/a/b/c.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {m_codeDir.string() + "/a/b/c.sol"}) == ImportCheck::PathDisallowed());
// If only file name is specified, its parent dir path is empty. This should be equivalent to
// whitelisting the work dir.
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/contract.sol'", {"contract.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import 'contract.sol'", {"contract.sol"}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_remappings, AllowPathsFixture)
{
// Adding a remapping whitelists target's parent directory and subdirectories
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/c.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {"x=../code/a/b/c.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"x=../code/a/b/c.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=/contract.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=/contract.sol/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "/a/b=../code/X/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "/a/b/=../code/X/b/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {m_portablePrefix + "/a/b=../code/X/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {m_portablePrefix + "/a/b/=../code/X/b/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "/a/b:y/z=x/w"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "/a/b:y/z=x/w"}) == ImportCheck::PathDisallowed());
// Adding a remapping whitelists the target and subdirectories when the target is a directory
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {"x=../code/a/b/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"x=../code/a/b/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/c/"}) == ImportCheck::PathDisallowed());
// Adding a remapping whitelists target's parent directory and subdirectories when the target
// is a directory but does not have a trailing slash
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {"x=../code/a/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"x=../code/a/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/c"}));
// Adding a remapping to a relative target at VFS root whitelists the work dir
BOOST_TEST(checkImport("import '/../../x/y/z.sol'", {"x=contract.sol", "--base-path=../code/a/b/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '/../../../work/a/b/c.sol'", {"x=contract.sol", "--base-path=../code/a/b/"}));
BOOST_TEST(checkImport("import '/../../x/y/z.sol'", {"x=contract.sol/", "--base-path=../code/a/b/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '/../../../work/a/b/c.sol'", {"x=contract.sol/", "--base-path=../code/a/b/"}) == ImportCheck::PathDisallowed());
// Adding a remapping with an empty target does not whitelist anything
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "="}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"../code/="}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '/../work/a/b/c.sol'", {"../code/=", "--base-path", m_portablePrefix}) == ImportCheck::PathDisallowed());
// Adding a remapping that includes .. or . segments whitelists the parent dir and subdirectories
// of the resolved target
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=."}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=."}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=./"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=./"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=.."}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=.."}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/./.."}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/./.."}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/./.."}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/./../"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/./../"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/./../"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/./../b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/./../b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/./../b"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/./../b/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/./../b/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/./../b/"}) == ImportCheck::PathDisallowed());
// If the target is just a file name, its parent dir path is empty. This should be equivalent to
// whitelisting the work dir.
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/contract.sol'", {"x=contract.sol"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import 'contract.sol'", {"x=contract.sol"}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_base_path, AllowPathsFixture)
{
// Relative base path whitelists its content
BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=../code/a"}));
BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path=../code/a"}));
BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path=../code/a"}));
BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path=../code/a"}));
BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=../code/a/"}));
BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path=../code/a/"}));
BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path=../code/a/"}));
BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path=../code/a/"}));
BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path=../code/."}));
BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path=../code/./"}));
BOOST_TEST(checkImport("import 'code/a/b/c.sol'", {"--base-path=.."}));
BOOST_TEST(checkImport("import 'code/a/b/c.sol'", {"--base-path=../"}));
// Absolute base path whitelists its content
BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path", m_codeDir.string() + "/a"}));
BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path", m_codeDir.string() + "/a"}));
BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path", m_codeDir.string() + "/a"}));
BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path", m_codeDir.string() + "/a"}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_work_dir, AllowPathsFixture)
{
// Work dir is only automatically whitelisted if it matches base path
BOOST_TEST(checkImport("import 'b/../../../work/a/b/c.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed());
// Compiling a file in the work dir whitelists it even if it's not in base path
BOOST_TEST(checkImport("import 'b/../../../work/a/b/c.sol'", {"--base-path", "../code/a/", "a/b/c.sol"}));
// Work dir can also be whitelisted manually
BOOST_TEST(checkImport("import 'b/../../../work/a/b/c.sol'", {"--base-path", "../code/a/", "--allow-paths=."}));
// Not setting base path whitelists the working directory
BOOST_TEST(checkImport("import 'a/b/c.sol'", {}));
BOOST_TEST(checkImport("import 'a/b/c/d.sol'", {}));
BOOST_TEST(checkImport("import 'a/b/X.sol'", {}));
BOOST_TEST(checkImport("import 'a/X/c.sol'", {}));
// Setting base path to an empty value whitelists the working directory
BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path", ""}));
BOOST_TEST(checkImport("import 'a/b/c/d.sol'", {"--base-path", ""}));
BOOST_TEST(checkImport("import 'a/b/X.sol'", {"--base-path", ""}));
BOOST_TEST(checkImport("import 'a/X/c.sol'", {"--base-path", ""}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_symlinks_within_whitelisted_dir, AllowPathsFixture)
{
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths=../code/a/b_sym/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b_sym/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths=../code/a/b_sym"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b_sym"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c_sym.sol'", {"--allow-paths=../code/a/b/c.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths=../code/a/b/c_sym.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c_sym.sol'", {"--allow-paths=../code/a/b/c_sym.sol"}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_symlinks_outside_whitelisted_dir, AllowPathsFixture)
{
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/y_sym/z.sol'", {"--allow-paths=../code/a/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/y_sym/z.sol'", {"--allow-paths=../code/x/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/a/"}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/x/"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/a/b/z_sym.sol"}));
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/x/y/z.sol"}));
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace solidity::frontend::test

View File

@ -168,7 +168,7 @@ BOOST_AUTO_TEST_CASE(cli_mode_options)
expectedOptions.input.addStdin = true;
expectedOptions.input.basePath = "/home/user/";
expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "", "c", "/usr/lib"};
expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "c", "/usr/lib"};
expectedOptions.input.ignoreMissingFiles = true;
expectedOptions.input.errorRecovery = (inputMode == InputMode::Compiler);
expectedOptions.output.dir = "/tmp/out";
@ -307,7 +307,7 @@ BOOST_AUTO_TEST_CASE(assembly_mode_options)
};
expectedOptions.input.addStdin = true;
expectedOptions.input.basePath = "/home/user/";
expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "", "c", "/usr/lib"};
expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "c", "/usr/lib"};
expectedOptions.input.ignoreMissingFiles = true;
expectedOptions.output.overwriteFiles = true;
expectedOptions.output.evmVersion = EVMVersion::spuriousDragon();

View File

@ -54,5 +54,22 @@ test::OptionsReaderAndMessages test::parseCommandLineAndReadInputFiles(
if (success && _processInput)
success = cli.processInput();
return {success, cli.options(), cli.fileReader(), cli.standardJsonInput(), sout.str(), serr.str()};
return {
success,
cli.options(),
cli.fileReader(),
cli.standardJsonInput(),
sout.str(),
stripPreReleaseWarning(serr.str()),
};
}
string test::stripPreReleaseWarning(string const& _stderrContent)
{
static regex const preReleaseWarningRegex{
R"(Warning( \(3805\))?: This is a pre-release compiler version, please do not use it in production\.\n)"
R"((\n)?)"
};
return regex_replace(_stderrContent, preReleaseWarningRegex, "");
}

View File

@ -50,4 +50,6 @@ OptionsReaderAndMessages parseCommandLineAndReadInputFiles(
bool _processInput = false
);
std::string stripPreReleaseWarning(std::string const& _stderrContent);
} // namespace solidity::frontend::test