1
0
Fork 0
apt/apt-pkg/cachefilter-patterns.cc
Daniel Baumann 6810ba718b
Adding upstream version 3.0.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-20 21:10:43 +02:00

619 lines
21 KiB
C++

/*
* cachefilter-patterns.cc - Parser for aptitude-style patterns
*
* Copyright (c) 2019 Canonical Ltd
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <config.h>
#include <apt-pkg/cachefilter-patterns.h>
#include <apti18n.h>
using namespace std::literals;
namespace APT
{
namespace Internal
{
static const constexpr struct
{
std::string_view shortName;
std::string_view 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},
{"p"sv, "?priority"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 <class... Args>
std::string rstrprintf(Args... args)
{
std::string str;
strprintf(str, std::forward<Args>(args)...);
return str;
}
// Parse a complete pattern, make sure it's the entire input
std::unique_ptr<PatternTreeParser::Node> 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::Node> PatternTreeParser::parse()
{
return parseOr();
}
std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseOr()
{
auto start = state.offset;
std::vector<std::unique_ptr<PatternTreeParser::Node>> 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<PatternNode>();
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::Node> PatternTreeParser::parseAnd()
{
auto start = state.offset;
std::vector<std::unique_ptr<PatternTreeParser::Node>> 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<PatternNode>();
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::Node> 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<PatternNode>();
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::Node> PatternTreeParser::parsePrimary()
{
std::unique_ptr<Node> 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::Node> 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::Node> PatternTreeParser::parseArgument(bool shrt)
{
std::unique_ptr<Node> 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::Node> 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<PatternNode>();
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::Node> PatternTreeParser::parsePattern()
{
static constexpr auto CHARS = ("0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"-"sv);
if (sentence[state.offset] != '?')
return nullptr;
auto node = std::make_unique<PatternNode>();
node->end = node->start = state.offset;
state.offset++;
while (CHARS.find(sentence[state.offset]) != std::string_view::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::Node> PatternTreeParser::parseQuotedWord()
{
if (sentence[state.offset] != '"')
return nullptr;
auto node = std::make_unique<WordNode>();
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::Node> 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]) != std::string_view::npos)
return nullptr;
auto node = std::make_unique<WordNode>();
node->start = state.offset;
while (DISALLOWED.find(sentence[state.offset]) == std::string_view::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;
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 << '"' : os << word;
}
std::nullptr_t PatternTreeParser::Node::error(std::string message)
{
throw Error{*this, message};
}
bool PatternTreeParser::PatternNode::matches(std::string_view name, int min, int max)
{
if (name != term)
return false;
if (max != 0 && !haveArgumentList)
error(rstrprintf("%.*s expects an argument list", (int)term.size(), term.data()));
if (max == 0 && haveArgumentList)
error(rstrprintf("%.*s does not expect an argument list", (int)term.size(), term.data()));
if (min >= 0 && min == max && (arguments.size() != size_t(min)))
error(rstrprintf("%.*s expects %d arguments, but received %d arguments", (int)term.size(), term.data(), min, arguments.size()));
if (min >= 0 && arguments.size() < size_t(min))
error(rstrprintf("%.*s expects at least %d arguments, but received %d arguments", (int)term.size(), term.data(), min, arguments.size()));
if (max >= 0 && arguments.size() > size_t(max))
error(rstrprintf("%.*s expects at most %d arguments, but received %d arguments", (int)term.size(), term.data(), max, arguments.size()));
return true;
}
std::unique_ptr<APT::CacheFilter::Matcher> PatternParser::aPattern(std::unique_ptr<PatternTreeParser::Node> &nodeP)
{
assert(nodeP != nullptr);
auto node = dynamic_cast<PatternTreeParser::PatternNode *>(nodeP.get());
if (node == nullptr)
nodeP->error("Expected a pattern");
if (node->matches("?architecture", 1, 1))
return std::make_unique<APT::CacheFilter::PackageArchitectureMatchesSpecification>(aWord(node->arguments[0]));
if (node->matches("?archive", 1, 1))
return std::make_unique<Patterns::VersionIsArchive>(aWord(node->arguments[0]));
if (node->matches("?codename", 1, 1))
return std::make_unique<Patterns::VersionIsCodename>(aWord(node->arguments[0]));
if (node->matches("?all-versions", 1, 1))
return std::make_unique<Patterns::VersionIsAllVersions>(aPattern(node->arguments[0]));
if (node->matches("?any-version", 1, 1))
return std::make_unique<Patterns::VersionIsAnyVersion>(aPattern(node->arguments[0]));
if (node->matches("?automatic", 0, 0))
return std::make_unique<Patterns::PackageIsAutomatic>(file);
if (node->matches("?broken", 0, 0))
return std::make_unique<Patterns::PackageIsBroken>(file);
if (node->matches("?config-files", 0, 0))
return std::make_unique<Patterns::PackageIsConfigFiles>();
if (node->matches("?depends", 1, 1))
return std::make_unique<Patterns::VersionDepends>(aPattern(node->arguments[0]));
if (node->matches("?predepends", 1, 1))
return std::make_unique<Patterns::VersionDepends>(aPattern(node->arguments[0]), pkgCache::Dep::PreDepends);
if (node->matches("?suggests", 1, 1))
return std::make_unique<Patterns::VersionDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Suggests);
if (node->matches("?recommends", 1, 1))
return std::make_unique<Patterns::VersionDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Recommends);
if (node->matches("?conflicts", 1, 1))
return std::make_unique<Patterns::VersionDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Conflicts);
if (node->matches("?replaces", 1, 1))
return std::make_unique<Patterns::VersionDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Replaces);
if (node->matches("?obsoletes", 1, 1))
return std::make_unique<Patterns::VersionDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Obsoletes);
if (node->matches("?breaks", 1, 1))
return std::make_unique<Patterns::VersionDepends>(aPattern(node->arguments[0]), pkgCache::Dep::DpkgBreaks);
if (node->matches("?enhances", 1, 1))
return std::make_unique<Patterns::VersionDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Enhances);
if (node->matches("?reverse-depends", 1, 1))
return std::make_unique<Patterns::PackageReverseDepends>(aPattern(node->arguments[0]));
if (node->matches("?reverse-predepends", 1, 1))
return std::make_unique<Patterns::PackageReverseDepends>(aPattern(node->arguments[0]), pkgCache::Dep::PreDepends);
if (node->matches("?reverse-suggests", 1, 1))
return std::make_unique<Patterns::PackageReverseDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Suggests);
if (node->matches("?reverse-recommends", 1, 1))
return std::make_unique<Patterns::PackageReverseDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Recommends);
if (node->matches("?reverse-conflicts", 1, 1))
return std::make_unique<Patterns::PackageReverseDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Conflicts);
if (node->matches("?reverse-replaces", 1, 1))
return std::make_unique<Patterns::PackageReverseDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Replaces);
if (node->matches("?reverse-obsoletes", 1, 1))
return std::make_unique<Patterns::PackageReverseDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Obsoletes);
if (node->matches("?reverse-breaks", 1, 1))
return std::make_unique<Patterns::PackageReverseDepends>(aPattern(node->arguments[0]), pkgCache::Dep::DpkgBreaks);
if (node->matches("?reverse-enhances", 1, 1))
return std::make_unique<Patterns::PackageReverseDepends>(aPattern(node->arguments[0]), pkgCache::Dep::Enhances);
if (node->matches("?essential", 0, 0))
return std::make_unique<Patterns::PackageIsEssential>();
if (node->matches("?priority", 1, 1))
return std::make_unique<Patterns::VersionIsPriority>(aWord(node->arguments[0]));
if (node->matches("?exact-name", 1, 1))
return std::make_unique<Patterns::PackageHasExactName>(aWord(node->arguments[0]));
if (node->matches("?false", 0, 0))
return std::make_unique<APT::CacheFilter::FalseMatcher>();
if (node->matches("?garbage", 0, 0))
return std::make_unique<Patterns::PackageIsGarbage>(file);
if (node->matches("?installed", 0, 0))
return std::make_unique<Patterns::PackageIsInstalled>(file);
if (node->matches("?name", 1, 1))
return std::make_unique<APT::CacheFilter::PackageNameMatchesRegEx>(aWord(node->arguments[0]));
if (node->matches("?not", 1, 1))
return std::make_unique<APT::CacheFilter::NOTMatcher>(aPattern(node->arguments[0]).release());
if (node->matches("?obsolete", 0, 0))
return std::make_unique<Patterns::PackageIsObsolete>();
if (node->matches("?origin", 1, 1))
return std::make_unique<Patterns::VersionIsOrigin>(aWord(node->arguments[0]));
if (node->matches("?phasing", 0, 0))
return std::make_unique<Patterns::PackageIsPhasing>(file);
if (node->matches("?section", 1, 1))
return std::make_unique<Patterns::VersionIsSection>(aWord(node->arguments[0]));
if (node->matches("?security", 0, 0))
return std::make_unique<Patterns::VersionIsSecurity>();
if (node->matches("?source-package", 1, 1))
return std::make_unique<Patterns::VersionIsSourcePackage>(aWord(node->arguments[0]));
if (node->matches("?source-version", 1, 1))
return std::make_unique<Patterns::VersionIsSourceVersion>(aWord(node->arguments[0]));
if (node->matches("?true", 0, 0))
return std::make_unique<APT::CacheFilter::TrueMatcher>();
if (node->matches("?upgradable", 0, 0))
return std::make_unique<Patterns::PackageIsUpgradable>(file);
if (node->matches("?version", 1, 1))
return std::make_unique<Patterns::VersionIsVersion>(aWord(node->arguments[0]));
if (node->matches("?virtual", 0, 0))
return std::make_unique<Patterns::PackageIsVirtual>();
if (node->matches("?x-name-fnmatch", 1, 1))
return std::make_unique<APT::CacheFilter::PackageNameMatchesFnmatch>(aWord(node->arguments[0]));
// Variable argument patterns
if (node->matches("?and", 0, -1) || node->matches("?narrow", 0, -1))
{
auto pattern = std::make_unique<APT::CacheFilter::ANDMatcher>();
for (auto &arg : node->arguments)
pattern->AND(aPattern(arg).release());
if (node->term == "?narrow")
return std::make_unique<Patterns::VersionIsAnyVersion>(std::move(pattern));
return pattern;
}
if (node->matches("?or", 0, -1))
{
auto pattern = std::make_unique<APT::CacheFilter::ORMatcher>();
for (auto &arg : node->arguments)
pattern->OR(aPattern(arg).release());
return pattern;
}
node->error(rstrprintf("Unrecognized pattern '%.*s'", (int)node->term.size(), node->term.data()));
return nullptr;
}
std::string PatternParser::aWord(std::unique_ptr<PatternTreeParser::Node> &nodeP)
{
assert(nodeP != nullptr);
auto node = dynamic_cast<PatternTreeParser::WordNode *>(nodeP.get());
if (node == nullptr)
nodeP->error("Expected a word");
return std::string{node->word};
}
namespace Patterns
{
BaseRegexMatcher::BaseRegexMatcher(std::string const &Pattern)
{
pattern.emplace();
int const Res = regcomp(&*pattern, Pattern.c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB);
if (Res == 0)
return;
char Error[300];
regerror(Res, &*pattern, Error, sizeof(Error));
_error->Error(_("Regex compilation error - %s"), Error);
pattern.reset();
}
bool BaseRegexMatcher::operator()(const char *string)
{
if (unlikely(pattern == std::nullopt) || string == nullptr)
return false;
else
return regexec(&*pattern, string, 0, 0, 0) == 0;
}
BaseRegexMatcher::~BaseRegexMatcher()
{
if (pattern == std::nullopt)
return;
regfree(&*pattern);
}
} // namespace Patterns
} // namespace Internal
// The bridge into the public world
std::unique_ptr<APT::CacheFilter::Matcher> APT::CacheFilter::ParsePattern(std::string_view 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 << "\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