/* * cachefilter-patterns.cc - Parser for aptitude-style patterns * * Copyright (c) 2019 Canonical Ltd * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include namespace APT { namespace Internal { template std::string rstrprintf(Args... args) { std::string str; strprintf(str, std::forward(args)...); return str; } // Parse a complete pattern, make sure it's the entire input std::unique_ptr PatternTreeParser::parseTop() { skipSpace(); auto node = parse(); skipSpace(); if (node->end != sentence.size()) throw Error{Node{node->end, sentence.size()}, "Expected end of file"}; return node; } // Parse any pattern std::unique_ptr PatternTreeParser::parse() { std::unique_ptr node; if ((node = parsePattern()) != nullptr) return node; if ((node = parseQuotedWord()) != nullptr) return node; if ((node = parseWord()) != nullptr) return node; throw Error{Node{state.offset, sentence.size()}, "Expected pattern, quoted word, or word"}; } // Parse a list pattern (or function call pattern) std::unique_ptr PatternTreeParser::parsePattern() { static constexpr auto CHARS = ("0123456789" "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "-"_sv); if (sentence[state.offset] != '?') return nullptr; auto node = std::make_unique(); node->end = node->start = state.offset; state.offset++; while (CHARS.find(sentence[state.offset]) != APT::StringView::npos) { ++state.offset; } node->term = sentence.substr(node->start, state.offset - node->start); node->end = skipSpace(); // We don't have any arguments, return node; if (sentence[state.offset] != '(') return node; node->end = ++state.offset; skipSpace(); node->haveArgumentList = true; // Empty argument list, return if (sentence[state.offset] == ')') { node->end = ++state.offset; return node; } node->arguments.push_back(parse()); skipSpace(); while (sentence[state.offset] == ',') { ++state.offset; skipSpace(); // This was a trailing comma - allow it and break the loop if (sentence[state.offset] == ')') break; node->arguments.push_back(parse()); skipSpace(); } node->end = state.offset; if (sentence[state.offset] != ')') throw Error{node->arguments.empty() ? *node : *node->arguments[node->arguments.size() - 1], rstrprintf("Expected closing parenthesis or comma after last argument, received %c", sentence[state.offset])}; node->end = ++state.offset; return node; } // Parse a quoted word atom std::unique_ptr PatternTreeParser::parseQuotedWord() { if (sentence[state.offset] != '"') return nullptr; auto node = std::make_unique(); node->start = state.offset; // Eat beginning of string state.offset++; while (sentence[state.offset] != '"' && sentence[state.offset] != '\0') state.offset++; // End of string if (sentence[state.offset] != '"') throw Error{*node, "Could not find end of string"}; state.offset++; node->end = state.offset; node->word = sentence.substr(node->start + 1, node->end - node->start - 2); return node; } // Parse a bare word atom std::unique_ptr PatternTreeParser::parseWord() { static const constexpr auto DISALLOWED_START = "?~,()\0"_sv; static const constexpr auto DISALLOWED = ",()\0"_sv; if (DISALLOWED_START.find(sentence[state.offset]) != APT::StringView::npos) return nullptr; auto node = std::make_unique(); node->start = state.offset; while (DISALLOWED.find(sentence[state.offset]) == APT::StringView::npos) state.offset++; node->end = state.offset; node->word = sentence.substr(node->start, node->end - node->start); return node; } // Rendering of the tree in JSON for debugging std::ostream &PatternTreeParser::PatternNode::render(std::ostream &os) { os << "{" << "\"term\": \"" << term.to_string() << "\",\n" << "\"arguments\": [\n"; for (auto &node : arguments) node->render(os) << "," << std::endl; os << "null]\n"; os << "}\n"; return os; } std::ostream &PatternTreeParser::WordNode::render(std::ostream &os) { os << '"' << word.to_string() << '"'; return os; } std::nullptr_t PatternTreeParser::Node::error(std::string message) { throw Error{*this, message}; } bool PatternTreeParser::PatternNode::matches(APT::StringView name, int min, int max) { if (name != term) return false; if (max != 0 && !haveArgumentList) error(rstrprintf("%s expects an argument list", term.to_string().c_str())); if (max == 0 && haveArgumentList) error(rstrprintf("%s does not expect an argument list", term.to_string().c_str())); if (min >= 0 && min == max && (arguments.size() != size_t(min))) error(rstrprintf("%s expects %d arguments, but received %d arguments", term.to_string().c_str(), min, arguments.size())); if (min >= 0 && arguments.size() < size_t(min)) error(rstrprintf("%s expects at least %d arguments, but received %d arguments", term.to_string().c_str(), min, arguments.size())); if (max >= 0 && arguments.size() > size_t(max)) error(rstrprintf("%s expects at most %d arguments, but received %d arguments", term.to_string().c_str(), max, arguments.size())); return true; } std::unique_ptr PatternParser::aPattern(std::unique_ptr &nodeP) { assert(nodeP != nullptr); auto node = dynamic_cast(nodeP.get()); if (node == nullptr) nodeP->error("Expected a pattern"); if (node->matches("?architecture", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?archive", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?all-versions", 1, 1)) return std::make_unique(aPattern(node->arguments[0])); if (node->matches("?any-version", 1, 1)) return std::make_unique(aPattern(node->arguments[0])); if (node->matches("?automatic", 0, 0)) return std::make_unique(file); if (node->matches("?broken", 0, 0)) return std::make_unique(file); if (node->matches("?config-files", 0, 0)) return std::make_unique(); if (node->matches("?essential", 0, 0)) return std::make_unique(); if (node->matches("?exact-name", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?false", 0, 0)) return std::make_unique(); if (node->matches("?garbage", 0, 0)) return std::make_unique(file); if (node->matches("?installed", 0, 0)) return std::make_unique(file); if (node->matches("?name", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?not", 1, 1)) return std::make_unique(aPattern(node->arguments[0]).release()); if (node->matches("?obsolete", 0, 0)) return std::make_unique(); if (node->matches("?origin", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?section", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?source-package", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?source-version", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?true", 0, 0)) return std::make_unique(); if (node->matches("?upgradable", 0, 0)) return std::make_unique(file); if (node->matches("?version", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?virtual", 0, 0)) return std::make_unique(); if (node->matches("?x-name-fnmatch", 1, 1)) return std::make_unique(aWord(node->arguments[0])); // Variable argument patterns if (node->matches("?and", 0, -1) || node->matches("?narrow", 0, -1)) { auto pattern = std::make_unique(); for (auto &arg : node->arguments) pattern->AND(aPattern(arg).release()); if (node->term == "?narrow") return std::make_unique(std::move(pattern)); return pattern; } if (node->matches("?or", 0, -1)) { auto pattern = std::make_unique(); for (auto &arg : node->arguments) pattern->OR(aPattern(arg).release()); return pattern; } node->error(rstrprintf("Unrecognized pattern '%s'", node->term.to_string().c_str())); return nullptr; } std::string PatternParser::aWord(std::unique_ptr &nodeP) { assert(nodeP != nullptr); auto node = dynamic_cast(nodeP.get()); if (node == nullptr) nodeP->error("Expected a word"); return node->word.to_string(); } namespace Patterns { BaseRegexMatcher::BaseRegexMatcher(std::string const &Pattern) { pattern = new regex_t; int const Res = regcomp(pattern, Pattern.c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB); if (Res == 0) return; delete pattern; pattern = NULL; char Error[300]; regerror(Res, pattern, Error, sizeof(Error)); _error->Error(_("Regex compilation error - %s"), Error); } bool BaseRegexMatcher::operator()(const char *string) { if (unlikely(pattern == NULL)) return false; else return regexec(pattern, string, 0, 0, 0) == 0; } BaseRegexMatcher::~BaseRegexMatcher() { if (pattern == NULL) return; regfree(pattern); delete pattern; } } // namespace Patterns } // namespace Internal // The bridge into the public world std::unique_ptr APT::CacheFilter::ParsePattern(APT::StringView pattern, pkgCacheFile *file) { if (file != nullptr && !file->BuildDepCache()) return nullptr; try { auto top = APT::Internal::PatternTreeParser(pattern).parseTop(); APT::Internal::PatternParser parser{file}; return parser.aPattern(top); } catch (APT::Internal::PatternTreeParser::Error &e) { std::stringstream ss; ss << "input:" << e.location.start << "-" << e.location.end << ": error: " << e.message << "\n"; ss << pattern.to_string() << "\n"; for (size_t i = 0; i < e.location.start; i++) ss << " "; for (size_t i = e.location.start; i < e.location.end; i++) ss << "^"; ss << "\n"; _error->Error("%s", ss.str().c_str()); return nullptr; } } } // namespace APT