From 24db645167b42adba5d9c221215be83bef39e2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Wed, 3 Jun 2015 21:57:22 +0200 Subject: NOISSUE sanitize Json Removes magical parameter madness. All require* can throw All ensure* need a default value and never throw --- logic/Json.cpp | 55 +++++++------- logic/Json.h | 119 ++++++++++++++++++------------- logic/minecraft/MinecraftVersionList.cpp | 14 ++-- logic/minecraft/ProfileUtils.cpp | 8 +-- logic/minecraft/RawLibrary.cpp | 10 +-- logic/minecraft/VersionFile.cpp | 42 +++++------ 6 files changed, 130 insertions(+), 118 deletions(-) diff --git a/logic/Json.cpp b/logic/Json.cpp index 97bab474..f2cbc8a3 100644 --- a/logic/Json.cpp +++ b/logic/Json.cpp @@ -44,7 +44,7 @@ static bool isBinaryJson(const QByteArray &data) decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; } -QJsonDocument ensureDocument(const QByteArray &data, const QString &what) +QJsonDocument requireDocument(const QByteArray &data, const QString &what) { if (isBinaryJson(data)) { @@ -66,11 +66,11 @@ QJsonDocument ensureDocument(const QByteArray &data, const QString &what) return doc; } } -QJsonDocument ensureDocument(const QString &filename, const QString &what) +QJsonDocument requireDocument(const QString &filename, const QString &what) { - return ensureDocument(FS::read(filename), what); + return requireDocument(FS::read(filename), what); } -QJsonObject ensureObject(const QJsonDocument &doc, const QString &what) +QJsonObject requireObject(const QJsonDocument &doc, const QString &what) { if (!doc.isObject()) { @@ -78,7 +78,7 @@ QJsonObject ensureObject(const QJsonDocument &doc, const QString &what) } return doc.object(); } -QJsonArray ensureArray(const QJsonDocument &doc, const QString &what) +QJsonArray requireArray(const QJsonDocument &doc, const QString &what) { if (!doc.isArray()) { @@ -140,10 +140,9 @@ QJsonValue toJson(const QVariant &variant) } -template<> QByteArray ensureIsType(const QJsonValue &value, const Requirement, - const QString &what) +template<> QByteArray requireIsType(const QJsonValue &value, const QString &what) { - const QString string = ensureIsType(value, Required, what); + const QString string = ensureIsType(value, what); // ensure that the string can be safely cast to Latin1 if (string != QString::fromLatin1(string.toLatin1())) { @@ -152,7 +151,7 @@ template<> QByteArray ensureIsType(const QJsonValue &value, const Re return QByteArray::fromHex(string.toLatin1()); } -template<> QJsonArray ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +template<> QJsonArray requireIsType(const QJsonValue &value, const QString &what) { if (!value.isArray()) { @@ -162,7 +161,7 @@ template<> QJsonArray ensureIsType(const QJsonValue &value, const Re } -template<> QString ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +template<> QString requireIsType(const QJsonValue &value, const QString &what) { if (!value.isString()) { @@ -171,8 +170,7 @@ template<> QString ensureIsType(const QJsonValue &value, const Requirem return value.toString(); } -template<> bool ensureIsType(const QJsonValue &value, const Requirement, - const QString &what) +template<> bool requireIsType(const QJsonValue &value, const QString &what) { if (!value.isBool()) { @@ -181,8 +179,7 @@ template<> bool ensureIsType(const QJsonValue &value, const Requirement, return value.toBool(); } -template<> double ensureIsType(const QJsonValue &value, const Requirement, - const QString &what) +template<> double requireIsType(const QJsonValue &value, const QString &what) { if (!value.isDouble()) { @@ -191,10 +188,9 @@ template<> double ensureIsType(const QJsonValue &value, const Requiremen return value.toDouble(); } -template<> int ensureIsType(const QJsonValue &value, const Requirement, - const QString &what) +template<> int requireIsType(const QJsonValue &value, const QString &what) { - const double doubl = ensureIsType(value, Required, what); + const double doubl = requireIsType(value, what); if (fmod(doubl, 1) != 0) { throw JsonException(what + " is not an integer"); @@ -202,10 +198,9 @@ template<> int ensureIsType(const QJsonValue &value, const Requirement, return int(doubl); } -template<> QDateTime ensureIsType(const QJsonValue &value, const Requirement, - const QString &what) +template<> QDateTime requireIsType(const QJsonValue &value, const QString &what) { - const QString string = ensureIsType(value, Required, what); + const QString string = requireIsType(value, what); const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate); if (!datetime.isValid()) { @@ -214,10 +209,9 @@ template<> QDateTime ensureIsType(const QJsonValue &value, const Requ return datetime; } -template<> QUrl ensureIsType(const QJsonValue &value, const Requirement, - const QString &what) +template<> QUrl requireIsType(const QJsonValue &value, const QString &what) { - const QString string = ensureIsType(value, Required, what); + const QString string = ensureIsType(value, what); if (string.isEmpty()) { return QUrl(); @@ -230,15 +224,16 @@ template<> QUrl ensureIsType(const QJsonValue &value, const Requirement, return url; } -template<> QDir ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +template<> QDir requireIsType(const QJsonValue &value, const QString &what) { - const QString string = ensureIsType(value, Required, what); + const QString string = requireIsType(value, what); + // FIXME: does not handle invalid characters! return QDir::current().absoluteFilePath(string); } -template<> QUuid ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +template<> QUuid requireIsType(const QJsonValue &value, const QString &what) { - const QString string = ensureIsType(value, Required, what); + const QString string = requireIsType(value, what); const QUuid uuid = QUuid(string); if (uuid.toString() != string) // converts back => valid { @@ -247,7 +242,7 @@ template<> QUuid ensureIsType(const QJsonValue &value, const Requirement, return uuid; } -template<> QJsonObject ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +template<> QJsonObject requireIsType(const QJsonValue &value, const QString &what) { if (!value.isObject()) { @@ -256,7 +251,7 @@ template<> QJsonObject ensureIsType(const QJsonValue &value, const return value.toObject(); } -template<> QVariant ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +template<> QVariant requireIsType(const QJsonValue &value, const QString &what) { if (value.isNull() || value.isUndefined()) { @@ -265,7 +260,7 @@ template<> QVariant ensureIsType(const QJsonValue &value, const Requir return value.toVariant(); } -template<> QJsonValue ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +template<> QJsonValue requireIsType(const QJsonValue &value, const QString &what) { if (value.isNull() || value.isUndefined()) { diff --git a/logic/Json.h b/logic/Json.h index 9f4c5445..55f0cfc1 100644 --- a/logic/Json.h +++ b/logic/Json.h @@ -18,11 +18,6 @@ namespace Json { DECLARE_EXCEPTION(Json); -enum Requirement -{ - Required -}; - /// @throw FileSystemException void write(const QJsonDocument &doc, const QString &filename); /// @throw FileSystemException @@ -36,13 +31,13 @@ QByteArray toText(const QJsonObject &obj); QByteArray toText(const QJsonArray &array); /// @throw JsonException -QJsonDocument ensureDocument(const QByteArray &data, const QString &what = "Document"); +QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document"); /// @throw JsonException -QJsonDocument ensureDocument(const QString &filename, const QString &what = "Document"); +QJsonDocument requireDocument(const QString &filename, const QString &what = "Document"); /// @throw JsonException -QJsonObject ensureObject(const QJsonDocument &doc, const QString &what = "Document"); +QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document"); /// @throw JsonException -QJsonArray ensureArray(const QJsonDocument &doc, const QString &what = "Document"); +QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document"); /////////////////// WRITING //////////////////// @@ -107,22 +102,36 @@ QJsonArray toJsonArray(const QList &container) ////////////////// READING //////////////////// +/// @throw JsonException template -T ensureIsType(const QJsonValue &value, const Requirement requirement = Required, const QString &what = "Value"); - -template<> double ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> bool ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> int ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QJsonObject ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QJsonArray ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QJsonValue ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QByteArray ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QDateTime ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QVariant ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QString ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QUuid ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QDir ensureIsType(const QJsonValue &value, const Requirement, const QString &what); -template<> QUrl ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +T requireIsType(const QJsonValue &value, const QString &what = "Value"); + +/// @throw JsonException +template<> double requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> bool requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> int requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QJsonObject requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QJsonArray requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QJsonValue requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QByteArray requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QDateTime requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QVariant requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QString requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QUuid requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QDir requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QUrl requireIsType(const QJsonValue &value, const QString &what); // the following functions are higher level functions, that make use of the above functions for // type conversion @@ -133,26 +142,30 @@ T ensureIsType(const QJsonValue &value, const T default_, const QString &what = { return default_; } - return ensureIsType(value, Required, what); + try + { + return requireIsType(value, what); + } + catch (JsonException &) + { + return default_; + } } /// @throw JsonException template -T ensureIsType(const QJsonObject &parent, const QString &key, - const Requirement requirement = Required, - const QString &what = "__placeholder__") +T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") { const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); if (!parent.contains(key)) { throw JsonException(localWhat + "s parent does not contain " + localWhat); } - return ensureIsType(parent.value(key), requirement, localWhat); + return requireIsType(parent.value(key), localWhat); } template -T ensureIsType(const QJsonObject &parent, const QString &key, const T default_, - const QString &what = "__placeholder__") +T ensureIsType(const QJsonObject &parent, const QString &key, const T default_, const QString &what = "__placeholder__") { const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); if (!parent.contains(key)) @@ -163,53 +176,49 @@ T ensureIsType(const QJsonObject &parent, const QString &key, const T default_, } template -QList ensureIsArrayOf(const QJsonDocument &doc) +QList requireIsArrayOf(const QJsonDocument &doc) { - const QJsonArray array = ensureArray(doc); + const QJsonArray array = requireArray(doc); QList out; for (const QJsonValue val : array) { - out.append(ensureIsType(val, Required, "Document")); + out.append(requireIsType(val, "Document")); } return out; } template -QList ensureIsArrayOf(const QJsonValue &value, const Requirement = Required, - const QString &what = "Value") +QList ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value") { - const QJsonArray array = ensureIsType(value, Required, what); + const QJsonArray array = requireIsType(value, what); QList out; for (const QJsonValue val : array) { - out.append(ensureIsType(val, Required, what)); + out.append(ensureIsType(val, what)); } return out; } template -QList ensureIsArrayOf(const QJsonValue &value, const QList default_, - const QString &what = "Value") +QList ensureIsArrayOf(const QJsonValue &value, const QList default_, const QString &what = "Value") { if (value.isUndefined()) { return default_; } - return ensureIsArrayOf(value, Required, what); + return ensureIsArrayOf(value, what); } /// @throw JsonException template -QList ensureIsArrayOf(const QJsonObject &parent, const QString &key, - const Requirement requirement = Required, - const QString &what = "__placeholder__") +QList requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") { const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); if (!parent.contains(key)) { throw JsonException(localWhat + "s parent does not contain " + localWhat); } - return ensureIsArrayOf(parent.value(key), requirement, localWhat); + return requireIsArrayOf(parent.value(key), localWhat); } template @@ -226,14 +235,22 @@ QList ensureIsArrayOf(const QJsonObject &parent, const QString &key, // this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers #define JSON_HELPERFUNCTIONS(NAME, TYPE) \ - inline TYPE ensure##NAME(const QJsonValue &value, const Requirement requirement = Required, const QString &what = "Value") \ -{ return ensureIsType(value, requirement, what); } \ + inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \ + { \ + return requireIsType(value, what); \ + } \ inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_, const QString &what = "Value") \ -{ return ensureIsType(value, default_, what); } \ - inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const Requirement requirement = Required, const QString &what = "__placeholder__") \ -{ return ensureIsType(parent, key, requirement, what); } \ + { \ + return ensureIsType(value, default_, what); \ + } \ + inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \ + { \ + return requireIsType(parent, key, what); \ + } \ inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_, const QString &what = "__placeholder") \ -{ return ensureIsType(parent, key, default_, what); } + { \ + return ensureIsType(parent, key, default_, what); \ + } JSON_HELPERFUNCTIONS(Array, QJsonArray) JSON_HELPERFUNCTIONS(Object, QJsonObject) diff --git a/logic/minecraft/MinecraftVersionList.cpp b/logic/minecraft/MinecraftVersionList.cpp index 44be281b..8ba51b99 100644 --- a/logic/minecraft/MinecraftVersionList.cpp +++ b/logic/minecraft/MinecraftVersionList.cpp @@ -157,11 +157,11 @@ void MinecraftVersionList::loadBuiltinList() qDebug() << "Loading builtin version list."; // grab the version list data from internal resources. const QJsonDocument doc = - Json::ensureDocument(QString(":/versions/minecraft.json"), "builtin version list"); + Json::requireDocument(QString(":/versions/minecraft.json"), "builtin version list"); const QJsonObject root = doc.object(); // parse all the versions - for (const auto version : Json::ensureArray(root.value("versions"))) + for (const auto version : Json::requireArray(root.value("versions"))) { QJsonObject versionObj = version.toObject(); QString versionID = versionObj.value("id").toString(""); @@ -203,9 +203,9 @@ void MinecraftVersionList::loadBuiltinList() mcVersion->m_processArguments = versionObj.value("processArguments").toString("legacy"); if (versionObj.contains("+traits")) { - for (auto traitVal : Json::ensureArray(versionObj.value("+traits"))) + for (auto traitVal : Json::requireArray(versionObj.value("+traits"))) { - mcVersion->m_traits.insert(Json::ensureString(traitVal)); + mcVersion->m_traits.insert(Json::requireString(traitVal)); } } m_lookup[versionID] = mcVersion; @@ -226,9 +226,9 @@ void MinecraftVersionList::loadMojangList(QJsonDocument jsonDoc, VersionSource s try { - QJsonObject latest = Json::ensureObject(root.value("latest")); - m_latestReleaseID = Json::ensureString(latest.value("release")); - m_latestSnapshotID = Json::ensureString(latest.value("snapshot")); + QJsonObject latest = Json::requireObject(root.value("latest")); + m_latestReleaseID = Json::requireString(latest.value("release")); + m_latestSnapshotID = Json::requireString(latest.value("snapshot")); } catch (Exception &err) { diff --git a/logic/minecraft/ProfileUtils.cpp b/logic/minecraft/ProfileUtils.cpp index 68fe0f14..9a886f1d 100644 --- a/logic/minecraft/ProfileUtils.cpp +++ b/logic/minecraft/ProfileUtils.cpp @@ -74,18 +74,18 @@ bool readOverrideOrders(QString path, PatchOrder &order) // and then read it and process it if all above is true. try { - auto obj = Json::ensureObject(doc); + auto obj = Json::requireObject(doc); // check order file version. - auto version = Json::ensureInteger(obj.value("version")); + auto version = Json::requireInteger(obj.value("version")); if (version != currentOrderFileVersion) { throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") .arg(currentOrderFileVersion)); } - auto orderArray = Json::ensureArray(obj.value("order")); + auto orderArray = Json::requireArray(obj.value("order")); for(auto item: orderArray) { - order.append(Json::ensureString(item)); + order.append(Json::requireString(item)); } } catch (JSONValidationError &err) diff --git a/logic/minecraft/RawLibrary.cpp b/logic/minecraft/RawLibrary.cpp index 90883312..ceaa9dd0 100644 --- a/logic/minecraft/RawLibrary.cpp +++ b/logic/minecraft/RawLibrary.cpp @@ -39,15 +39,15 @@ RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &fil if (libObj.contains("extract")) { out->applyExcludes = true; - auto extractObj = ensureObject(libObj.value("extract")); - for (auto excludeVal : ensureArray(extractObj.value("exclude"))) + auto extractObj = requireObject(libObj.value("extract")); + for (auto excludeVal : requireArray(extractObj.value("exclude"))) { - out->extract_excludes.append(ensureString(excludeVal)); + out->extract_excludes.append(requireString(excludeVal)); } } if (libObj.contains("natives")) { - QJsonObject nativesObj = ensureObject(libObj.value("natives")); + QJsonObject nativesObj = requireObject(libObj.value("natives")); for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) { if (!it.value().isString()) @@ -127,7 +127,7 @@ RawLibraryPtr RawLibrary::fromJsonPlus(const QJsonObject &libObj, const QString } if (libObj.contains("MMC-depend")) { - const QString dependString = ensureString(libObj.value("MMC-depend")); + const QString dependString = requireString(libObj.value("MMC-depend")); if (dependString == "hard") { lib->dependType = RawLibrary::Hard; diff --git a/logic/minecraft/VersionFile.cpp b/logic/minecraft/VersionFile.cpp index 426cba8c..24f2eb9c 100644 --- a/logic/minecraft/VersionFile.cpp +++ b/logic/minecraft/VersionFile.cpp @@ -52,7 +52,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi { if (root.contains("order")) { - out->order = ensureInteger(root.value("order")); + out->order = requireInteger(root.value("order")); } else { @@ -71,7 +71,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi { if (root.contains(key)) { - variable = ensureString(root.value(key)); + variable = requireString(root.value(key)); } }; @@ -79,7 +79,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi { if (root.contains(key)) { - return ensureString(root.value(key)); + return requireString(root.value(key)); } return QString(); }; @@ -101,48 +101,48 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi if (root.contains("minimumLauncherVersion")) { - out->minimumLauncherVersion = ensureInteger(root.value("minimumLauncherVersion")); + out->minimumLauncherVersion = requireInteger(root.value("minimumLauncherVersion")); } if (root.contains("tweakers")) { out->shouldOverwriteTweakers = true; - for (auto tweakerVal : ensureArray(root.value("tweakers"))) + for (auto tweakerVal : requireArray(root.value("tweakers"))) { - out->overwriteTweakers.append(ensureString(tweakerVal)); + out->overwriteTweakers.append(requireString(tweakerVal)); } } if (root.contains("+tweakers")) { - for (auto tweakerVal : ensureArray(root.value("+tweakers"))) + for (auto tweakerVal : requireArray(root.value("+tweakers"))) { - out->addTweakers.append(ensureString(tweakerVal)); + out->addTweakers.append(requireString(tweakerVal)); } } if (root.contains("-tweakers")) { - for (auto tweakerVal : ensureArray(root.value("-tweakers"))) + for (auto tweakerVal : requireArray(root.value("-tweakers"))) { - out->removeTweakers.append(ensureString(tweakerVal)); + out->removeTweakers.append(requireString(tweakerVal)); } } if (root.contains("+traits")) { - for (auto tweakerVal : ensureArray(root.value("+traits"))) + for (auto tweakerVal : requireArray(root.value("+traits"))) { - out->traits.insert(ensureString(tweakerVal)); + out->traits.insert(requireString(tweakerVal)); } } if (root.contains("libraries")) { out->shouldOverwriteLibs = true; - for (auto libVal : ensureArray(root.value("libraries"))) + for (auto libVal : requireArray(root.value("libraries"))) { - auto libObj = ensureObject(libVal); + auto libObj = requireObject(libVal); auto lib = RawLibrary::fromJson(libObj, filename); out->overwriteLibs.append(lib); @@ -151,9 +151,9 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi if (root.contains("+jarMods")) { - for (auto libVal : ensureArray(root.value("+jarMods"))) + for (auto libVal : requireArray(root.value("+jarMods"))) { - QJsonObject libObj = ensureObject(libVal); + QJsonObject libObj = requireObject(libVal); // parse the jarmod auto lib = Jarmod::fromJson(libObj, filename, out->name); if(lib->originalName.isEmpty()) @@ -169,9 +169,9 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi if (root.contains("+libraries")) { - for (auto libVal : ensureArray(root.value("+libraries"))) + for (auto libVal : requireArray(root.value("+libraries"))) { - QJsonObject libObj = ensureObject(libVal); + QJsonObject libObj = requireObject(libVal); // parse the library auto lib = RawLibrary::fromJsonPlus(libObj, filename); out->addLibs.append(lib); @@ -180,10 +180,10 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi if (root.contains("-libraries")) { - for (auto libVal : ensureArray(root.value("-libraries"))) + for (auto libVal : requireArray(root.value("-libraries"))) { - auto libObj = ensureObject(libVal); - out->removeLibs.append(ensureString(libObj.value("name"))); + auto libObj = requireObject(libVal); + out->removeLibs.append(requireString(libObj.value("name"))); } } return out; -- cgit v1.2.3