/* 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"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths=../code/a/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths=../code/a/b"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); // Non-normalized paths allowed BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/."}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/./"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/.."}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/../"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/b"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/./b"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/../a/b"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a///b"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/b//"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/b///"}) == ImportCheck::PathDisallowed()); // 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=/../../"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths=/../.."}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths=/../../a/../"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport(import, {"--allow-paths=/../../" + m_portablePrefix}) == ImportCheck::PathDisallowed()); // 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", ""})); BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", "x,,y"})); // Work dir is not base path BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", "", "--base-path=../code/"})); BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", "x,,y", "--base-path=../code/"})); 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"}) == ImportCheck::PathDisallowed()); 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/"}) == ImportCheck::PathDisallowed()); 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/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed()); 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"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a"}) == ImportCheck::PathDisallowed()); 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"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", "../code/a"}) == ImportCheck::PathDisallowed()); // 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"}) == ImportCheck::PathDisallowed()); 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"}) == ImportCheck::PathDisallowed()); // Non-normalized relative import paths BOOST_TEST(checkImport("import 'a/../../code/a/./b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/../../code/a/../a/b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/../../code/a///b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); // 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"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); 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"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "/a/b/=../code/X/b/"}) == ImportCheck::PathDisallowed()); 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/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/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed()); 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"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {"x=../code/a/b"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"x=../code/a/b"}) == ImportCheck::PathDisallowed()); 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 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/"})); 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::FileNotFound()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"../code/="})); BOOST_TEST(checkImport("import '/../work/a/b/c.sol'", {"../code/=", "--base-path", m_portablePrefix})); // 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=."})); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=."})); 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=../"}) == 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=../code/a/b/./.."}) == ImportCheck::PathDisallowed()); 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/b/c.sol'", {"x=../code/a/b/./../"}) == ImportCheck::PathDisallowed()); 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/b/c.sol'", {"x=../code/a/b/./../b"}) == ImportCheck::PathDisallowed()); 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()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/./../b/"}) == ImportCheck::PathDisallowed()); 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"})); 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"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path=../code/a"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path=../code/a"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path=../code/a"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path=../code/."}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path=../code/./"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'code/a/b/c.sol'", {"--base-path=.."}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'code/a/b/c.sol'", {"--base-path=../"}) == ImportCheck::PathDisallowed()); // Absolute base path whitelists its content BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path", m_codeDir.string() + "/a"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path", m_codeDir.string() + "/a"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path", m_codeDir.string() + "/a"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path", m_codeDir.string() + "/a"}) == ImportCheck::PathDisallowed()); } 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'", {}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/b/c/d.sol'", {}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/b/X.sol'", {}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/X/c.sol'", {}) == ImportCheck::PathDisallowed()); // Setting base path to an empty value whitelists the working directory BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path", ""}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/b/c/d.sol'", {"--base-path", ""}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/b/X.sol'", {"--base-path", ""}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import 'a/X/c.sol'", {"--base-path", ""}) == ImportCheck::PathDisallowed()); } 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/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths=../code/a/b_sym/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b_sym/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths=../code/a/b_sym"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b_sym"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c_sym.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/c_sym.sol"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c_sym.sol'", {"--allow-paths=../code/a/b/c_sym.sol"}) == ImportCheck::PathDisallowed()); } 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/"}) == ImportCheck::PathDisallowed()); 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/"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/a/b/z_sym.sol"}) == ImportCheck::PathDisallowed()); BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/x/y/z.sol"}) == ImportCheck::PathDisallowed()); } BOOST_AUTO_TEST_SUITE_END() } // namespace solidity::frontend::test