Merge branch 'main' into feature/add-bru-setNextRequest

This commit is contained in:
Martin Hoecker
2023-10-24 22:38:44 +02:00
88 changed files with 2826 additions and 1126 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@usebruno/cli",
"version": "0.14.0",
"version": "0.15.1",
"license": "MIT",
"main": "src/index.js",
"bin": {
@@ -24,8 +24,8 @@
"package.json"
],
"dependencies": {
"@usebruno/js": "0.8.0",
"@usebruno/lang": "0.8.0",
"@usebruno/js": "0.9.1",
"@usebruno/lang": "0.9.0",
"axios": "^1.5.1",
"chai": "^4.3.7",
"chalk": "^3.0.0",

View File

@@ -162,9 +162,7 @@ const getCollectionRoot = (dir) => {
}
const content = fs.readFileSync(collectionRootPath, 'utf8');
const json = collectionBruToJson(content);
return json;
return collectionBruToJson(content);
};
const builder = async (yargs) => {

View File

@@ -88,6 +88,13 @@ const prepareRequest = (request, collectionRoot) => {
axiosRequest.data = request.body.xml;
}
if (request.body.mode === 'sparql') {
if (!contentTypeDefined) {
axiosRequest.headers['content-type'] = 'application/sparql-query';
}
axiosRequest.data = request.body.sparql;
}
if (request.body.mode === 'formUrlEncoded') {
axiosRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
const params = {};

View File

@@ -12,10 +12,10 @@ const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@use
const { stripExtension } = require('../utils/filesystem');
const { getOptions } = require('../utils/bru');
const https = require('https');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { HttpProxyAgent } = require('http-proxy-agent');
const { SocksProxyAgent } = require('socks-proxy-agent');
const { makeAxiosInstance } = require('../utils/axios-instance');
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
const runSingleRequest = async function (
filename,
@@ -48,7 +48,7 @@ const runSingleRequest = async function (
// run pre-request vars
const preRequestVars = get(bruJson, 'request.vars.req');
if (preRequestVars && preRequestVars.length) {
if (preRequestVars?.length) {
const varsRuntime = new VarsRuntime();
varsRuntime.runPreRequestVars(
preRequestVars,
@@ -65,7 +65,7 @@ const runSingleRequest = async function (
get(collectionRoot, 'request.script.req'),
get(bruJson, 'request.script.req')
]).join(os.EOL);
if (requestScriptFile && requestScriptFile.length) {
if (requestScriptFile?.length) {
const scriptRuntime = new ScriptRuntime();
const result = await scriptRuntime.runRequestScript(
decomment(requestScriptFile),
@@ -91,36 +91,56 @@ const runSingleRequest = async function (
if (insecure) {
httpsAgentRequestFields['rejectUnauthorized'] = false;
} else {
const cacertArray = [options['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS];
const cacert = cacertArray.find((el) => el);
if (cacert && cacert.length > 1) {
const caCertArray = [options['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS];
const caCert = caCertArray.find((el) => el);
if (caCert && caCert.length > 1) {
try {
caCrt = fs.readFileSync(cacert);
httpsAgentRequestFields['ca'] = caCrt;
httpsAgentRequestFields['ca'] = fs.readFileSync(caCert);
} catch (err) {
console.log('Error reading CA cert file:' + cacert, err);
console.log('Error reading CA cert file:' + caCert, err);
}
}
}
const interpolationOptions = {
envVars: envVariables,
collectionVariables,
processEnvVars
};
// client certificate config
const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []);
for (let clientCert of clientCertConfig) {
const domain = interpolateString(clientCert.domain, interpolationOptions);
const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions);
const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions);
if (domain && certFilePath && keyFilePath) {
const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*');
if (request.url.match(hostRegex)) {
try {
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
} catch (err) {
console.log('Error reading cert/key file', err);
}
httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions);
break;
}
}
}
// set proxy if enabled
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
if (proxyEnabled) {
let proxyUri;
const interpolationOptions = {
envVars: envVariables,
collectionVariables,
processEnvVars
};
const shouldProxy = shouldUseProxy(request.url, get(brunoConfig, 'proxy.bypassProxy', ''));
if (proxyEnabled && shouldProxy) {
const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions);
const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions);
const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions);
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
const socksEnabled = proxyProtocol.includes('socks');
interpolateString;
let proxyUri;
if (proxyAuthEnabled) {
const proxyAuthUsername = interpolateString(get(brunoConfig, 'proxy.auth.username'), interpolationOptions);
const proxyAuthPassword = interpolateString(get(brunoConfig, 'proxy.auth.password'), interpolationOptions);
@@ -132,16 +152,13 @@ const runSingleRequest = async function (
if (socksEnabled) {
const socksProxyAgent = new SocksProxyAgent(proxyUri);
request.httpsAgent = socksProxyAgent;
request.httpAgent = socksProxyAgent;
} else {
request.httpsAgent = new HttpsProxyAgent(
request.httpsAgent = new PatchedHttpsProxyAgent(
proxyUri,
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
);
request.httpAgent = new HttpProxyAgent(proxyUri);
}
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
@@ -167,7 +184,7 @@ const runSingleRequest = async function (
responseTime = response.headers.get('request-duration');
response.headers.delete('request-duration');
} catch (err) {
if (err && err.response) {
if (err?.response) {
response = err.response;
// Prevents the duration on leaking to the actual result
@@ -204,7 +221,7 @@ const runSingleRequest = async function (
// run post-response vars
const postResponseVars = get(bruJson, 'request.vars.res');
if (postResponseVars && postResponseVars.length) {
if (postResponseVars?.length) {
const varsRuntime = new VarsRuntime();
varsRuntime.runPostResponseVars(
postResponseVars,
@@ -222,7 +239,7 @@ const runSingleRequest = async function (
get(collectionRoot, 'request.script.res'),
get(bruJson, 'request.script.res')
]).join(os.EOL);
if (responseScriptFile && responseScriptFile.length) {
if (responseScriptFile?.length) {
const scriptRuntime = new ScriptRuntime();
const result = await scriptRuntime.runResponseScript(
decomment(responseScriptFile),
@@ -283,7 +300,7 @@ const runSingleRequest = async function (
testResults = get(result, 'results', []);
}
if (testResults && testResults.length) {
if (testResults?.length) {
each(testResults, (testResult) => {
if (testResult.status === 'pass') {
console.log(chalk.green(``) + chalk.dim(testResult.description));

View File

@@ -4,10 +4,10 @@ const axios = require('axios');
* Function that configures axios with timing interceptors
* Important to note here that the timings are not completely accurate.
* @see https://github.com/axios/axios/issues/695
* @returns {import('axios').AxiosStatic}
* @returns {axios.AxiosInstance}
*/
function makeAxiosInstance() {
/** @type {import('axios').AxiosStatic} */
/** @type {axios.AxiosInstance} */
const instance = axios.create();
instance.interceptors.request.use((config) => {
@@ -26,9 +26,7 @@ function makeAxiosInstance() {
if (error.response) {
const end = Date.now();
const start = error.config.headers['request-start-time'];
if (error.response) {
error.response.headers['request-duration'] = end - start;
}
error.response.headers['request-duration'] = end - start;
}
return Promise.reject(error);
}

View File

@@ -0,0 +1,85 @@
const parseUrl = require('url').parse;
const { isEmpty } = require('lodash');
const { HttpsProxyAgent } = require('https-proxy-agent');
const DEFAULT_PORTS = {
ftp: 21,
gopher: 70,
http: 80,
https: 443,
ws: 80,
wss: 443
};
/**
* check for proxy bypass, copied form 'proxy-from-env'
*/
const shouldUseProxy = (url, proxyBypass) => {
if (proxyBypass === '*') {
return false; // Never proxy if wildcard is set.
}
// use proxy if no proxyBypass is set
if (!proxyBypass || typeof proxyBypass !== 'string' || isEmpty(proxyBypass.trim())) {
return true;
}
const parsedUrl = typeof url === 'string' ? parseUrl(url) : url || {};
let proto = parsedUrl.protocol;
let hostname = parsedUrl.host;
let port = parsedUrl.port;
if (typeof hostname !== 'string' || !hostname || typeof proto !== 'string') {
return false; // Don't proxy URLs without a valid scheme or host.
}
proto = proto.split(':', 1)[0];
// Stripping ports in this way instead of using parsedUrl.hostname to make
// sure that the brackets around IPv6 addresses are kept.
hostname = hostname.replace(/:\d*$/, '');
port = parseInt(port) || DEFAULT_PORTS[proto] || 0;
return proxyBypass.split(/[,;\s]/).every(function (dontProxyFor) {
if (!dontProxyFor) {
return true; // Skip zero-length hosts.
}
const parsedProxy = dontProxyFor.match(/^(.+):(\d+)$/);
let parsedProxyHostname = parsedProxy ? parsedProxy[1] : dontProxyFor;
const parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2]) : 0;
if (parsedProxyPort && parsedProxyPort !== port) {
return true; // Skip if ports don't match.
}
if (!/^[.*]/.test(parsedProxyHostname)) {
// No wildcards, so stop proxying if there is an exact match.
return hostname !== parsedProxyHostname;
}
if (parsedProxyHostname.charAt(0) === '*') {
// Remove leading wildcard.
parsedProxyHostname = parsedProxyHostname.slice(1);
}
// Stop proxying if the hostname ends with the no_proxy host.
return !hostname.endsWith(parsedProxyHostname);
});
};
/**
* Patched version of HttpsProxyAgent to get around a bug that ignores
* options like ca and rejectUnauthorized when upgrading the socket to TLS:
* https://github.com/TooTallNate/proxy-agents/issues/194
*/
class PatchedHttpsProxyAgent extends HttpsProxyAgent {
constructor(proxy, opts) {
super(proxy, opts);
this.constructorOpts = opts;
}
async connect(req, opts) {
const combinedOpts = { ...this.constructorOpts, ...opts };
return super.connect(req, combinedOpts);
}
}
module.exports = {
shouldUseProxy,
PatchedHttpsProxyAgent
};