diff --git a/libpkg/data/package.cpp b/libpkg/data/package.cpp index b4d32de..ff3ae9c 100644 --- a/libpkg/data/package.cpp +++ b/libpkg/data/package.cpp @@ -443,6 +443,84 @@ bool Package::isArchAny() const return true; } +static bool containsUnexpectedCharacters(std::string_view value) +{ + for (auto c : value) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { + continue; + } + switch (c) { + case '+': + case '-': + case '_': + case '.': + continue; + default: + return true; + } + } + return false; +} + +static bool containsUnprintableCharacters(std::string_view value) +{ + for (auto c : value) { + if (c < ' ' || c >= 127) { + return true; + } + } + return false; +} + +#define CHECK_FIELD_EMPTY(field) \ + if (field.empty()) { \ + problems.emplace_back(#field " is empty"); \ + } +#define CHECK_FIELD_FOR_UNEXPECTED_CHARS(field) \ + if (containsUnexpectedCharacters(field)) { \ + problems.emplace_back(#field " contains unexpected characters"); \ + } +#define CHECK_FIELD_FOR_UNPRINTABLE_CHARS(field) \ + if (containsUnprintableCharacters(field)) { \ + problems.emplace_back(#field " contains unprintable or non-ASCII characters"); \ + } +#define CHECK_FIELD_STRICT(field) \ + CHECK_FIELD_EMPTY(field) \ + CHECK_FIELD_FOR_UNEXPECTED_CHARS(field) +#define CHECK_FIELD_RELAXED(field) \ + CHECK_FIELD_EMPTY(field) \ + CHECK_FIELD_FOR_UNPRINTABLE_CHARS(field) + +/*! + * \brief Performs a basic sanity check of the package's fields. + * \returns Returns an empty vector if no problems were found; otherwise returns the problem descriptions. + */ +std::vector Package::validate() const +{ + // check basic fields + auto problems = std::vector(); + CHECK_FIELD_STRICT(name) + CHECK_FIELD_RELAXED(version) + CHECK_FIELD_STRICT(arch) + + // check dependencies + const auto checkDeps = sourceInfo ? sourceInfo->checkDependencies : std::vector(); + const auto makeDeps = sourceInfo ? sourceInfo->makeDependencies : std::vector(); + for (const auto &deps : { dependencies, optionalDependencies, provides, conflicts, replaces, checkDeps, makeDeps }) { + for (const auto &dep : deps) { + if (dep.name.empty()) { + problems.emplace_back("dependency is empty"); + return problems; + } + if (containsUnexpectedCharacters(dep.name)) { + problems.emplace_back("dependency contains unexpected characters"); + return problems; + } + } + } + return problems; +} + DependencySetBase::iterator DependencySet::find(const Dependency &dependency) { for (auto range = equal_range(dependency.name); range.first != range.second; ++range.first) { diff --git a/libpkg/data/package.h b/libpkg/data/package.h index 828ad7e..ecb9a93 100644 --- a/libpkg/data/package.h +++ b/libpkg/data/package.h @@ -417,6 +417,7 @@ struct LIBPKG_EXPORT Package : public PackageBase, std::vector processDllsReferencedByImportLibs(std::set &&dllsReferencedByImportLibs); bool addDepsAndProvidesFromOtherPackage(const Package &otherPackage, bool force = false); bool isArchAny() const; + std::vector validate() const; using ReflectiveRapidJSON::JsonSerializable::fromJson; using ReflectiveRapidJSON::JsonSerializable::toJson; using ReflectiveRapidJSON::JsonSerializable::toJsonDocument; diff --git a/librepomgr/buildactions/repomanagement.cpp b/librepomgr/buildactions/repomanagement.cpp index c727d28..a20a36b 100644 --- a/librepomgr/buildactions/repomanagement.cpp +++ b/librepomgr/buildactions/repomanagement.cpp @@ -466,6 +466,10 @@ void CheckForProblems::run() RepositoryProblem{ .desc = "configured local package directory \"" % db->localPkgDir + "\" is not a directory" }); } db->allPackages([&](LibPkg::StorageID, std::shared_ptr &&package) { + const auto packageProblems = package->validate(); + for (const auto &problem : packageProblems) { + problems.emplace_back(RepositoryProblem{ .desc = problem, .pkg = package->name }); + } if (!package->packageInfo) { problems.emplace_back(RepositoryProblem{ .desc = "no package info present", .pkg = package->name }); return false;