2023-01-09 03:34:30 +00:00
|
|
|
const { Errors } = require('./enig_error.js');
|
|
|
|
|
|
|
|
// deps
|
2023-02-13 00:51:36 +00:00
|
|
|
const { isString, isObject, truncate } = require('lodash');
|
2023-08-24 18:11:23 +00:00
|
|
|
const httpsNoRedirects = require('node:https');
|
|
|
|
const { https: httpsWithRedirects } = require('follow-redirects');
|
2023-01-09 03:34:30 +00:00
|
|
|
const httpSignature = require('http-signature');
|
|
|
|
const crypto = require('crypto');
|
2023-02-12 19:38:52 +00:00
|
|
|
|
|
|
|
const DefaultTimeoutMilliseconds = 5000;
|
2023-01-09 03:34:30 +00:00
|
|
|
|
2023-01-12 05:37:09 +00:00
|
|
|
exports.getJson = getJson;
|
2023-01-09 03:34:30 +00:00
|
|
|
exports.postJson = postJson;
|
|
|
|
|
2023-01-12 05:37:09 +00:00
|
|
|
function getJson(url, options, cb) {
|
2023-01-14 06:55:05 +00:00
|
|
|
options = Object.assign({}, { method: 'GET' }, options);
|
2023-01-12 05:37:09 +00:00
|
|
|
|
2023-01-14 06:55:05 +00:00
|
|
|
return _makeRequest(url, options, (err, body, res) => {
|
2023-01-12 05:37:09 +00:00
|
|
|
if (err) {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
|
2023-02-05 17:42:30 +00:00
|
|
|
if (Array.isArray(options.validContentTypes)) {
|
|
|
|
const contentType = res.headers['content-type'] || '';
|
|
|
|
if (
|
|
|
|
!options.validContentTypes.some(ct => {
|
|
|
|
return contentType.startsWith(ct);
|
|
|
|
})
|
|
|
|
) {
|
|
|
|
return cb(Errors.HttpError(`Invalid Content-Type: ${contentType}`));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-12 05:37:09 +00:00
|
|
|
let parsed;
|
|
|
|
try {
|
|
|
|
parsed = JSON.parse(body);
|
|
|
|
} catch (e) {
|
|
|
|
return cb(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
return cb(null, parsed, res);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-09 03:34:30 +00:00
|
|
|
function postJson(url, json, options, cb) {
|
|
|
|
if (!isString(json)) {
|
|
|
|
json = JSON.stringify(json);
|
|
|
|
}
|
|
|
|
|
2023-01-14 06:55:05 +00:00
|
|
|
options = Object.assign({}, { method: 'POST', body: json }, options);
|
|
|
|
if (
|
|
|
|
!options.headers ||
|
|
|
|
!Object.keys(options.headers).find(h => h.toLowerCase() === 'content-type')
|
|
|
|
) {
|
|
|
|
options.headers['Content-Type'] = 'application/json';
|
|
|
|
}
|
2023-01-09 03:34:30 +00:00
|
|
|
|
2023-01-14 06:55:05 +00:00
|
|
|
return _makeRequest(url, options, cb);
|
|
|
|
}
|
2023-01-09 03:34:30 +00:00
|
|
|
|
2023-01-14 06:55:05 +00:00
|
|
|
function _makeRequest(url, options, cb) {
|
2023-02-13 00:51:36 +00:00
|
|
|
options = Object.assign({}, { timeout: DefaultTimeoutMilliseconds }, options);
|
2023-02-12 19:38:52 +00:00
|
|
|
|
2023-01-14 06:55:05 +00:00
|
|
|
if (options.body) {
|
2023-02-09 04:32:54 +00:00
|
|
|
options.headers['Content-Length'] = Buffer.from(options.body).length;
|
2023-01-14 06:55:05 +00:00
|
|
|
|
|
|
|
if (options?.sign?.headers?.includes('digest')) {
|
2023-01-20 05:31:14 +00:00
|
|
|
options.headers['Digest'] =
|
|
|
|
'SHA-256=' +
|
|
|
|
crypto.createHash('sha256').update(options.body).digest('base64');
|
2023-01-14 06:55:05 +00:00
|
|
|
}
|
2023-01-09 03:34:30 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 04:43:58 +00:00
|
|
|
let cbCalled = false;
|
|
|
|
const cbWrapper = (e, b, r) => {
|
|
|
|
if (!cbCalled) {
|
|
|
|
cbCalled = true;
|
|
|
|
return cb(e, b, r);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-08-24 18:11:23 +00:00
|
|
|
let https;
|
|
|
|
if (options.method === 'POST' || options.sign) {
|
|
|
|
https = httpsNoRedirects;
|
|
|
|
} else {
|
|
|
|
https = httpsWithRedirects;
|
|
|
|
}
|
|
|
|
|
2023-01-09 03:34:30 +00:00
|
|
|
const req = https.request(url, options, res => {
|
|
|
|
let body = [];
|
|
|
|
res.on('data', d => {
|
|
|
|
body.push(d);
|
|
|
|
});
|
|
|
|
|
|
|
|
res.on('end', () => {
|
|
|
|
body = Buffer.concat(body).toString();
|
|
|
|
|
|
|
|
if (res.statusCode < 200 || res.statusCode > 299) {
|
2023-02-25 04:43:58 +00:00
|
|
|
return cbWrapper(
|
2023-01-12 05:37:09 +00:00
|
|
|
Errors.HttpError(
|
2023-02-08 19:53:56 +00:00
|
|
|
`URL ${url} HTTP error ${res.statusCode}: ${truncate(body, {
|
|
|
|
length: 128,
|
|
|
|
})}`
|
2023-01-14 06:55:05 +00:00
|
|
|
)
|
2023-01-12 05:37:09 +00:00
|
|
|
);
|
2023-01-09 03:34:30 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 04:43:58 +00:00
|
|
|
return cbWrapper(null, body, res);
|
2023-01-09 03:34:30 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
if (isObject(options.sign)) {
|
|
|
|
try {
|
|
|
|
httpSignature.sign(req, options.sign);
|
|
|
|
} catch (e) {
|
2023-02-25 18:50:30 +00:00
|
|
|
req.destroy(Errors.Invalid(`Invalid signing material: ${e}`));
|
2023-01-09 03:34:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
req.on('error', err => {
|
2023-02-25 04:43:58 +00:00
|
|
|
return cbWrapper(err);
|
2023-01-09 03:34:30 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
req.on('timeout', () => {
|
2023-02-25 18:50:30 +00:00
|
|
|
req.destroy(Errors.Timeout('Timeout making HTTP request'));
|
2023-01-09 03:34:30 +00:00
|
|
|
});
|
|
|
|
|
2023-01-20 05:31:14 +00:00
|
|
|
if (options.body) {
|
|
|
|
req.write(options.body);
|
|
|
|
}
|
2023-01-12 05:37:09 +00:00
|
|
|
req.end();
|
|
|
|
}
|