From ce626e5e919cabb32a7fa1be35025837196b8204 Mon Sep 17 00:00:00 2001 From: Ivan Komissarov Date: Tue, 16 May 2023 01:32:37 +0300 Subject: js: represent QByteArray as JsArrayBuffer On macOS, Info.plist can (rarely) contain binary data which is parsed into a QByteArray. However, since byte arrays were discarded when converting from variant, such keys were lost e.g. when doing plist merging. Fix that by converting QByteArray to a JS ArrayBuffer object. Using such types is a bit awkward as seen in the testcase, but conforms JS type system. Change-Id: I7a680aa7943ba3bde1ddf4ac84e3485fb0ba01d8 Reviewed-by: Christian Kandeler --- src/lib/corelib/language/scriptengine.cpp | 6 ++++ src/lib/corelib/language/scriptengine.h | 1 + src/lib/corelib/tools/scripttools.cpp | 17 ++++++++-- src/lib/corelib/tools/scripttools.h | 1 + src/shared/quickjs/quickjs.c | 8 +++++ src/shared/quickjs/quickjs.h | 1 + .../byteArrayInfoPlist/ByteArray-Info.plist | 11 +++++++ .../byteArrayInfoPlist/byteArrayInfoPlist.qbs | 37 ++++++++++++++++++++++ .../testdata-apple/byteArrayInfoPlist/main.c | 1 + tests/auto/blackbox/tst_blackboxapple.cpp | 17 ++++++++++ tests/auto/blackbox/tst_blackboxapple.h | 1 + 11 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/ByteArray-Info.plist create mode 100644 tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/byteArrayInfoPlist.qbs create mode 100644 tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/main.c diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp index 58c2817c6..6a67c1228 100644 --- a/src/lib/corelib/language/scriptengine.cpp +++ b/src/lib/corelib/language/scriptengine.cpp @@ -481,6 +481,12 @@ void ScriptEngine::addInternalExtension(const char *name, JSValue ext) m_internalExtensions.insert(QLatin1String(name), JS_DupValue(m_context, ext)); } +JSValue ScriptEngine::asJsValue(const QByteArray &s) +{ + return JS_NewArrayBufferCopy( + m_context, reinterpret_cast(s.constData()), s.size()); +} + JSValue ScriptEngine::asJsValue(const QString &s) { const auto it = m_stringCache.constFind(s); diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h index 8c97e3079..4d797dd43 100644 --- a/src/lib/corelib/language/scriptengine.h +++ b/src/lib/corelib/language/scriptengine.h @@ -283,6 +283,7 @@ public: JSValue getInternalExtension(const char *name) const; void addInternalExtension(const char *name, JSValue ext); + JSValue asJsValue(const QByteArray &s); JSValue asJsValue(const QString &s); JSValue asJsValue(const QStringList &l); JSValue asJsValue(const QVariantList &l); diff --git a/src/lib/corelib/tools/scripttools.cpp b/src/lib/corelib/tools/scripttools.cpp index 109b74486..4953bbbb5 100644 --- a/src/lib/corelib/tools/scripttools.cpp +++ b/src/lib/corelib/tools/scripttools.cpp @@ -179,6 +179,11 @@ bool getJsBoolProperty(JSContext *ctx, JSValue obj, const QString &prop) return JS_VALUE_GET_BOOL(getJsProperty(ctx, obj, prop)); } +JSValue makeJsArrayBuffer(JSContext *ctx, const QByteArray &s) +{ + return ScriptEngine::engineForContext(ctx)->asJsValue(s); +} + JSValue makeJsString(JSContext *ctx, const QString &s) { return ScriptEngine::engineForContext(ctx)->asJsValue(s); @@ -212,6 +217,8 @@ QStringList getJsStringList(JSContext *ctx, JSValue val) JSValue makeJsVariant(JSContext *ctx, const QVariant &v) { switch (static_cast(v.userType())) { + case QMetaType::QByteArray: + return makeJsArrayBuffer(ctx, v.toByteArray()); case QMetaType::QString: return makeJsString(ctx, v.toString()); case QMetaType::QStringList: @@ -230,9 +237,6 @@ JSValue makeJsVariant(JSContext *ctx, const QVariant &v) return JS_NewBool(ctx, v.toBool()); case QMetaType::QVariantMap: return makeJsVariantMap(ctx, v.toMap()); - case QMetaType::QByteArray: - QBS_ASSERT(!"QByteArray is not a valid type for JS variant", return JS_UNDEFINED); - [[fallthrough]]; default: return JS_UNDEFINED; } @@ -254,6 +258,13 @@ static QVariant getJsVariantImpl(JSContext *ctx, JSValue val, QList pat return getJsString(ctx, val); if (JS_IsBool(val)) return bool(JS_VALUE_GET_BOOL(val)); + if (JS_IsArrayBuffer(val)) { + size_t size = 0; + const auto data = JS_GetArrayBuffer(ctx, &size, val); + if (!data || !size) + return QByteArray(); + return QByteArray(reinterpret_cast(data), size); + } if (JS_IsArray(ctx, val)) { if (path.contains(val)) return {}; diff --git a/src/lib/corelib/tools/scripttools.h b/src/lib/corelib/tools/scripttools.h index 1af7b2b9e..ea7993485 100644 --- a/src/lib/corelib/tools/scripttools.h +++ b/src/lib/corelib/tools/scripttools.h @@ -72,6 +72,7 @@ QVariant getJsVariantProperty(JSContext *ctx, JSValueConst obj, const QString &p QString getJsString(JSContext *ctx, JSValueConst val); QString getJsString(JSContext *ctx, JSAtom atom); QBS_AUTOTEST_EXPORT QVariant getJsVariant(JSContext *ctx, JSValueConst val); +JSValue makeJsArrayBuffer(JSContext *ctx, const QByteArray &s); JSValue makeJsString(JSContext *ctx, const QString &s); JSValue makeJsStringList(JSContext *ctx, const QStringList &l); JSValue makeJsVariant(JSContext *ctx, const QVariant &v); diff --git a/src/shared/quickjs/quickjs.c b/src/shared/quickjs/quickjs.c index f90fb9e4f..5dee09b93 100644 --- a/src/shared/quickjs/quickjs.c +++ b/src/shared/quickjs/quickjs.c @@ -9822,6 +9822,14 @@ BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, BOOL val) return TRUE; } +JS_BOOL JS_IsArrayBuffer(JSValueConst v) +{ + if (!JS_IsObject(v)) + return FALSE; + JSObject *p = JS_VALUE_GET_OBJ(v); + return p->class_id == JS_CLASS_ARRAY_BUFFER || p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER; +} + BOOL JS_IsError(JSContext *ctx, JSValueConst val) { JSObject *p; diff --git a/src/shared/quickjs/quickjs.h b/src/shared/quickjs/quickjs.h index a5adf0cd2..73ba9a58e 100644 --- a/src/shared/quickjs/quickjs.h +++ b/src/shared/quickjs/quickjs.h @@ -665,6 +665,7 @@ JS_BOOL JS_IsFunction(JSContext* ctx, JSValueConst val); JS_BOOL JS_IsRegExp(JSContext* ctx, JSValueConst val); JS_BOOL JS_IsConstructor(JSContext* ctx, JSValueConst val); JS_BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, JS_BOOL val); +JS_BOOL JS_IsArrayBuffer(JSValueConst v); JSValue JS_NewArray(JSContext *ctx); int JS_IsArray(JSContext *ctx, JSValueConst val); diff --git a/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/ByteArray-Info.plist b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/ByteArray-Info.plist new file mode 100644 index 000000000..df0429f25 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/ByteArray-Info.plist @@ -0,0 +1,11 @@ + + + + + DataKey + + VGhlIGRhdGEgdmFsdWU= + StringKey + The string value + + diff --git a/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/byteArrayInfoPlist.qbs b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/byteArrayInfoPlist.qbs new file mode 100644 index 000000000..4df0886ff --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/byteArrayInfoPlist.qbs @@ -0,0 +1,37 @@ +import qbs.BundleTools +import qbs.TextFile + +CppApplication { + Depends { name: "bundle" } + cpp.minimumMacosVersion: "10.7" + files: ["main.c", "ByteArray-Info.plist"] + type: base.concat(["txt_output"]) + + Properties { + condition: qbs.targetOS.includes("darwin") + bundle.isBundle: true + bundle.identifierPrefix: "com.test" + } + + Rule { + inputs: ["aggregate_infoplist"] + Artifact { + filePath: input.fileName + ".out" + fileTags: ["txt_output"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating" + output.fileName + " from " + input.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + var plist = new BundleTools.infoPlistContents(input.filePath); + var content = plist["DataKey"]; + var int8view = new Uint8Array(content); + file = new TextFile(output.filePath, TextFile.WriteOnly); + file.write(String.fromCharCode.apply(null, int8view)); + file.close(); + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/main.c b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/main.c new file mode 100644 index 000000000..76e819701 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/main.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/tst_blackboxapple.cpp b/tests/auto/blackbox/tst_blackboxapple.cpp index 5de371792..01446c815 100644 --- a/tests/auto/blackbox/tst_blackboxapple.cpp +++ b/tests/auto/blackbox/tst_blackboxapple.cpp @@ -684,6 +684,23 @@ void TestBlackboxApple::bundleStructure_data() QTest::newRow("G") << "G" << "com.apple.product-type.in-app-purchase-content"; } +void TestBlackboxApple::byteArrayInfoPlist() +{ + QDir::setCurrent(testDataDir + "/byteArrayInfoPlist"); + + QCOMPARE(runQbs(), 0); + + const auto infoPlistPath = getInfoPlistPath( + relativeProductBuildDir("byteArrayInfoPlist") + "/byteArrayInfoPlist.app"); + QVERIFY(QFile::exists(infoPlistPath)); + const auto outFilePath = + relativeProductBuildDir("byteArrayInfoPlist") + "/bytearrayInfoPlist-Info.plist.out"; + QFile file(outFilePath); + QVERIFY(file.exists()); + QVERIFY(file.open(QIODevice::ReadOnly)); + QCOMPARE(file.readAll(), "The data value"); +} + void TestBlackboxApple::codesign() { QFETCH(bool, isBundle); diff --git a/tests/auto/blackbox/tst_blackboxapple.h b/tests/auto/blackbox/tst_blackboxapple.h index 32eee2432..9c329e961 100644 --- a/tests/auto/blackbox/tst_blackboxapple.h +++ b/tests/auto/blackbox/tst_blackboxapple.h @@ -55,6 +55,7 @@ private slots: void assetCatalogsMultiple(); void bundleStructure(); void bundleStructure_data(); + void byteArrayInfoPlist(); void codesign(); void codesign_data(); void deploymentTarget(); -- cgit v1.2.1