1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.NsisUpdater = void 0;
|
4 | const builder_util_runtime_1 = require("builder-util-runtime");
|
5 | const child_process_1 = require("child_process");
|
6 | const path = require("path");
|
7 | const BaseUpdater_1 = require("./BaseUpdater");
|
8 | const FileWithEmbeddedBlockMapDifferentialDownloader_1 = require("./differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader");
|
9 | const GenericDifferentialDownloader_1 = require("./differentialDownloader/GenericDifferentialDownloader");
|
10 | const main_1 = require("./main");
|
11 | const util_1 = require("./util");
|
12 | const Provider_1 = require("./providers/Provider");
|
13 | const fs_extra_1 = require("fs-extra");
|
14 | const windowsExecutableCodeSignatureVerifier_1 = require("./windowsExecutableCodeSignatureVerifier");
|
15 | const url_1 = require("url");
|
16 | const zlib_1 = require("zlib");
|
17 | class NsisUpdater extends BaseUpdater_1.BaseUpdater {
|
18 | constructor(options, app) {
|
19 | super(options, app);
|
20 | }
|
21 |
|
22 | doDownloadUpdate(downloadUpdateOptions) {
|
23 | const provider = downloadUpdateOptions.updateInfoAndProvider.provider;
|
24 | const fileInfo = Provider_1.findFile(provider.resolveFiles(downloadUpdateOptions.updateInfoAndProvider.info), "exe");
|
25 | return this.executeDownload({
|
26 | fileExtension: "exe",
|
27 | downloadUpdateOptions,
|
28 | fileInfo,
|
29 | task: async (destinationFile, downloadOptions, packageFile, removeTempDirIfAny) => {
|
30 | const packageInfo = fileInfo.packageInfo;
|
31 | const isWebInstaller = packageInfo != null && packageFile != null;
|
32 | if (isWebInstaller || (await this.differentialDownloadInstaller(fileInfo, downloadUpdateOptions, destinationFile, provider))) {
|
33 | await this.httpExecutor.download(fileInfo.url, destinationFile, downloadOptions);
|
34 | }
|
35 | const signatureVerificationStatus = await this.verifySignature(destinationFile);
|
36 | if (signatureVerificationStatus != null) {
|
37 | await removeTempDirIfAny();
|
38 |
|
39 | throw builder_util_runtime_1.newError(`New version ${downloadUpdateOptions.updateInfoAndProvider.info.version} is not signed by the application owner: ${signatureVerificationStatus}`, "ERR_UPDATER_INVALID_SIGNATURE");
|
40 | }
|
41 | if (isWebInstaller) {
|
42 | if (await this.differentialDownloadWebPackage(downloadUpdateOptions, packageInfo, packageFile, provider)) {
|
43 | try {
|
44 | await this.httpExecutor.download(new url_1.URL(packageInfo.path), packageFile, {
|
45 | headers: downloadUpdateOptions.requestHeaders,
|
46 | cancellationToken: downloadUpdateOptions.cancellationToken,
|
47 | sha512: packageInfo.sha512,
|
48 | });
|
49 | }
|
50 | catch (e) {
|
51 | try {
|
52 | await fs_extra_1.unlink(packageFile);
|
53 | }
|
54 | catch (ignored) {
|
55 |
|
56 | }
|
57 | throw e;
|
58 | }
|
59 | }
|
60 | }
|
61 | },
|
62 | });
|
63 | }
|
64 |
|
65 |
|
66 |
|
67 | async verifySignature(tempUpdateFile) {
|
68 | let publisherName;
|
69 | try {
|
70 | publisherName = (await this.configOnDisk.value).publisherName;
|
71 | if (publisherName == null) {
|
72 | return null;
|
73 | }
|
74 | }
|
75 | catch (e) {
|
76 | if (e.code === "ENOENT") {
|
77 |
|
78 | return null;
|
79 | }
|
80 | throw e;
|
81 | }
|
82 | return await windowsExecutableCodeSignatureVerifier_1.verifySignature(Array.isArray(publisherName) ? publisherName : [publisherName], tempUpdateFile, this._logger);
|
83 | }
|
84 | doInstall(options) {
|
85 | const args = ["--updated"];
|
86 | if (options.isSilent) {
|
87 | args.push("/S");
|
88 | }
|
89 | if (options.isForceRunAfter) {
|
90 | args.push("--force-run");
|
91 | }
|
92 | const packagePath = this.downloadedUpdateHelper == null ? null : this.downloadedUpdateHelper.packageFile;
|
93 | if (packagePath != null) {
|
94 |
|
95 | args.push(`--package-file=${packagePath}`);
|
96 | }
|
97 | const callUsingElevation = () => {
|
98 | _spawn(path.join(process.resourcesPath, "elevate.exe"), [options.installerPath].concat(args)).catch(e => this.dispatchError(e));
|
99 | };
|
100 | if (options.isAdminRightsRequired) {
|
101 | this._logger.info("isAdminRightsRequired is set to true, run installer using elevate.exe");
|
102 | callUsingElevation();
|
103 | return true;
|
104 | }
|
105 | _spawn(options.installerPath, args).catch((e) => {
|
106 |
|
107 |
|
108 | const errorCode = e.code;
|
109 | this._logger.info(`Cannot run installer: error code: ${errorCode}, error message: "${e.message}", will be executed again using elevate if EACCES"`);
|
110 | if (errorCode === "UNKNOWN" || errorCode === "EACCES") {
|
111 | callUsingElevation();
|
112 | }
|
113 | else {
|
114 | this.dispatchError(e);
|
115 | }
|
116 | });
|
117 | return true;
|
118 | }
|
119 | async differentialDownloadInstaller(fileInfo, downloadUpdateOptions, installerPath, provider) {
|
120 | try {
|
121 | if (this._testOnlyOptions != null && !this._testOnlyOptions.isUseDifferentialDownload) {
|
122 | return true;
|
123 | }
|
124 | const blockmapFileUrls = util_1.blockmapFiles(fileInfo.url, this.app.version, downloadUpdateOptions.updateInfoAndProvider.info.version);
|
125 | this._logger.info(`Download block maps (old: "${blockmapFileUrls[0]}", new: ${blockmapFileUrls[1]})`);
|
126 | const downloadBlockMap = async (url) => {
|
127 | const data = await this.httpExecutor.downloadToBuffer(url, {
|
128 | headers: downloadUpdateOptions.requestHeaders,
|
129 | cancellationToken: downloadUpdateOptions.cancellationToken,
|
130 | });
|
131 | if (data == null || data.length === 0) {
|
132 | throw new Error(`Blockmap "${url.href}" is empty`);
|
133 | }
|
134 | try {
|
135 | return JSON.parse(zlib_1.gunzipSync(data).toString());
|
136 | }
|
137 | catch (e) {
|
138 | throw new Error(`Cannot parse blockmap "${url.href}", error: ${e}`);
|
139 | }
|
140 | };
|
141 | const downloadOptions = {
|
142 | newUrl: fileInfo.url,
|
143 | oldFile: path.join(this.downloadedUpdateHelper.cacheDir, builder_util_runtime_1.CURRENT_APP_INSTALLER_FILE_NAME),
|
144 | logger: this._logger,
|
145 | newFile: installerPath,
|
146 | isUseMultipleRangeRequest: provider.isUseMultipleRangeRequest,
|
147 | requestHeaders: downloadUpdateOptions.requestHeaders,
|
148 | cancellationToken: downloadUpdateOptions.cancellationToken,
|
149 | };
|
150 | if (this.listenerCount(main_1.DOWNLOAD_PROGRESS) > 0) {
|
151 | downloadOptions.onProgress = it => this.emit(main_1.DOWNLOAD_PROGRESS, it);
|
152 | }
|
153 | const blockMapDataList = await Promise.all(blockmapFileUrls.map(u => downloadBlockMap(u)));
|
154 | await new GenericDifferentialDownloader_1.GenericDifferentialDownloader(fileInfo.info, this.httpExecutor, downloadOptions).download(blockMapDataList[0], blockMapDataList[1]);
|
155 | return false;
|
156 | }
|
157 | catch (e) {
|
158 | this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`);
|
159 | if (this._testOnlyOptions != null) {
|
160 |
|
161 | throw e;
|
162 | }
|
163 | return true;
|
164 | }
|
165 | }
|
166 | async differentialDownloadWebPackage(downloadUpdateOptions, packageInfo, packagePath, provider) {
|
167 | if (packageInfo.blockMapSize == null) {
|
168 | return true;
|
169 | }
|
170 | try {
|
171 | const downloadOptions = {
|
172 | newUrl: new url_1.URL(packageInfo.path),
|
173 | oldFile: path.join(this.downloadedUpdateHelper.cacheDir, builder_util_runtime_1.CURRENT_APP_PACKAGE_FILE_NAME),
|
174 | logger: this._logger,
|
175 | newFile: packagePath,
|
176 | requestHeaders: this.requestHeaders,
|
177 | isUseMultipleRangeRequest: provider.isUseMultipleRangeRequest,
|
178 | cancellationToken: downloadUpdateOptions.cancellationToken,
|
179 | };
|
180 | if (this.listenerCount(main_1.DOWNLOAD_PROGRESS) > 0) {
|
181 | downloadOptions.onProgress = it => this.emit(main_1.DOWNLOAD_PROGRESS, it);
|
182 | }
|
183 | await new FileWithEmbeddedBlockMapDifferentialDownloader_1.FileWithEmbeddedBlockMapDifferentialDownloader(packageInfo, this.httpExecutor, downloadOptions).download();
|
184 | }
|
185 | catch (e) {
|
186 | this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`);
|
187 |
|
188 | return process.platform === "win32";
|
189 | }
|
190 | return false;
|
191 | }
|
192 | }
|
193 | exports.NsisUpdater = NsisUpdater;
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | async function _spawn(exe, args) {
|
200 | return new Promise((resolve, reject) => {
|
201 | try {
|
202 | const process = child_process_1.spawn(exe, args, {
|
203 | detached: true,
|
204 | stdio: "ignore",
|
205 | });
|
206 | process.on("error", error => {
|
207 | reject(error);
|
208 | });
|
209 | process.unref();
|
210 | if (process.pid !== undefined) {
|
211 | resolve(true);
|
212 | }
|
213 | }
|
214 | catch (error) {
|
215 | reject(error);
|
216 | }
|
217 | });
|
218 | }
|
219 |
|
\ | No newline at end of file |