From b82a2c33121ff773d3cb02b1bdd2561e24fd3de0 Mon Sep 17 00:00:00 2001 From: Matthew Dickinson Date: Fri, 18 Oct 2024 17:46:51 -0400 Subject: [PATCH 1/3] Added cookie support to CLI requests --- packages/bruno-cli/package.json | 1 + packages/bruno-cli/src/commands/run.js | 9 ++ .../src/runner/run-single-request.js | 14 +++ packages/bruno-cli/src/utils/cookies.js | 100 ++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 packages/bruno-cli/src/utils/cookies.js diff --git a/packages/bruno-cli/package.json b/packages/bruno-cli/package.json index 5020f8475..f62f9dc64 100644 --- a/packages/bruno-cli/package.json +++ b/packages/bruno-cli/package.json @@ -41,6 +41,7 @@ "lodash": "^4.17.21", "qs": "^6.11.0", "socks-proxy-agent": "^8.0.2", + "tough-cookie": "^4.1.3", "@usebruno/vm2": "^3.9.13", "xmlbuilder": "^15.1.1", "yargs": "^17.6.2" diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 58b3cdf80..6f5b942d4 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -211,6 +211,11 @@ const builder = async (yargs) => { description: 'The specified custom CA certificate (--cacert) will be used exclusively and the default truststore is ignored, if this option is specified. Evaluated in combination with "--cacert" only.' }) + .option('use-cookies', { + type: 'boolean', + default: false, + description: 'Automatically save and sent cookies with requests' + }) .option('env', { describe: 'Environment variables', type: 'string' @@ -301,6 +306,7 @@ const handler = async function (argv) { filename, cacert, ignoreTruststore, + useCookies, env, envVar, insecure, @@ -392,6 +398,9 @@ const handler = async function (argv) { if (insecure) { options['insecure'] = true; } + if (useCookies) { + options['useCookies'] = true; + } if (cacert && cacert.length) { if (insecure) { console.error(chalk.red(`Ignoring the cacert option since insecure connections are enabled`)); diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index cb59c78ba..9a4f22370 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -20,6 +20,7 @@ const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-he const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util'); const path = require('path'); const { createFormData } = require('../utils/common'); +const { getCookieStringForUrl, saveCookies, shouldUseCookies } = require('../utils/cookies'); const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/; const onConsoleLog = (type, args) => { @@ -178,6 +179,14 @@ const runSingleRequest = async function ( }); } + //set cookies if enabled + if (options.useCookies) { + const cookieString = getCookieStringForUrl(request.url); + if (cookieString && typeof cookieString === 'string' && cookieString.length) { + request.headers['cookie'] = cookieString; + } + } + // stringify the request url encoded params if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { request.data = qs.stringify(request.data); @@ -220,6 +229,11 @@ const runSingleRequest = async function ( // Prevents the duration on leaking to the actual result responseTime = response.headers.get('request-duration'); response.headers.delete('request-duration'); + + //save cookies if enabled + if (options.useCookies) { + saveCookies(request.url, response.headers); + } } catch (err) { if (err?.response) { response = err.response; diff --git a/packages/bruno-cli/src/utils/cookies.js b/packages/bruno-cli/src/utils/cookies.js new file mode 100644 index 000000000..acb58b505 --- /dev/null +++ b/packages/bruno-cli/src/utils/cookies.js @@ -0,0 +1,100 @@ +const { Cookie, CookieJar } = require('tough-cookie'); +const each = require('lodash/each'); + +const cookieJar = new CookieJar(); + +const addCookieToJar = (setCookieHeader, requestUrl) => { + const cookie = Cookie.parse(setCookieHeader, { loose: true }); + cookieJar.setCookieSync(cookie, requestUrl, { + ignoreError: true // silently ignore things like parse errors and invalid domains + }); +}; + +const getCookiesForUrl = (url) => { + return cookieJar.getCookiesSync(url); +}; + +const getCookieStringForUrl = (url) => { + const cookies = getCookiesForUrl(url); + + if (!Array.isArray(cookies) || !cookies.length) { + return ''; + } + + const validCookies = cookies.filter((cookie) => !cookie.expires || cookie.expires > Date.now()); + + return validCookies.map((cookie) => cookie.cookieString()).join('; '); +}; + +const getDomainsWithCookies = () => { + return new Promise((resolve, reject) => { + const domainCookieMap = {}; + + cookieJar.store.getAllCookies((err, cookies) => { + if (err) { + return reject(err); + } + + cookies.forEach((cookie) => { + if (!domainCookieMap[cookie.domain]) { + domainCookieMap[cookie.domain] = [cookie]; + } else { + domainCookieMap[cookie.domain].push(cookie); + } + }); + + const domains = Object.keys(domainCookieMap); + const domainsWithCookies = []; + + each(domains, (domain) => { + const cookies = domainCookieMap[domain]; + const validCookies = cookies.filter((cookie) => !cookie.expires || cookie.expires > Date.now()); + + if (validCookies.length) { + domainsWithCookies.push({ + domain, + cookies: validCookies, + cookieString: validCookies.map((cookie) => cookie.cookieString()).join('; ') + }); + } + }); + + resolve(domainsWithCookies); + }); + }); +}; + +const deleteCookiesForDomain = (domain) => { + return new Promise((resolve, reject) => { + cookieJar.store.removeCookies(domain, null, (err) => { + if (err) { + return reject(err); + } + + return resolve(); + }); + }); +}; + +const saveCookies = (url, headers) => { + let setCookieHeaders = []; + if (headers['set-cookie']) { + setCookieHeaders = Array.isArray(headers['set-cookie']) + ? headers['set-cookie'] + : [headers['set-cookie']]; + for (let setCookieHeader of setCookieHeaders) { + if (typeof setCookieHeader === 'string' && setCookieHeader.length) { + addCookieToJar(setCookieHeader, url); + } + } + } +} + +module.exports = { + addCookieToJar, + getCookiesForUrl, + getCookieStringForUrl, + getDomainsWithCookies, + deleteCookiesForDomain, + saveCookies +}; From 9d4246d74b6aca0394eff646711a81c3c22b0eaa Mon Sep 17 00:00:00 2001 From: lohit Date: Thu, 21 Nov 2024 17:51:18 +0530 Subject: [PATCH 2/3] changed `use-cookies` to `disable-cookies` support for sending cookies by default --- packages/bruno-cli/src/commands/run.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 6f5b942d4..0e1de296b 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -211,7 +211,7 @@ const builder = async (yargs) => { description: 'The specified custom CA certificate (--cacert) will be used exclusively and the default truststore is ignored, if this option is specified. Evaluated in combination with "--cacert" only.' }) - .option('use-cookies', { + .option('disable-cookies', { type: 'boolean', default: false, description: 'Automatically save and sent cookies with requests' @@ -306,7 +306,7 @@ const handler = async function (argv) { filename, cacert, ignoreTruststore, - useCookies, + disableCookies, env, envVar, insecure, @@ -398,8 +398,8 @@ const handler = async function (argv) { if (insecure) { options['insecure'] = true; } - if (useCookies) { - options['useCookies'] = true; + if (disableCookies) { + options['disableCookies'] = true; } if (cacert && cacert.length) { if (insecure) { From 6d8cc38946c414a368de878bcd1395144ebc1daf Mon Sep 17 00:00:00 2001 From: lohit Date: Thu, 21 Nov 2024 17:53:09 +0530 Subject: [PATCH 3/3] changed `use-cookies` to `disable-cookies` --- packages/bruno-cli/src/runner/run-single-request.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 9a4f22370..e4f3740f6 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -180,7 +180,7 @@ const runSingleRequest = async function ( } //set cookies if enabled - if (options.useCookies) { + if (!options.disableCookies) { const cookieString = getCookieStringForUrl(request.url); if (cookieString && typeof cookieString === 'string' && cookieString.length) { request.headers['cookie'] = cookieString; @@ -231,7 +231,7 @@ const runSingleRequest = async function ( response.headers.delete('request-duration'); //save cookies if enabled - if (options.useCookies) { + if (!options.disableCookies) { saveCookies(request.url, response.headers); } } catch (err) {