/* 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 . */ // SPDX-License-Identifier: GPL-3.0 /// Unit tests for solc/CommandLineInterface.h #include #include #include #include #include #include #include #include #include #include #include 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 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 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*--> .*:\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 { 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