/* * 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 { static const constexpr struct { APT::StringView shortName; APT::StringView longName; bool takesArgument; } shortPatterns[] = { {"r"_sv, "?architecture"_sv, true}, {"A"_sv, "?archive"_sv, true}, {"M"_sv, "?automatic"_sv, false}, {"b"_sv, "?broken"_sv, false}, {"c"_sv, "?config-files"_sv, false}, // FIXME: The words after ~D should be case-insensitive {"DDepends:"_sv, "?depends"_sv, true}, {"DPre-Depends:"_sv, "?pre-depends"_sv, true}, {"DSuggests:"_sv, "?suggests"_sv, true}, {"DRecommends:"_sv, "?recommends"_sv, true}, {"DConflicts:"_sv, "?conflicts"_sv, true}, {"DReplaces:"_sv, "?replaces"_sv, true}, {"DObsoletes:"_sv, "?obsoletes"_sv, true}, {"DBreaks:"_sv, "?breaks"_sv, true}, {"DEnhances:"_sv, "?enhances"_sv, true}, {"D"_sv, "?depends"_sv, true}, {"RDepends:"_sv, "?reverse-depends"_sv, true}, {"RPre-Depends:"_sv, "?reverse-pre-depends"_sv, true}, {"RSuggests:"_sv, "?reverse-suggests"_sv, true}, {"RRecommends:"_sv, "?reverse-recommends"_sv, true}, {"RConflicts:"_sv, "?reverse-conflicts"_sv, true}, {"RReplaces:"_sv, "?reverse-replaces"_sv, true}, {"RObsoletes:"_sv, "?reverse-obsoletes"_sv, true}, {"RBreaks:"_sv, "?reverse-breaks"_sv, true}, {"REnhances:"_sv, "?reverse-enhances"_sv, true}, {"R"_sv, "?reverse-depends"_sv, true}, {"E"_sv, "?essential"_sv, false}, {"F"_sv, "?false"_sv, false}, {"g"_sv, "?garbage"_sv, false}, {"i"_sv, "?installed"_sv, false}, {"n"_sv, "?name"_sv, true}, {"o"_sv, "?obsolete"_sv, false}, {"O"_sv, "?origin"_sv, true}, {"s"_sv, "?section"_sv, true}, {"e"_sv, "?source-package"_sv, true}, {"T"_sv, "?true"_sv, false}, {"U"_sv, "?upgradable"_sv, false}, {"V"_sv, "?version"_sv, true}, {"v"_sv, "?virtual"_sv, false}, }; 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 == nullptr) throw Error{Node{0, sentence.size()}, "Expected pattern"}; 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() { return parseOr(); } std::unique_ptr PatternTreeParser::parseOr() { auto start = state.offset; std::vector> nodes; auto firstNode = parseAnd(); if (firstNode == nullptr) return nullptr; nodes.push_back(std::move(firstNode)); for (skipSpace(); sentence[state.offset] == '|'; skipSpace()) { state.offset++; skipSpace(); auto node = parseAnd(); if (node == nullptr) throw Error{Node{state.offset, sentence.size()}, "Expected pattern after |"}; nodes.push_back(std::move(node)); } if (nodes.size() == 0) return nullptr; if (nodes.size() == 1) return std::move(nodes[0]); auto node = std::make_unique(); node->start = start; node->end = nodes[nodes.size() - 1]->end; node->term = "?or"; node->arguments = std::move(nodes); node->haveArgumentList = true; return node; } std::unique_ptr PatternTreeParser::parseAnd() { auto start = state.offset; std::vector> nodes; for (skipSpace(); state.offset < sentence.size(); skipSpace()) { auto node = parseUnary(); if (node == nullptr) break; nodes.push_back(std::move(node)); } if (nodes.size() == 0) return nullptr; if (nodes.size() == 1) return std::move(nodes[0]); auto node = std::make_unique(); node->start = start; node->end = nodes[nodes.size() - 1]->end; node->term = "?and"; node->arguments = std::move(nodes); node->haveArgumentList = true; return node; } std::unique_ptr PatternTreeParser::parseUnary() { if (sentence[state.offset] != '!') return parsePrimary(); auto start = ++state.offset; auto primary = parsePrimary(); if (primary == nullptr) throw Error{Node{start, sentence.size()}, "Expected pattern"}; auto node = std::make_unique(); node->start = start; node->end = primary->end; node->term = "?not"; node->arguments.push_back(std::move(primary)); node->haveArgumentList = true; return node; } std::unique_ptr PatternTreeParser::parsePrimary() { std::unique_ptr node; if ((node = parseShortPattern()) != nullptr) return node; if ((node = parsePattern()) != nullptr) return node; if ((node = parseGroup()) != nullptr) return node; return nullptr; } std::unique_ptr PatternTreeParser::parseGroup() { if (sentence[state.offset] != '(') return nullptr; auto start = state.offset++; skipSpace(); auto node = parse(); if (node == nullptr) throw Error{Node{state.offset, sentence.size()}, "Expected pattern after '('"}; skipSpace(); if (sentence[state.offset] != ')') throw Error{Node{state.offset, sentence.size()}, "Expected closing parenthesis"}; auto end = ++state.offset; node->start = start; node->end = end; return node; } std::unique_ptr PatternTreeParser::parseArgument(bool shrt) { std::unique_ptr node; if ((node = parseQuotedWord()) != nullptr) return node; if ((node = parseWord(shrt)) != nullptr) return node; if ((node = parse()) != nullptr) return node; throw Error{Node{state.offset, sentence.size()}, "Expected pattern, quoted word, or word"}; } // Parse a short pattern std::unique_ptr PatternTreeParser::parseShortPattern() { if (sentence[state.offset] != '~') return nullptr; for (auto &sp : shortPatterns) { if (sentence.substr(state.offset + 1, sp.shortName.size()) != sp.shortName) continue; auto node = std::make_unique(); node->end = node->start = state.offset; node->term = sp.longName; state.offset += sp.shortName.size() + 1; if (sp.takesArgument) { node->arguments.push_back(parseArgument(true)); node->haveArgumentList = true; } node->end = state.offset; return node; } throw Error{Node{state.offset, sentence.size()}, "Unknown short pattern"}; } // 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); if (node->term.size() <= 1) throw Error{*node, "Pattern must have a term/name"}; 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(parseArgument(false)); 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(parseArgument(false)); 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(bool shrt) { // Characters not allowed at the start of a word (also see ..._SHRT) static const constexpr auto DISALLOWED_START = "!?~|,() \0"_sv; // Characters terminating a word inside a long pattern static const constexpr auto DISALLOWED_LONG = "|,()\0"_sv; // Characters terminating a word as a short form argument, should contain all of START. static const constexpr auto DISALLOWED_SHRT = "!?~|,() \0"_sv; const auto DISALLOWED = shrt ? DISALLOWED_SHRT : DISALLOWED_LONG; 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.to_string(); if (haveArgumentList) { os << "("; for (auto &node : arguments) node->render(os) << ","; os << ")"; } return os; } std::ostream &PatternTreeParser::WordNode::render(std::ostream &os) { return quoted ? os << '"' << word.to_string() << '"' : os << word.to_string(); } 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("?depends", 1, 1)) return std::make_unique(aPattern(node->arguments[0])); if (node->matches("?predepends", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::PreDepends); if (node->matches("?suggests", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Suggests); if (node->matches("?recommends", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Recommends); if (node->matches("?conflicts", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Conflicts); if (node->matches("?replaces", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Replaces); if (node->matches("?obsoletes", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Obsoletes); if (node->matches("?breaks", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::DpkgBreaks); if (node->matches("?enhances", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Enhances); if (node->matches("?reverse-depends", 1, 1)) return std::make_unique(aPattern(node->arguments[0])); if (node->matches("?reverse-predepends", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::PreDepends); if (node->matches("?reverse-suggests", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Suggests); if (node->matches("?reverse-recommends", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Recommends); if (node->matches("?reverse-conflicts", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Conflicts); if (node->matches("?reverse-replaces", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Replaces); if (node->matches("?reverse-obsoletes", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Obsoletes); if (node->matches("?reverse-breaks", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::DpkgBreaks); if (node->matches("?reverse-enhances", 1, 1)) return std::make_unique(aPattern(node->arguments[0]), pkgCache::Dep::Enhances); 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 == nullptr) || string == nullptr) 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