feat(client): improve SDK packaging and registry config
This commit is contained in:
+32
-31
@@ -1,8 +1,9 @@
|
||||
#include "UpdaterLogic.h"
|
||||
#include <QSet>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QCoreApplication>
|
||||
#include <QEventLoop>
|
||||
#include <QElapsedTimer>
|
||||
#include <QThread>
|
||||
@@ -341,45 +342,45 @@ bool UpdaterLogic::loadOfflinePackage(const QString& packagePath, const QString&
|
||||
{
|
||||
m_offlineError.clear();
|
||||
QFile package(packagePath);
|
||||
if (!package.open(QIODevice::ReadOnly)) { m_offlineError = "无法打开离线更新包"; return false; }
|
||||
if (package.read(8) != QByteArray("MUPD0001", 8)) { m_offlineError = "离线包格式标识无效"; return false; }
|
||||
const QByteArray lengthBytes = package.read(8);
|
||||
if (lengthBytes.size() != 8) { m_offlineError = "离线包头不完整"; return false; }
|
||||
quint64 headerSize = 0;
|
||||
for (int i = 0; i < 8; ++i) headerSize |= quint64(static_cast<unsigned char>(lengthBytes[i])) << (i * 8);
|
||||
if (headerSize == 0 || headerSize > 64ULL * 1024 * 1024 || headerSize > quint64(package.size() - 16)) { m_offlineError = "离线包头长度无效"; return false; }
|
||||
QJsonParseError error;
|
||||
const QJsonDocument wrapperDoc = QJsonDocument::fromJson(package.read(qint64(headerSize)), &error);
|
||||
if (error.error != QJsonParseError::NoError || !wrapperDoc.isObject()) { m_offlineError = "离线包头 JSON 无效"; return false; }
|
||||
if (!package.open(QIODevice::ReadOnly)) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "Cannot open the offline update package"); return false; }
|
||||
if (package.read(8) != QByteArray("MUPD0001", 8)) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline package format identifier is invalid"); return false; }
|
||||
const QByteArray lengthBytes = package.read(8);
|
||||
if (lengthBytes.size() != 8) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline package header is incomplete"); return false; }
|
||||
quint64 headerSize = 0;
|
||||
for (int i = 0; i < 8; ++i) headerSize |= quint64(static_cast<unsigned char>(lengthBytes[i])) << (i * 8);
|
||||
if (headerSize == 0 || headerSize > 64ULL * 1024 * 1024 || headerSize > quint64(package.size() - 16)) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline package header length is invalid"); return false; }
|
||||
QJsonParseError error;
|
||||
const QJsonDocument wrapperDoc = QJsonDocument::fromJson(package.read(qint64(headerSize)), &error);
|
||||
if (error.error != QJsonParseError::NoError || !wrapperDoc.isObject()) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline package header JSON is invalid"); return false; }
|
||||
const QJsonObject wrapper = wrapperDoc.object();
|
||||
const QByteArray packageText = wrapper.value("package_text").toString().toUtf8();
|
||||
const QByteArray manifestText = wrapper.value("manifest_text").toString().toUtf8();
|
||||
const QString publicKey = QApplication::applicationDirPath() + "/config/manifest_public_key.pem";
|
||||
if (!verifySignature(packageText, wrapper.value("package_signature").toString(), publicKey)) { m_offlineError = "离线包 RSA 签名无效"; return false; }
|
||||
const QJsonObject packageMeta = QJsonDocument::fromJson(packageText, &error).object();
|
||||
if (error.error != QJsonParseError::NoError || packageMeta.value("format").toString() != "MUPD0001") { m_offlineError = "离线包签名元数据无效"; return false; }
|
||||
if (QString::fromLatin1(QCryptographicHash::hash(manifestText, QCryptographicHash::Sha256).toHex()) != packageMeta.value("manifest_sha256").toString()) { m_offlineError = "Manifest 摘要与包签名不一致"; return false; }
|
||||
QJsonObject manifest = QJsonDocument::fromJson(manifestText, &error).object();
|
||||
if (error.error != QJsonParseError::NoError || manifest.isEmpty()) { m_offlineError = "离线 Manifest 无效"; return false; }
|
||||
if (!verifySignature(packageText, wrapper.value("package_signature").toString(), publicKey)) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline package RSA signature is invalid"); return false; }
|
||||
const QJsonObject packageMeta = QJsonDocument::fromJson(packageText, &error).object();
|
||||
if (error.error != QJsonParseError::NoError || packageMeta.value("format").toString() != "MUPD0001") { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline package signature metadata is invalid"); return false; }
|
||||
if (QString::fromLatin1(QCryptographicHash::hash(manifestText, QCryptographicHash::Sha256).toHex()) != packageMeta.value("manifest_sha256").toString()) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The manifest digest does not match the package signature"); return false; }
|
||||
QJsonObject manifest = QJsonDocument::fromJson(manifestText, &error).object();
|
||||
if (error.error != QJsonParseError::NoError || manifest.isEmpty()) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline manifest is invalid"); return false; }
|
||||
manifest.insert("signature", wrapper.value("manifest_signature").toString());
|
||||
m_manifest = manifest; m_manifestText = QString::fromUtf8(manifestText); m_fileItems.clear();
|
||||
if (manifest.value("app_id") != packageMeta.value("app_id") || manifest.value("channel") != packageMeta.value("channel") || manifest.value("version") != packageMeta.value("version")) { m_offlineError = "包信息与 Manifest 身份不一致"; return false; }
|
||||
if (!verifyManifestSignature()) { m_offlineError = "离线 Manifest RSA 签名无效"; return false; }
|
||||
if (manifest.value("app_id") != packageMeta.value("app_id") || manifest.value("channel") != packageMeta.value("channel") || manifest.value("version") != packageMeta.value("version")) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "Package information does not match manifest identity"); return false; }
|
||||
if (!verifyManifestSignature()) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline manifest RSA signature is invalid"); return false; }
|
||||
const qint64 payloadStart = 16 + qint64(headerSize);
|
||||
const QJsonArray entries = packageMeta.value("files").toArray();
|
||||
for (const QJsonValue& value : entries) {
|
||||
const QJsonObject item = value.toObject(); const QString path = QDir::fromNativeSeparators(item.value("path").toString());
|
||||
const qint64 offset = item.value("offset").toVariant().toLongLong(); const qint64 size = item.value("size").toVariant().toLongLong();
|
||||
if (!isSafeRelativePath(path) || offset < 0 || size < 0 || payloadStart + offset + size > package.size()) { m_offlineError = "离线包包含不安全路径或越界数据: " + path; return false; }
|
||||
FileDownloadItem fi{path, QString(), item.value("sha256").toString(), size}; m_fileItems.append(fi);
|
||||
if (stagingDir.isEmpty()) continue;
|
||||
const QString target = QDir(stagingDir).filePath(path); if (!QDir().mkpath(QFileInfo(target).path()) || !package.seek(payloadStart + offset)) { m_offlineError = "无法准备离线文件: " + path; return false; }
|
||||
QSaveFile output(target); if (!output.open(QIODevice::WriteOnly)) { m_offlineError = "无法创建暂存文件: " + path; return false; }
|
||||
QCryptographicHash hash(QCryptographicHash::Sha256); qint64 remaining = size;
|
||||
while (remaining > 0) { const QByteArray block = package.read(qMin<qint64>(remaining, 1024 * 1024)); if (block.isEmpty() || output.write(block) != block.size()) { output.cancelWriting(); m_offlineError = "离线文件读取失败: " + path; return false; } hash.addData(block); remaining -= block.size(); }
|
||||
if (QString::fromLatin1(hash.result().toHex()).compare(fi.sha256, Qt::CaseInsensitive) != 0 || !output.commit()) { output.cancelWriting(); m_offlineError = "离线文件 Hash 或写入失败: " + path; return false; }
|
||||
}
|
||||
if (entries.size() != m_manifest.value("files").toArray().size()) { m_offlineError = "离线包文件数量与 Manifest 不一致"; return false; }
|
||||
if (!isSafeRelativePath(path) || offset < 0 || size < 0 || payloadStart + offset + size > package.size()) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline package contains an unsafe path or out-of-range data: %1").arg(path); return false; }
|
||||
FileDownloadItem fi{path, QString(), item.value("sha256").toString(), size}; m_fileItems.append(fi);
|
||||
if (stagingDir.isEmpty()) continue;
|
||||
const QString target = QDir(stagingDir).filePath(path); if (!QDir().mkpath(QFileInfo(target).path()) || !package.seek(payloadStart + offset)) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "Cannot prepare offline file: %1").arg(path); return false; }
|
||||
QSaveFile output(target); if (!output.open(QIODevice::WriteOnly)) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "Cannot create staged file: %1").arg(path); return false; }
|
||||
QCryptographicHash hash(QCryptographicHash::Sha256); qint64 remaining = size;
|
||||
while (remaining > 0) { const QByteArray block = package.read(qMin<qint64>(remaining, 1024 * 1024)); if (block.isEmpty() || output.write(block) != block.size()) { output.cancelWriting(); m_offlineError = QCoreApplication::translate("UpdaterLogic", "Failed to read offline file: %1").arg(path); return false; } hash.addData(block); remaining -= block.size(); }
|
||||
if (QString::fromLatin1(hash.result().toHex()).compare(fi.sha256, Qt::CaseInsensitive) != 0 || !output.commit()) { output.cancelWriting(); m_offlineError = QCoreApplication::translate("UpdaterLogic", "Offline file hash verification or write failed: %1").arg(path); return false; }
|
||||
}
|
||||
if (entries.size() != m_manifest.value("files").toArray().size()) { m_offlineError = QCoreApplication::translate("UpdaterLogic", "The offline package file count does not match the manifest"); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -514,7 +515,7 @@ bool UpdaterLogic::downloadSingleFile(const QString& url, const QString& savePat
|
||||
|
||||
if (existingSize > 0 && httpStatus == 200)
|
||||
{
|
||||
// 服务端忽略 Range,当前文件是“旧片段 + 完整响应”,必须安全重下。
|
||||
// The server ignored Range; the current file is "old fragment + full response" and must be redownloaded safely.
|
||||
qDebug() << "Server ignored Range; restart full download:" << savePath;
|
||||
QFile::remove(partPath);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user