#include #include #include #include #include #include #include namespace tt::app { constexpr auto* TAG = "AppManifest"; constexpr bool validateString(const std::string& value, const std::function& isValidChar) { return std::ranges::all_of(value, isValidChar); } bool getValueFromManifest(const std::map& map, const std::string& key, std::string& output) { const auto iterator = map.find(key); if (iterator == map.end()) { LOG_E(TAG, "Failed to find %s in manifest", key.c_str()); return false; } output = iterator->second; return true; } bool isValidId(const std::string& id) { return id.size() >= 5 && validateString(id, [](const char c) { return std::isalnum(c) != 0 || c == '.'; }); } bool isValidManifestVersion(const std::string& version) { return !version.empty() && validateString(version, [](const char c) { return std::isalnum(c) != 0 || c == '.'; }); } bool isValidAppVersionName(const std::string& version) { return !version.empty() && validateString(version, [](const char c) { return std::isalnum(c) != 0 || c == '.' || c == '-' || c == '_'; }); } bool isValidAppVersionCode(const std::string& version) { return !version.empty() && validateString(version, [](const char c) { return std::isdigit(c) != 0; }); } bool isValidName(const std::string& name) { return name.size() >= 2 && validateString(name, [](const char c) { return std::isalnum(c) != 0 || c == ' ' || c == '-'; }); } uint16_t parseAppFlagsString(const std::string& raw) { uint16_t flags = AppManifest::Flags::None; if (raw.empty()) { return flags; } auto parts = string::split(raw, ","); for (auto& part : parts) { std::string trimmed = string::trim(part, " \t\r\n"); if (trimmed.empty()) { continue; } std::string lower = string::lowercase(trimmed); if (lower == "hidestatusbar" || lower == "hide_statusbar" || lower == "hide-statusbar" || lower == "hide_status_bar") { flags |= AppManifest::Flags::HideStatusBar; } else if (lower == "hidden") { flags |= AppManifest::Flags::Hidden; } else if (lower == "none" || lower == "0" || lower == "") { // keep as none, no additional flag } else { LOG_W(TAG, "Unknown app flag \"%s\" - ignoring", trimmed.c_str()); } } return flags; } /** The V1 format's first line is always the literal "[manifest]" section header; V2 files are flat from the first line onward. */ static bool detectIsV1Format(const std::string& filePath) { std::string first_line; bool got_first_line = false; file::readLines(filePath, true, [&first_line, &got_first_line](const char* line) { if (!got_first_line) { first_line = string::trim(std::string(line), " \t\r\n"); got_first_line = true; } }); return first_line == "[manifest]"; } bool parseManifest(const std::string& filePath, AppManifest& manifest) { LOG_I(TAG, "Parsing manifest %s", filePath.c_str()); bool is_v1_format = detectIsV1Format(filePath); std::map properties; if (!file::loadPropertiesFile(filePath, properties)) { LOG_E(TAG, "Failed to load manifest at %s", filePath.c_str()); return false; } bool success = is_v1_format ? parseManifestV1(properties, manifest) : parseManifestV2(properties, manifest); if (!success) { return false; } manifest.appCategory = Category::User; manifest.appLocation = Location::external(""); return true; } }