1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.createHttpError = createHttpError;
|
7 | exports.parseJson = parseJson;
|
8 | exports.configureRequestOptionsFromUrl = configureRequestOptionsFromUrl;
|
9 | exports.configureRequestUrl = configureRequestUrl;
|
10 | exports.safeGetHeader = safeGetHeader;
|
11 | exports.configureRequestOptions = configureRequestOptions;
|
12 | exports.safeStringifyJson = safeStringifyJson;
|
13 | exports.DigestTransform = exports.HttpExecutor = exports.HttpError = void 0;
|
14 |
|
15 | function _bluebirdLst() {
|
16 | const data = require("bluebird-lst");
|
17 |
|
18 | _bluebirdLst = function () {
|
19 | return data;
|
20 | };
|
21 |
|
22 | return data;
|
23 | }
|
24 |
|
25 | function _crypto() {
|
26 | const data = require("crypto");
|
27 |
|
28 | _crypto = function () {
|
29 | return data;
|
30 | };
|
31 |
|
32 | return data;
|
33 | }
|
34 |
|
35 | var _debug2 = _interopRequireDefault(require("debug"));
|
36 |
|
37 | function _fsExtraP() {
|
38 | const data = require("fs-extra-p");
|
39 |
|
40 | _fsExtraP = function () {
|
41 | return data;
|
42 | };
|
43 |
|
44 | return data;
|
45 | }
|
46 |
|
47 | function _stream() {
|
48 | const data = require("stream");
|
49 |
|
50 | _stream = function () {
|
51 | return data;
|
52 | };
|
53 |
|
54 | return data;
|
55 | }
|
56 |
|
57 | function _url() {
|
58 | const data = require("url");
|
59 |
|
60 | _url = function () {
|
61 | return data;
|
62 | };
|
63 |
|
64 | return data;
|
65 | }
|
66 |
|
67 | function _CancellationToken() {
|
68 | const data = require("./CancellationToken");
|
69 |
|
70 | _CancellationToken = function () {
|
71 | return data;
|
72 | };
|
73 |
|
74 | return data;
|
75 | }
|
76 |
|
77 | function _index() {
|
78 | const data = require("./index");
|
79 |
|
80 | _index = function () {
|
81 | return data;
|
82 | };
|
83 |
|
84 | return data;
|
85 | }
|
86 |
|
87 | function _ProgressCallbackTransform() {
|
88 | const data = require("./ProgressCallbackTransform");
|
89 |
|
90 | _ProgressCallbackTransform = function () {
|
91 | return data;
|
92 | };
|
93 |
|
94 | return data;
|
95 | }
|
96 |
|
97 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
98 |
|
99 | const debug = (0, _debug2.default)("electron-builder");
|
100 |
|
101 | function createHttpError(response, description = null) {
|
102 | return new HttpError(response.statusCode || -1, `${response.statusCode} ${response.statusMessage}` + (description == null ? "" : "\n" + JSON.stringify(description, null, " ")) + "\nHeaders: " + safeStringifyJson(response.headers), description);
|
103 | }
|
104 |
|
105 | const HTTP_STATUS_CODES = new Map([[429, "Too many requests"], [400, "Bad request"], [403, "Forbidden"], [404, "Not found"], [405, "Method not allowed"], [406, "Not acceptable"], [408, "Request timeout"], [413, "Request entity too large"], [500, "Internal server error"], [502, "Bad gateway"], [503, "Service unavailable"], [504, "Gateway timeout"], [505, "HTTP version not supported"]]);
|
106 |
|
107 | class HttpError extends Error {
|
108 | constructor(statusCode, message = `HTTP error: ${HTTP_STATUS_CODES.get(statusCode) || statusCode}`, description = null) {
|
109 | super(message);
|
110 | this.statusCode = statusCode;
|
111 | this.description = description;
|
112 | this.name = "HttpError";
|
113 | }
|
114 |
|
115 | }
|
116 |
|
117 | exports.HttpError = HttpError;
|
118 |
|
119 | function parseJson(result) {
|
120 | return result.then(it => it == null || it.length === 0 ? null : JSON.parse(it));
|
121 | }
|
122 |
|
123 | class HttpExecutor {
|
124 | constructor() {
|
125 | this.maxRedirects = 10;
|
126 | }
|
127 |
|
128 | request(options, cancellationToken = new (_CancellationToken().CancellationToken)(), data) {
|
129 | configureRequestOptions(options);
|
130 | const encodedData = data == null ? undefined : Buffer.from(JSON.stringify(data));
|
131 |
|
132 | if (encodedData != null) {
|
133 | options.method = "post";
|
134 | options.headers["Content-Type"] = "application/json";
|
135 | options.headers["Content-Length"] = encodedData.length;
|
136 | }
|
137 |
|
138 | return this.doApiRequest(options, cancellationToken, it => {
|
139 | it.end(encodedData);
|
140 | });
|
141 | }
|
142 |
|
143 | doApiRequest(options, cancellationToken, requestProcessor, redirectCount = 0) {
|
144 | if (debug.enabled) {
|
145 | debug(`Request: ${safeStringifyJson(options)}`);
|
146 | }
|
147 |
|
148 | return cancellationToken.createPromise((resolve, reject, onCancel) => {
|
149 | const request = this.createRequest(options, response => {
|
150 | try {
|
151 | this.handleResponse(response, options, cancellationToken, resolve, reject, redirectCount, requestProcessor);
|
152 | } catch (e) {
|
153 | reject(e);
|
154 | }
|
155 | });
|
156 | this.addErrorAndTimeoutHandlers(request, reject);
|
157 | this.addRedirectHandlers(request, options, reject, redirectCount, options => {
|
158 | this.doApiRequest(options, cancellationToken, requestProcessor, redirectCount).then(resolve).catch(reject);
|
159 | });
|
160 | requestProcessor(request, reject);
|
161 | onCancel(() => request.abort());
|
162 | });
|
163 | } // noinspection JSUnusedLocalSymbols
|
164 |
|
165 |
|
166 | addRedirectHandlers(request, options, reject, redirectCount, handler) {// not required for NodeJS
|
167 | }
|
168 |
|
169 | addErrorAndTimeoutHandlers(request, reject) {
|
170 | this.addTimeOutHandler(request, reject);
|
171 | request.on("error", reject);
|
172 | request.on("aborted", () => {
|
173 | reject(new Error("Request has been aborted by the server"));
|
174 | });
|
175 | }
|
176 |
|
177 | handleResponse(response, options, cancellationToken, resolve, reject, redirectCount, requestProcessor) {
|
178 | if (debug.enabled) {
|
179 | debug(`Response: ${response.statusCode} ${response.statusMessage}, request options: ${safeStringifyJson(options)}`);
|
180 | } // we handle any other >= 400 error on request end (read detailed message in the response body)
|
181 |
|
182 |
|
183 | if (response.statusCode === 404) {
|
184 | // error is clear, we don't need to read detailed error description
|
185 | reject(createHttpError(response, `method: ${options.method} url: ${options.protocol || "https:"}
|
186 |
|
187 | Please double check that your authentication token is correct. Due to security reasons actual status maybe not reported, but 404.
|
188 | `));
|
189 | return;
|
190 | } else if (response.statusCode === 204) {
|
191 | // on DELETE request
|
192 | resolve();
|
193 | return;
|
194 | }
|
195 |
|
196 | const redirectUrl = safeGetHeader(response, "location");
|
197 |
|
198 | if (redirectUrl != null) {
|
199 | if (redirectCount > this.maxRedirects) {
|
200 | reject(this.createMaxRedirectError());
|
201 | return;
|
202 | }
|
203 |
|
204 | this.doApiRequest(HttpExecutor.prepareRedirectUrlOptions(redirectUrl, options), cancellationToken, requestProcessor, redirectCount).then(resolve).catch(reject);
|
205 | return;
|
206 | }
|
207 |
|
208 | response.setEncoding("utf8");
|
209 | let data = "";
|
210 | response.on("data", chunk => data += chunk);
|
211 | response.on("end", () => {
|
212 | try {
|
213 | if (response.statusCode != null && response.statusCode >= 400) {
|
214 | const contentType = safeGetHeader(response, "content-type");
|
215 | const isJson = contentType != null && (Array.isArray(contentType) ? contentType.find(it => it.includes("json")) != null : contentType.includes("json"));
|
216 | reject(createHttpError(response, isJson ? JSON.parse(data) : data));
|
217 | } else {
|
218 | resolve(data.length === 0 ? null : data);
|
219 | }
|
220 | } catch (e) {
|
221 | reject(e);
|
222 | }
|
223 | });
|
224 | }
|
225 |
|
226 | downloadToBuffer(url, options) {
|
227 | var _this = this;
|
228 |
|
229 | return (0, _bluebirdLst().coroutine)(function* () {
|
230 | return yield options.cancellationToken.createPromise((resolve, reject, onCancel) => {
|
231 | let result = null;
|
232 | const requestOptions = {
|
233 | headers: options.headers || undefined,
|
234 | // because PrivateGitHubProvider requires HttpExecutor.prepareRedirectUrlOptions logic, so, we need to redirect manually
|
235 | redirect: "manual"
|
236 | };
|
237 | configureRequestUrl(url, requestOptions);
|
238 | configureRequestOptions(requestOptions);
|
239 |
|
240 | _this.doDownload(requestOptions, {
|
241 | destination: null,
|
242 | options,
|
243 | onCancel,
|
244 | callback: error => {
|
245 | if (error == null) {
|
246 | resolve(result);
|
247 | } else {
|
248 | reject(error);
|
249 | }
|
250 | },
|
251 | responseHandler: (response, callback) => {
|
252 | const contentLength = safeGetHeader(response, "content-length");
|
253 | let position = -1;
|
254 |
|
255 | if (contentLength != null) {
|
256 | const size = parseInt(contentLength, 10);
|
257 |
|
258 | if (size > 0) {
|
259 | if (size > 5242880) {
|
260 | callback(new Error("Maximum allowed size is 5 MB"));
|
261 | return;
|
262 | }
|
263 |
|
264 | result = Buffer.alloc(size);
|
265 | position = 0;
|
266 | }
|
267 | }
|
268 |
|
269 | response.on("data", chunk => {
|
270 | if (position !== -1) {
|
271 | chunk.copy(result, position);
|
272 | position += chunk.length;
|
273 | } else if (result == null) {
|
274 | result = chunk;
|
275 | } else {
|
276 | if (result.length > 5242880) {
|
277 | callback(new Error("Maximum allowed size is 5 MB"));
|
278 | return;
|
279 | }
|
280 |
|
281 | result = Buffer.concat([result, chunk]);
|
282 | }
|
283 | });
|
284 | response.on("end", () => {
|
285 | if (result != null && position !== -1 && position !== result.length) {
|
286 | callback(new Error(`Received data length ${position} is not equal to expected ${result.length}`));
|
287 | } else {
|
288 | callback(null);
|
289 | }
|
290 | });
|
291 | }
|
292 | }, 0);
|
293 | });
|
294 | })();
|
295 | }
|
296 |
|
297 | doDownload(requestOptions, options, redirectCount) {
|
298 | const request = this.createRequest(requestOptions, response => {
|
299 | if (response.statusCode >= 400) {
|
300 | options.callback(new Error(`Cannot download "${requestOptions.protocol || "https:"}//${requestOptions.hostname}${requestOptions.path}", status ${response.statusCode}: ${response.statusMessage}`));
|
301 | return;
|
302 | } // this code not relevant for Electron (redirect event instead handled)
|
303 |
|
304 |
|
305 | const redirectUrl = safeGetHeader(response, "location");
|
306 |
|
307 | if (redirectUrl != null) {
|
308 | if (redirectCount < this.maxRedirects) {
|
309 | this.doDownload(HttpExecutor.prepareRedirectUrlOptions(redirectUrl, requestOptions), options, redirectCount++);
|
310 | } else {
|
311 | options.callback(this.createMaxRedirectError());
|
312 | }
|
313 |
|
314 | return;
|
315 | }
|
316 |
|
317 | if (options.responseHandler == null) {
|
318 | configurePipes(options, response);
|
319 | } else {
|
320 | options.responseHandler(response, options.callback);
|
321 | }
|
322 | });
|
323 | this.addErrorAndTimeoutHandlers(request, options.callback);
|
324 | this.addRedirectHandlers(request, requestOptions, options.callback, redirectCount, requestOptions => {
|
325 | this.doDownload(requestOptions, options, redirectCount++);
|
326 | });
|
327 | request.end();
|
328 | }
|
329 |
|
330 | createMaxRedirectError() {
|
331 | return new Error(`Too many redirects (> ${this.maxRedirects})`);
|
332 | }
|
333 |
|
334 | addTimeOutHandler(request, callback) {
|
335 | request.on("socket", socket => {
|
336 | socket.setTimeout(60 * 1000, () => {
|
337 | request.abort();
|
338 | callback(new Error("Request timed out"));
|
339 | });
|
340 | });
|
341 | }
|
342 |
|
343 | static prepareRedirectUrlOptions(redirectUrl, options) {
|
344 | const newOptions = configureRequestOptionsFromUrl(redirectUrl, Object.assign({}, options));
|
345 | const headers = newOptions.headers;
|
346 |
|
347 | if (headers != null && headers.authorization != null && headers.authorization.startsWith("token")) {
|
348 | const parsedNewUrl = new (_url().URL)(redirectUrl);
|
349 |
|
350 | if (parsedNewUrl.hostname.endsWith(".amazonaws.com")) {
|
351 | delete headers.authorization;
|
352 | }
|
353 | }
|
354 |
|
355 | return newOptions;
|
356 | }
|
357 |
|
358 | }
|
359 |
|
360 | exports.HttpExecutor = HttpExecutor;
|
361 |
|
362 | function configureRequestOptionsFromUrl(url, options) {
|
363 | const result = configureRequestOptions(options);
|
364 | configureRequestUrl(new (_url().URL)(url), result);
|
365 | return result;
|
366 | }
|
367 |
|
368 | function configureRequestUrl(url, options) {
|
369 | options.protocol = url.protocol;
|
370 | options.hostname = url.hostname;
|
371 |
|
372 | if (url.port) {
|
373 | options.port = url.port;
|
374 | } else if (options.port) {
|
375 | delete options.port;
|
376 | }
|
377 |
|
378 | options.path = url.pathname + url.search;
|
379 | }
|
380 |
|
381 | class DigestTransform extends _stream().Transform {
|
382 | constructor(expected, algorithm = "sha512", encoding = "base64") {
|
383 | super();
|
384 | this.expected = expected;
|
385 | this.algorithm = algorithm;
|
386 | this.encoding = encoding;
|
387 | this._actual = null;
|
388 | this.isValidateOnEnd = true;
|
389 | this.digester = (0, _crypto().createHash)(algorithm);
|
390 | } // noinspection JSUnusedGlobalSymbols
|
391 |
|
392 |
|
393 | get actual() {
|
394 | return this._actual;
|
395 | } // noinspection JSUnusedGlobalSymbols
|
396 |
|
397 |
|
398 | _transform(chunk, encoding, callback) {
|
399 | this.digester.update(chunk);
|
400 | callback(null, chunk);
|
401 | } // noinspection JSUnusedGlobalSymbols
|
402 |
|
403 |
|
404 | _flush(callback) {
|
405 | this._actual = this.digester.digest(this.encoding);
|
406 |
|
407 | if (this.isValidateOnEnd) {
|
408 | try {
|
409 | this.validate();
|
410 | } catch (e) {
|
411 | callback(e);
|
412 | return;
|
413 | }
|
414 | }
|
415 |
|
416 | callback(null);
|
417 | }
|
418 |
|
419 | validate() {
|
420 | if (this._actual == null) {
|
421 | throw (0, _index().newError)("Not finished yet", "ERR_STREAM_NOT_FINISHED");
|
422 | }
|
423 |
|
424 | if (this._actual !== this.expected) {
|
425 | throw (0, _index().newError)(`${this.algorithm} checksum mismatch, expected ${this.expected}, got ${this._actual}`, "ERR_CHECKSUM_MISMATCH");
|
426 | }
|
427 |
|
428 | return null;
|
429 | }
|
430 |
|
431 | }
|
432 |
|
433 | exports.DigestTransform = DigestTransform;
|
434 |
|
435 | function checkSha2(sha2Header, sha2, callback) {
|
436 | if (sha2Header != null && sha2 != null && sha2Header !== sha2) {
|
437 | callback(new Error(`checksum mismatch: expected ${sha2} but got ${sha2Header} (X-Checksum-Sha2 header)`));
|
438 | return false;
|
439 | }
|
440 |
|
441 | return true;
|
442 | }
|
443 |
|
444 | function safeGetHeader(response, headerKey) {
|
445 | const value = response.headers[headerKey];
|
446 |
|
447 | if (value == null) {
|
448 | return null;
|
449 | } else if (Array.isArray(value)) {
|
450 | // electron API
|
451 | return value.length === 0 ? null : value[value.length - 1];
|
452 | } else {
|
453 | return value;
|
454 | }
|
455 | }
|
456 |
|
457 | function configurePipes(options, response) {
|
458 | if (!checkSha2(safeGetHeader(response, "X-Checksum-Sha2"), options.options.sha2, options.callback)) {
|
459 | return;
|
460 | }
|
461 |
|
462 | const streams = [];
|
463 |
|
464 | if (options.options.onProgress != null) {
|
465 | const contentLength = safeGetHeader(response, "content-length");
|
466 |
|
467 | if (contentLength != null) {
|
468 | streams.push(new (_ProgressCallbackTransform().ProgressCallbackTransform)(parseInt(contentLength, 10), options.options.cancellationToken, options.options.onProgress));
|
469 | }
|
470 | }
|
471 |
|
472 | const sha512 = options.options.sha512;
|
473 |
|
474 | if (sha512 != null) {
|
475 | streams.push(new DigestTransform(sha512, "sha512", sha512.length === 128 && !sha512.includes("+") && !sha512.includes("Z") && !sha512.includes("=") ? "hex" : "base64"));
|
476 | } else if (options.options.sha2 != null) {
|
477 | streams.push(new DigestTransform(options.options.sha2, "sha256", "hex"));
|
478 | }
|
479 |
|
480 | const fileOut = (0, _fsExtraP().createWriteStream)(options.destination);
|
481 | streams.push(fileOut);
|
482 | let lastStream = response;
|
483 |
|
484 | for (const stream of streams) {
|
485 | stream.on("error", error => {
|
486 | if (!options.options.cancellationToken.cancelled) {
|
487 | options.callback(error);
|
488 | }
|
489 | });
|
490 | lastStream = lastStream.pipe(stream);
|
491 | }
|
492 |
|
493 | fileOut.on("finish", () => {
|
494 | fileOut.close(options.callback);
|
495 | });
|
496 | }
|
497 |
|
498 | function configureRequestOptions(options, token, method) {
|
499 | if (method != null) {
|
500 | options.method = method;
|
501 | }
|
502 |
|
503 | let headers = options.headers;
|
504 |
|
505 | if (headers == null) {
|
506 | headers = {};
|
507 | options.headers = headers;
|
508 | }
|
509 |
|
510 | if (token != null) {
|
511 | headers.authorization = token.startsWith("Basic") ? token : `token ${token}`;
|
512 | }
|
513 |
|
514 | if (headers["User-Agent"] == null) {
|
515 | headers["User-Agent"] = "electron-builder";
|
516 | }
|
517 |
|
518 | if (method == null || method === "GET" || headers["Cache-Control"] == null) {
|
519 | headers["Cache-Control"] = "no-cache";
|
520 | } // do not specify for node (in any case we use https module)
|
521 |
|
522 |
|
523 | if (options.protocol == null && process.versions.electron != null) {
|
524 | options.protocol = "https:";
|
525 | }
|
526 |
|
527 | return options;
|
528 | }
|
529 |
|
530 | function safeStringifyJson(data, skippedNames) {
|
531 | return JSON.stringify(data, (name, value) => {
|
532 | if (name.endsWith("authorization") || name.endsWith("Password") || name.endsWith("PASSWORD") || name.endsWith("Token") || name.includes("password") || name.includes("token") || skippedNames != null && skippedNames.has(name)) {
|
533 | return "<stripped sensitive data>";
|
534 | }
|
535 |
|
536 | return value;
|
537 | }, 2);
|
538 | }
|
539 | // __ts-babel@6.0.4
|
540 | //# sourceMappingURL=httpExecutor.js.map |
\ | No newline at end of file |