UNPKG

16 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.createHttpError = createHttpError;
7exports.parseJson = parseJson;
8exports.configureRequestOptionsFromUrl = configureRequestOptionsFromUrl;
9exports.configureRequestUrl = configureRequestUrl;
10exports.safeGetHeader = safeGetHeader;
11exports.configureRequestOptions = configureRequestOptions;
12exports.safeStringifyJson = safeStringifyJson;
13exports.DigestTransform = exports.HttpExecutor = exports.HttpError = void 0;
14
15function _bluebirdLst() {
16 const data = require("bluebird-lst");
17
18 _bluebirdLst = function () {
19 return data;
20 };
21
22 return data;
23}
24
25function _crypto() {
26 const data = require("crypto");
27
28 _crypto = function () {
29 return data;
30 };
31
32 return data;
33}
34
35var _debug2 = _interopRequireDefault(require("debug"));
36
37function _fsExtraP() {
38 const data = require("fs-extra-p");
39
40 _fsExtraP = function () {
41 return data;
42 };
43
44 return data;
45}
46
47function _stream() {
48 const data = require("stream");
49
50 _stream = function () {
51 return data;
52 };
53
54 return data;
55}
56
57function _url() {
58 const data = require("url");
59
60 _url = function () {
61 return data;
62 };
63
64 return data;
65}
66
67function _CancellationToken() {
68 const data = require("./CancellationToken");
69
70 _CancellationToken = function () {
71 return data;
72 };
73
74 return data;
75}
76
77function _index() {
78 const data = require("./index");
79
80 _index = function () {
81 return data;
82 };
83
84 return data;
85}
86
87function _ProgressCallbackTransform() {
88 const data = require("./ProgressCallbackTransform");
89
90 _ProgressCallbackTransform = function () {
91 return data;
92 };
93
94 return data;
95}
96
97function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
98
99const debug = (0, _debug2.default)("electron-builder");
100
101function 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
105const 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
107class 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
117exports.HttpError = HttpError;
118
119function parseJson(result) {
120 return result.then(it => it == null || it.length === 0 ? null : JSON.parse(it));
121}
122
123class 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:"}//${options.hostname}${options.path}
186
187Please 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
360exports.HttpExecutor = HttpExecutor;
361
362function configureRequestOptionsFromUrl(url, options) {
363 const result = configureRequestOptions(options);
364 configureRequestUrl(new (_url().URL)(url), result);
365 return result;
366}
367
368function 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
381class 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
433exports.DigestTransform = DigestTransform;
434
435function 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
444function 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
457function 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
498function 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
530function 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