"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NsisUpdater = void 0; const builder_util_runtime_1 = require("builder-util-runtime"); const child_process_1 = require("child_process"); const path = require("path"); const BaseUpdater_1 = require("./BaseUpdater"); const FileWithEmbeddedBlockMapDifferentialDownloader_1 = require("./differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader"); const GenericDifferentialDownloader_1 = require("./differentialDownloader/GenericDifferentialDownloader"); const main_1 = require("./main"); const util_1 = require("./util"); const Provider_1 = require("./providers/Provider"); const fs_extra_1 = require("fs-extra"); const windowsExecutableCodeSignatureVerifier_1 = require("./windowsExecutableCodeSignatureVerifier"); const url_1 = require("url"); const zlib_1 = require("zlib"); class NsisUpdater extends BaseUpdater_1.BaseUpdater { constructor(options, app) { super(options, app); } /*** @private */ doDownloadUpdate(downloadUpdateOptions) { const provider = downloadUpdateOptions.updateInfoAndProvider.provider; const fileInfo = Provider_1.findFile(provider.resolveFiles(downloadUpdateOptions.updateInfoAndProvider.info), "exe"); return this.executeDownload({ fileExtension: "exe", downloadUpdateOptions, fileInfo, task: async (destinationFile, downloadOptions, packageFile, removeTempDirIfAny) => { const packageInfo = fileInfo.packageInfo; const isWebInstaller = packageInfo != null && packageFile != null; if (isWebInstaller && downloadUpdateOptions.disableWebInstaller) { throw builder_util_runtime_1.newError(`Unable to download new version ${downloadUpdateOptions.updateInfoAndProvider.info.version}. Web Installers are disabled`, "ERR_UPDATER_WEB_INSTALLER_DISABLED"); } if (!isWebInstaller && !downloadUpdateOptions.disableWebInstaller) { this._logger.warn("disableWebInstaller is set to false, you should set it to true if you do not plan on using a web installer. This will default to true in a future version."); } if (isWebInstaller || (await this.differentialDownloadInstaller(fileInfo, downloadUpdateOptions, destinationFile, provider))) { await this.httpExecutor.download(fileInfo.url, destinationFile, downloadOptions); } const signatureVerificationStatus = await this.verifySignature(destinationFile); if (signatureVerificationStatus != null) { await removeTempDirIfAny(); // noinspection ThrowInsideFinallyBlockJS throw builder_util_runtime_1.newError(`New version ${downloadUpdateOptions.updateInfoAndProvider.info.version} is not signed by the application owner: ${signatureVerificationStatus}`, "ERR_UPDATER_INVALID_SIGNATURE"); } if (isWebInstaller) { if (await this.differentialDownloadWebPackage(downloadUpdateOptions, packageInfo, packageFile, provider)) { try { await this.httpExecutor.download(new url_1.URL(packageInfo.path), packageFile, { headers: downloadUpdateOptions.requestHeaders, cancellationToken: downloadUpdateOptions.cancellationToken, sha512: packageInfo.sha512, }); } catch (e) { try { await fs_extra_1.unlink(packageFile); } catch (ignored) { // ignore } throw e; } } } }, }); } // $certificateInfo = (Get-AuthenticodeSignature 'xxx\yyy.exe' // | where {$_.Status.Equals([System.Management.Automation.SignatureStatus]::Valid) -and $_.SignerCertificate.Subject.Contains("CN=siemens.com")}) // | Out-String ; if ($certificateInfo) { exit 0 } else { exit 1 } async verifySignature(tempUpdateFile) { let publisherName; try { publisherName = (await this.configOnDisk.value).publisherName; if (publisherName == null) { return null; } } catch (e) { if (e.code === "ENOENT") { // no app-update.yml return null; } throw e; } return await windowsExecutableCodeSignatureVerifier_1.verifySignature(Array.isArray(publisherName) ? publisherName : [publisherName], tempUpdateFile, this._logger); } doInstall(options) { const args = ["--updated"]; if (options.isSilent) { args.push("/S"); } if (options.isForceRunAfter) { args.push("--force-run"); } if (this.installDirectory) { // maybe check if folder exists args.push(`/D=${this.installDirectory}`); } const packagePath = this.downloadedUpdateHelper == null ? null : this.downloadedUpdateHelper.packageFile; if (packagePath != null) { // only = form is supported args.push(`--package-file=${packagePath}`); } const callUsingElevation = () => { _spawn(path.join(process.resourcesPath, "elevate.exe"), [options.installerPath].concat(args)).catch(e => this.dispatchError(e)); }; if (options.isAdminRightsRequired) { this._logger.info("isAdminRightsRequired is set to true, run installer using elevate.exe"); callUsingElevation(); return true; } _spawn(options.installerPath, args).catch((e) => { // https://github.com/electron-userland/electron-builder/issues/1129 // Node 8 sends errors: https://nodejs.org/dist/latest-v8.x/docs/api/errors.html#errors_common_system_errors const errorCode = e.code; this._logger.info(`Cannot run installer: error code: ${errorCode}, error message: "${e.message}", will be executed again using elevate if EACCES"`); if (errorCode === "UNKNOWN" || errorCode === "EACCES") { callUsingElevation(); } else { this.dispatchError(e); } }); return true; } async differentialDownloadInstaller(fileInfo, downloadUpdateOptions, installerPath, provider) { try { if (this._testOnlyOptions != null && !this._testOnlyOptions.isUseDifferentialDownload) { return true; } const blockmapFileUrls = util_1.blockmapFiles(fileInfo.url, this.app.version, downloadUpdateOptions.updateInfoAndProvider.info.version); this._logger.info(`Download block maps (old: "${blockmapFileUrls[0]}", new: ${blockmapFileUrls[1]})`); const downloadBlockMap = async (url) => { const data = await this.httpExecutor.downloadToBuffer(url, { headers: downloadUpdateOptions.requestHeaders, cancellationToken: downloadUpdateOptions.cancellationToken, }); if (data == null || data.length === 0) { throw new Error(`Blockmap "${url.href}" is empty`); } try { return JSON.parse(zlib_1.gunzipSync(data).toString()); } catch (e) { throw new Error(`Cannot parse blockmap "${url.href}", error: ${e}`); } }; const downloadOptions = { newUrl: fileInfo.url, oldFile: path.join(this.downloadedUpdateHelper.cacheDir, builder_util_runtime_1.CURRENT_APP_INSTALLER_FILE_NAME), logger: this._logger, newFile: installerPath, isUseMultipleRangeRequest: provider.isUseMultipleRangeRequest, requestHeaders: downloadUpdateOptions.requestHeaders, cancellationToken: downloadUpdateOptions.cancellationToken, }; if (this.listenerCount(main_1.DOWNLOAD_PROGRESS) > 0) { downloadOptions.onProgress = it => this.emit(main_1.DOWNLOAD_PROGRESS, it); } const blockMapDataList = await Promise.all(blockmapFileUrls.map(u => downloadBlockMap(u))); await new GenericDifferentialDownloader_1.GenericDifferentialDownloader(fileInfo.info, this.httpExecutor, downloadOptions).download(blockMapDataList[0], blockMapDataList[1]); return false; } catch (e) { this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`); if (this._testOnlyOptions != null) { // test mode throw e; } return true; } } async differentialDownloadWebPackage(downloadUpdateOptions, packageInfo, packagePath, provider) { if (packageInfo.blockMapSize == null) { return true; } try { const downloadOptions = { newUrl: new url_1.URL(packageInfo.path), oldFile: path.join(this.downloadedUpdateHelper.cacheDir, builder_util_runtime_1.CURRENT_APP_PACKAGE_FILE_NAME), logger: this._logger, newFile: packagePath, requestHeaders: this.requestHeaders, isUseMultipleRangeRequest: provider.isUseMultipleRangeRequest, cancellationToken: downloadUpdateOptions.cancellationToken, }; if (this.listenerCount(main_1.DOWNLOAD_PROGRESS) > 0) { downloadOptions.onProgress = it => this.emit(main_1.DOWNLOAD_PROGRESS, it); } await new FileWithEmbeddedBlockMapDifferentialDownloader_1.FileWithEmbeddedBlockMapDifferentialDownloader(packageInfo, this.httpExecutor, downloadOptions).download(); } catch (e) { this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`); // during test (developer machine mac or linux) we must throw error return process.platform === "win32"; } return false; } } exports.NsisUpdater = NsisUpdater; /** * This handles both node 8 and node 10 way of emitting error when spawning a process * - node 8: Throws the error * - node 10: Emit the error(Need to listen with on) */ async function _spawn(exe, args) { return new Promise((resolve, reject) => { try { const process = child_process_1.spawn(exe, args, { detached: true, stdio: "ignore", }); process.on("error", error => { reject(error); }); process.unref(); if (process.pid !== undefined) { resolve(true); } } catch (error) { reject(error); } }); } //# sourceMappingURL=NsisUpdater.js.map