many("regex") condition function | No | Yes
License
diff --git a/src/backend/metadata/condition_grammar.h b/src/backend/metadata/condition_grammar.h
index 2cc16ad0..c26a659f 100644
--- a/src/backend/metadata/condition_grammar.h
+++ b/src/backend/metadata/condition_grammar.h
@@ -88,6 +88,7 @@ namespace loot {
function =
("file(" > filePath > ')')[phoenix::bind(&ConditionGrammar::CheckFile, this, qi::labels::_val, qi::labels::_1)]
| ("regex(" > quotedStr > ')')[phoenix::bind(&ConditionGrammar::CheckRegex, this, qi::labels::_val, qi::labels::_1)]
+ | ("many(" > quotedStr > ')')[phoenix::bind(&ConditionGrammar::CheckMany, this, qi::labels::_val, qi::labels::_1)]
| ("checksum(" > filePath > ',' > qi::hex > ')')[phoenix::bind(&ConditionGrammar::CheckSum, this, qi::labels::_val, qi::labels::_1, qi::labels::_2)]
| ("version(" > filePath > ',' > quotedStr > ',' > comparator > ')')[phoenix::bind(&ConditionGrammar::CheckVersion, this, qi::labels::_val, qi::labels::_1, qi::labels::_2, qi::labels::_3)]
| ("active(" > filePath > ')')[phoenix::bind(&ConditionGrammar::CheckActive, this, qi::labels::_val, qi::labels::_1)]
@@ -144,7 +145,7 @@ namespace loot {
bool _parseOnly;
//Eval's exact paths. Check for files and ghosted plugins.
- void CheckFile(bool& result, const std::string& file) {
+ void CheckFile(bool& result, const std::string& file) const {
if (_parseOnly)
return;
@@ -171,10 +172,8 @@ namespace loot {
BOOST_LOG_TRIVIAL(trace) << "The file does not exist.";
}
- void CheckRegex(bool& result, const std::string& regexStr) {
- if (_parseOnly)
- return;
- result = false;
+ // Split a regex string into the non-regex filesystem parent path, and the regex filename.
+ std::pair SplitRegex(const std::string& regex) const {
//Can't support a regex string where all path components may be regex, since this could
//lead to massive scanning if an unfortunately-named directory is encountered.
//As such, only the filename portion can be a regex. Need to separate that from the rest
@@ -185,14 +184,10 @@ namespace loot {
In C++ string literals, the backslash must be escaped once more to give "\\\\".
Split the regex with another regex! */
- //Need to also check if the regex is for a safe path.
-
- BOOST_LOG_TRIVIAL(trace) << "Checking to see if any files matching the regex \"" << regexStr << "\" exist.";
-
std::regex sepReg("/|(\\\\\\\\)", std::regex::ECMAScript | std::regex::icase);
std::vector components;
- std::sregex_token_iterator it(regexStr.begin(), regexStr.end(), sepReg, -1);
+ std::sregex_token_iterator it(regex.begin(), regex.end(), sepReg, -1);
std::sregex_token_iterator itend;
for (; it != itend; ++it) {
components.push_back(*it);
@@ -201,26 +196,17 @@ namespace loot {
std::string filename = components.back();
components.pop_back();
- std::string parent;
+ boost::filesystem::path parent;
for (std::vector::const_iterator it = components.begin(), endIt = components.end()--; it != endIt; ++it) {
if (*it == ".")
continue;
- parent += *it + '/';
+ parent += *it;
}
- if (boost::contains(parent, "../../")) {
+ if (!IsSafePath(parent.string())) {
BOOST_LOG_TRIVIAL(error) << "Invalid folder path: " << parent;
- throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid folder path:").str() + " " + parent);
- }
-
- //Now we have a valid parent path and a regex filename. Check that
- //the parent path exists and is a directory.
-
- boost::filesystem::path parent_path = _game->DataPath() / parent;
- if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) {
- BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory.";
- return;
+ throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid folder path:").str() + " " + parent.string());
}
std::regex reg;
@@ -232,8 +218,29 @@ namespace loot {
throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid regex string:").str() + " " + filename);
}
+ return std::pair(parent, reg);
+ }
+
+ void CheckRegex(bool& result, const std::string& regexStr) const {
+ if (_parseOnly)
+ return;
+ result = false;
+
+ BOOST_LOG_TRIVIAL(trace) << "Checking to see if any files matching the regex \"" << regexStr << "\" exist.";
+
+ std::pair pathRegex = SplitRegex(regexStr);
+
+ //Now we have a valid parent path and a regex filename. Check that
+ //the parent path exists and is a directory.
+
+ boost::filesystem::path parent_path = _game->DataPath() / pathRegex.first;
+ if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) {
+ BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory.";
+ return;
+ }
+
for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) {
- if (std::regex_match(itr->path().filename().string(), reg)) {
+ if (std::regex_match(itr->path().filename().string(), pathRegex.second)) {
result = true;
BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path();
return;
@@ -241,6 +248,35 @@ namespace loot {
}
}
+ void CheckMany(bool& result, const std::string& regexStr) const {
+ if (_parseOnly)
+ return;
+ result = false;
+
+ BOOST_LOG_TRIVIAL(trace) << "Checking to see if more than one file matching the regex \"" << regexStr << "\" exist.";
+
+ std::pair pathRegex = SplitRegex(regexStr);
+
+ //Now we have a valid parent path and a regex filename. Check that
+ //the parent path exists and is a directory.
+
+ boost::filesystem::path parent_path = _game->DataPath() / pathRegex.first;
+ if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) {
+ BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory.";
+ return;
+ }
+
+ size_t count = 0;
+ for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) {
+ if (std::regex_match(itr->path().filename().string(), pathRegex.second)) {
+ ++count;
+ BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path();
+ }
+ }
+
+ result = count > 1;
+ }
+
void CheckSum(bool& result, const std::string& file, const uint32_t checksum) {
if (_parseOnly)
return;
@@ -275,7 +311,7 @@ namespace loot {
result = checksum == crc;
}
- void CheckVersion(bool& result, const std::string& file, const std::string& version, const std::string& comparator) {
+ void CheckVersion(bool& result, const std::string& file, const std::string& version, const std::string& comparator) const {
if (_parseOnly)
return;
@@ -313,7 +349,7 @@ namespace loot {
BOOST_LOG_TRIVIAL(trace) << "Version check result: " << result;
}
- void CheckActive(bool& result, const std::string& file) {
+ void CheckActive(bool& result, const std::string& file) const {
if (_parseOnly)
return;
@@ -340,7 +376,7 @@ namespace loot {
}
//Checks that the path (not regex) doesn't go outside any game folders.
- bool IsSafePath(const std::string& path) {
+ bool IsSafePath(const std::string& path) const {
BOOST_LOG_TRIVIAL(trace) << "Checking to see if the path \"" << path << "\" is safe.";
std::vector components;
diff --git a/src/tests/backend/metadata/test_condition_grammar.h b/src/tests/backend/metadata/test_condition_grammar.h
index a992fc2f..07a373fc 100644
--- a/src/tests/backend/metadata/test_condition_grammar.h
+++ b/src/tests/backend/metadata/test_condition_grammar.h
@@ -164,6 +164,42 @@ TEST_F(ConditionGrammar, RegexConditionFalse) {
EXPECT_FALSE(eval);
}
+TEST_F(ConditionGrammar, ManyConditionTrue) {
+ loot::Game game(loot::Game::tes5);
+ game.SetGamePath(dataPath.parent_path());
+
+ boost::spirit::qi::space_type skipper;
+ bool eval = false;
+ bool r = false;
+ Grammar cg(&game, false);
+
+ std::string condition("many(\"Blank.+\\.esm\")");
+ std::string::const_iterator begin = condition.begin();
+ std::string::const_iterator end = condition.end();
+
+ EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval));
+ EXPECT_TRUE(r);
+ EXPECT_TRUE(eval);
+}
+
+TEST_F(ConditionGrammar, ManyConditionFalse) {
+ loot::Game game(loot::Game::tes5);
+ game.SetGamePath(dataPath.parent_path());
+
+ boost::spirit::qi::space_type skipper;
+ bool eval = true;
+ bool r = false;
+ Grammar cg(&game, false);
+
+ std::string condition("many(\"Blank\\.esm\")");
+ std::string::const_iterator begin = condition.begin();
+ std::string::const_iterator end = condition.end();
+
+ EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval));
+ EXPECT_TRUE(r);
+ EXPECT_FALSE(eval);
+}
+
TEST_F(ConditionGrammar, ChecksumConditionTrue) {
loot::Game game(loot::Game::tes5);
game.SetGamePath(dataPath.parent_path());
|