822 lines
19 KiB
JavaScript
822 lines
19 KiB
JavaScript
const { spawn } = require('child_process');
|
|
|
|
const STATUS_MARKER = '__CODEX_HTTP_STATUS__:';
|
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
const MAX_CAPTURE_LENGTH = 1024 * 1024;
|
|
const DATA_FLAGS = new Set(['--data', '--data-raw', '--data-binary', '-d']);
|
|
const HEADER_FLAGS = new Set(['--header', '-H']);
|
|
const METHOD_FLAGS = new Set(['--request', '-X']);
|
|
const IGNORED_HEADER_KEYS = new Set(['content-length']);
|
|
const HEADER_ID_PREFIX = 'header';
|
|
const STRIP_VALUE_FLAGS = new Set(['--write-out', '-w', '--output', '-o', '--dump-header', '-D']);
|
|
const STRIP_BOOLEAN_FLAGS = new Set([
|
|
'--silent',
|
|
'-s',
|
|
'--show-error',
|
|
'-S',
|
|
'--include',
|
|
'-i',
|
|
'--verbose',
|
|
'-v',
|
|
'--remote-name',
|
|
'-O',
|
|
'--remote-header-name',
|
|
'-J',
|
|
'--fail',
|
|
'-f',
|
|
'--fail-with-body',
|
|
]);
|
|
const TOKEN_REGEX = /__(?:PROFILE|SMS)_[A-Z0-9_]+__/g;
|
|
|
|
function createExecutionError(message, extra = {}) {
|
|
const error = new Error(message);
|
|
Object.assign(error, extra);
|
|
return error;
|
|
}
|
|
|
|
function normalizeText(value) {
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
}
|
|
|
|
function skipShellIndentation(input, index) {
|
|
let cursor = index;
|
|
|
|
while (cursor < input.length && /[\t \f\v\u00a0]/.test(input[cursor])) {
|
|
cursor += 1;
|
|
}
|
|
|
|
return cursor;
|
|
}
|
|
|
|
function normalizeCommand(command) {
|
|
const input = String(command || '')
|
|
.replace(/\r\n/g, '\n')
|
|
.replace(/\r/g, '\n');
|
|
|
|
let output = '';
|
|
let quote = null;
|
|
|
|
for (let index = 0; index < input.length; index += 1) {
|
|
const char = input[index];
|
|
|
|
if (quote === '\'') {
|
|
output += char;
|
|
if (char === '\'') {
|
|
quote = null;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (quote === '"') {
|
|
output += char;
|
|
|
|
if (char === '\\' && index + 1 < input.length) {
|
|
output += input[index + 1];
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (char === '"') {
|
|
quote = null;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (char === '\'' || char === '"') {
|
|
quote = char;
|
|
output += char;
|
|
continue;
|
|
}
|
|
|
|
if (char === '\\') {
|
|
const nextChar = input[index + 1];
|
|
if (nextChar === '\n') {
|
|
output += ' ';
|
|
index = skipShellIndentation(input, index + 2) - 1;
|
|
continue;
|
|
}
|
|
|
|
if (nextChar === 'n') {
|
|
output += ' ';
|
|
index = skipShellIndentation(input, index + 2) - 1;
|
|
continue;
|
|
}
|
|
|
|
if (nextChar === 'r' && input[index + 2] === 'n') {
|
|
output += ' ';
|
|
index = skipShellIndentation(input, index + 3) - 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
output += char;
|
|
}
|
|
|
|
return output.trim();
|
|
}
|
|
|
|
function tokenizeCurlCommand(command) {
|
|
const input = normalizeCommand(command);
|
|
const tokens = [];
|
|
let current = '';
|
|
let quote = null;
|
|
let escaping = false;
|
|
|
|
for (let index = 0; index < input.length; index += 1) {
|
|
const char = input[index];
|
|
|
|
if (escaping) {
|
|
current += char;
|
|
escaping = false;
|
|
continue;
|
|
}
|
|
|
|
if (quote === '\'') {
|
|
if (char === '\'') {
|
|
quote = null;
|
|
} else {
|
|
current += char;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (quote === '"') {
|
|
if (char === '"') {
|
|
quote = null;
|
|
continue;
|
|
}
|
|
|
|
if (char === '\\') {
|
|
const nextChar = input[index + 1];
|
|
if (nextChar) {
|
|
current += nextChar;
|
|
index += 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
current += char;
|
|
continue;
|
|
}
|
|
|
|
if (char === '\\') {
|
|
escaping = true;
|
|
continue;
|
|
}
|
|
|
|
if (char === '\'' || char === '"') {
|
|
quote = char;
|
|
continue;
|
|
}
|
|
|
|
if (/\s/.test(char)) {
|
|
if (current) {
|
|
tokens.push(current);
|
|
current = '';
|
|
}
|
|
continue;
|
|
}
|
|
|
|
current += char;
|
|
}
|
|
|
|
if (escaping) {
|
|
current += '\\';
|
|
}
|
|
|
|
if (quote) {
|
|
throw createExecutionError('Stored cURL contains an unterminated quoted value.', {
|
|
code: 'INVALID_CURL_TEMPLATE',
|
|
});
|
|
}
|
|
|
|
if (current) {
|
|
tokens.push(current);
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
function parseCurlCommand(command) {
|
|
const tokens = tokenizeCurlCommand(command);
|
|
if (tokens.length === 0 || tokens[0] !== 'curl') {
|
|
throw createExecutionError('Stored cURL template must start with "curl".', {
|
|
code: 'INVALID_CURL_TEMPLATE',
|
|
});
|
|
}
|
|
|
|
return {
|
|
command: 'curl',
|
|
args: tokens.slice(1),
|
|
};
|
|
}
|
|
|
|
function collectHeaders(args = []) {
|
|
const headers = {};
|
|
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const argument = args[index];
|
|
let rawHeader = '';
|
|
|
|
if (HEADER_FLAGS.has(argument) && index + 1 < args.length) {
|
|
rawHeader = String(args[index + 1] || '');
|
|
index += 1;
|
|
} else if (argument.startsWith('--header=')) {
|
|
rawHeader = argument.slice('--header='.length);
|
|
} else if (argument.startsWith('-H=')) {
|
|
rawHeader = argument.slice(3);
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
const separatorIndex = rawHeader.indexOf(':');
|
|
if (separatorIndex < 0) continue;
|
|
|
|
const key = normalizeText(rawHeader.slice(0, separatorIndex));
|
|
const value = normalizeText(rawHeader.slice(separatorIndex + 1));
|
|
if (!key) continue;
|
|
headers[key] = value;
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
function sanitizeHeaders(headers = {}) {
|
|
return Object.fromEntries(
|
|
Object.entries(headers || {}).filter(([key]) => {
|
|
const normalizedKey = normalizeText(key).toLowerCase();
|
|
return normalizedKey && !IGNORED_HEADER_KEYS.has(normalizedKey);
|
|
}),
|
|
);
|
|
}
|
|
|
|
function getDataArguments(args = []) {
|
|
const dataArgs = [];
|
|
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const argument = args[index];
|
|
|
|
if (DATA_FLAGS.has(argument) && index + 1 < args.length) {
|
|
dataArgs.push(String(args[index + 1] || ''));
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
const flag = Array.from(DATA_FLAGS).find((entry) => argument.startsWith(`${entry}=`));
|
|
if (flag) {
|
|
dataArgs.push(argument.slice(flag.length + 1));
|
|
}
|
|
}
|
|
|
|
return dataArgs;
|
|
}
|
|
|
|
function extractMethod(args = [], dataArgs = []) {
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const argument = args[index];
|
|
|
|
if (METHOD_FLAGS.has(argument) && index + 1 < args.length) {
|
|
return normalizeText(args[index + 1]).toUpperCase() || 'POST';
|
|
}
|
|
|
|
if (argument.startsWith('--request=')) {
|
|
return normalizeText(argument.slice('--request='.length)).toUpperCase() || 'POST';
|
|
}
|
|
|
|
if (argument.startsWith('-X=')) {
|
|
return normalizeText(argument.slice(3)).toUpperCase() || 'POST';
|
|
}
|
|
}
|
|
|
|
return dataArgs.length > 0 ? 'POST' : 'GET';
|
|
}
|
|
|
|
function extractUrl(args = []) {
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const argument = String(args[index] || '');
|
|
|
|
if (argument === '--url' && index + 1 < args.length) {
|
|
return normalizeText(args[index + 1]);
|
|
}
|
|
|
|
if (argument.startsWith('--url=')) {
|
|
return normalizeText(argument.slice('--url='.length));
|
|
}
|
|
|
|
if (/^https?:\/\//i.test(argument)) {
|
|
return normalizeText(argument);
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
function buildHeaderEntries(headers = {}) {
|
|
return Object.entries(sanitizeHeaders(headers)).map(([key, value], index) => ({
|
|
id: `${HEADER_ID_PREFIX}-${index}`,
|
|
key,
|
|
value,
|
|
enabled: true,
|
|
}));
|
|
}
|
|
|
|
function normalizeHeaderEntries(headerEntries = [], fallbackHeaders = {}) {
|
|
const normalizedEntries = [];
|
|
const seenIds = new Set();
|
|
|
|
(Array.isArray(headerEntries) ? headerEntries : []).forEach((entry, index) => {
|
|
const key = normalizeText(entry?.key);
|
|
const value = normalizeText(entry?.value);
|
|
const enabled = entry?.enabled !== false;
|
|
const fallbackId = `${HEADER_ID_PREFIX}-${index}`;
|
|
const rawId = normalizeText(entry?.id) || fallbackId;
|
|
const id = seenIds.has(rawId) ? `${rawId}-${index}` : rawId;
|
|
|
|
if (!key && !value) return;
|
|
|
|
seenIds.add(id);
|
|
normalizedEntries.push({
|
|
id,
|
|
key,
|
|
value,
|
|
enabled,
|
|
});
|
|
});
|
|
|
|
if (normalizedEntries.length > 0) {
|
|
return normalizedEntries;
|
|
}
|
|
|
|
if (Array.isArray(fallbackHeaders)) {
|
|
return normalizeHeaderEntries(fallbackHeaders, {});
|
|
}
|
|
|
|
return buildHeaderEntries(fallbackHeaders);
|
|
}
|
|
|
|
function buildRequestBlueprintFromCurl(rawCurlTemplate = '') {
|
|
const parsed = parseCurlCommand(String(rawCurlTemplate || ''));
|
|
const url = extractUrl(parsed.args);
|
|
|
|
if (!url) {
|
|
throw createExecutionError('Stored cURL template must include an absolute http(s) URL.', {
|
|
code: 'INVALID_CURL_TEMPLATE',
|
|
status: 422,
|
|
});
|
|
}
|
|
|
|
const dataArgs = getDataArguments(parsed.args);
|
|
const method = extractMethod(parsed.args, dataArgs);
|
|
const headers = sanitizeHeaders(collectHeaders(parsed.args));
|
|
|
|
return {
|
|
method,
|
|
url,
|
|
headers,
|
|
headerEntries: buildHeaderEntries(headers),
|
|
};
|
|
}
|
|
|
|
function quoteCurlValue(value = '') {
|
|
return `'${String(value ?? '').replace(/'/g, `'\\''`)}'`;
|
|
}
|
|
|
|
function serializeCurlTemplateFromArgs(args = []) {
|
|
return ['curl', ...(Array.isArray(args) ? args : []).map((argument) => quoteCurlValue(argument))].join(' ');
|
|
}
|
|
|
|
function buildPatchedCurlTemplateFromRequest(rawCurlTemplate = '', requestPatch = {}) {
|
|
const parsed = parseCurlCommand(String(rawCurlTemplate || ''));
|
|
const currentRequest = buildRequestBlueprintFromCurl(rawCurlTemplate);
|
|
const nextUrl = normalizeText(requestPatch?.url) || currentRequest.url;
|
|
const nextHeaders = normalizeHeaderEntries(
|
|
requestPatch?.headers,
|
|
currentRequest.headerEntries,
|
|
);
|
|
const nextHeaderArgs = nextHeaders
|
|
.filter((entry) => entry.enabled !== false)
|
|
.filter((entry) => normalizeText(entry.key))
|
|
.flatMap((entry) => ['--header', `${entry.key}: ${entry.value}`]);
|
|
|
|
const patchedArgs = [];
|
|
let insertedUrl = false;
|
|
let insertedHeaders = false;
|
|
|
|
for (let index = 0; index < parsed.args.length; index += 1) {
|
|
const argument = parsed.args[index];
|
|
|
|
if (HEADER_FLAGS.has(argument) && index + 1 < parsed.args.length) {
|
|
if (!insertedHeaders) {
|
|
patchedArgs.push(...nextHeaderArgs);
|
|
insertedHeaders = true;
|
|
}
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (argument.startsWith('--header=') || argument.startsWith('-H=')) {
|
|
if (!insertedHeaders) {
|
|
patchedArgs.push(...nextHeaderArgs);
|
|
insertedHeaders = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (argument === '--url' && index + 1 < parsed.args.length) {
|
|
if (!insertedUrl) {
|
|
patchedArgs.push('--url', nextUrl);
|
|
insertedUrl = true;
|
|
}
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (argument.startsWith('--url=')) {
|
|
if (!insertedUrl) {
|
|
patchedArgs.push('--url', nextUrl);
|
|
insertedUrl = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (/^https?:\/\//i.test(String(argument || ''))) {
|
|
if (!insertedUrl) {
|
|
patchedArgs.push(nextUrl);
|
|
insertedUrl = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
patchedArgs.push(argument);
|
|
}
|
|
|
|
if (!insertedUrl) {
|
|
patchedArgs.push('--url', nextUrl);
|
|
}
|
|
|
|
if (!insertedHeaders && nextHeaderArgs.length > 0) {
|
|
patchedArgs.push(...nextHeaderArgs);
|
|
}
|
|
|
|
return serializeCurlTemplateFromArgs(patchedArgs);
|
|
}
|
|
|
|
function replaceTokensInString(value, tokenValues = {}) {
|
|
let output = String(value || '');
|
|
const entries = Object.entries(tokenValues).sort((left, right) => right[0].length - left[0].length);
|
|
|
|
entries.forEach(([token, replacement]) => {
|
|
if (!token) return;
|
|
output = output.split(token).join(String(replacement ?? ''));
|
|
});
|
|
|
|
return output;
|
|
}
|
|
|
|
function replaceTokensInJsonValue(value, tokenValues = {}) {
|
|
if (Array.isArray(value)) {
|
|
return value.map((entry) => replaceTokensInJsonValue(entry, tokenValues));
|
|
}
|
|
|
|
if (value && typeof value === 'object') {
|
|
return Object.entries(value).reduce((accumulator, [key, entry]) => {
|
|
accumulator[key] = replaceTokensInJsonValue(entry, tokenValues);
|
|
return accumulator;
|
|
}, {});
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
return replaceTokensInString(value, tokenValues);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function normalizeJsonFormattingEscapes(value) {
|
|
const input = String(value || '')
|
|
.replace(/\r\n/g, '\n')
|
|
.replace(/\r/g, '\n');
|
|
|
|
let output = '';
|
|
let inString = false;
|
|
let escaping = false;
|
|
|
|
for (let index = 0; index < input.length; index += 1) {
|
|
const char = input[index];
|
|
|
|
if (inString) {
|
|
output += char;
|
|
|
|
if (escaping) {
|
|
escaping = false;
|
|
continue;
|
|
}
|
|
|
|
if (char === '\\') {
|
|
escaping = true;
|
|
continue;
|
|
}
|
|
|
|
if (char === '"') {
|
|
inString = false;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (char === '"') {
|
|
inString = true;
|
|
output += char;
|
|
continue;
|
|
}
|
|
|
|
if (char === '\\') {
|
|
const nextChar = input[index + 1];
|
|
|
|
if (nextChar === 'n') {
|
|
output += '\n';
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (nextChar === 'r' && input[index + 2] === 'n') {
|
|
output += '\n';
|
|
index += 2;
|
|
continue;
|
|
}
|
|
|
|
if (nextChar === 'r') {
|
|
output += '\n';
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (nextChar === 't') {
|
|
output += '\t';
|
|
index += 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
output += char;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
function parseJsonLikeArgument(value) {
|
|
const trimmed = String(value || '').trim();
|
|
if (
|
|
!trimmed
|
|
|| !(
|
|
(trimmed.startsWith('{') && trimmed.endsWith('}'))
|
|
|| (trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(trimmed);
|
|
} catch {
|
|
const normalized = normalizeJsonFormattingEscapes(trimmed);
|
|
if (normalized === trimmed) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(normalized);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function hydrateDataArgument(rawArgument, tokenValues = {}) {
|
|
const trimmed = String(rawArgument || '').trim();
|
|
if (!trimmed) return '';
|
|
|
|
const parsedJson = parseJsonLikeArgument(trimmed);
|
|
if (parsedJson !== null) {
|
|
return JSON.stringify(replaceTokensInJsonValue(parsedJson, tokenValues));
|
|
}
|
|
|
|
return replaceTokensInString(rawArgument, tokenValues);
|
|
}
|
|
|
|
function hydrateCurlArgs(args = [], tokenValues = {}) {
|
|
const hydratedArgs = [];
|
|
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const argument = args[index];
|
|
|
|
if (DATA_FLAGS.has(argument) && index + 1 < args.length) {
|
|
hydratedArgs.push(argument);
|
|
hydratedArgs.push(hydrateDataArgument(args[index + 1], tokenValues));
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
const dataFlagWithValue = Array.from(DATA_FLAGS).find((flag) => argument.startsWith(`${flag}=`));
|
|
if (dataFlagWithValue) {
|
|
const rawValue = argument.slice(dataFlagWithValue.length + 1);
|
|
hydratedArgs.push(`${dataFlagWithValue}=${hydrateDataArgument(rawValue, tokenValues)}`);
|
|
continue;
|
|
}
|
|
|
|
hydratedArgs.push(replaceTokensInString(argument, tokenValues));
|
|
}
|
|
|
|
return hydratedArgs;
|
|
}
|
|
|
|
function normalizeExecutionArgs(args = []) {
|
|
const normalizedArgs = [];
|
|
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const argument = args[index];
|
|
|
|
if (STRIP_BOOLEAN_FLAGS.has(argument)) {
|
|
continue;
|
|
}
|
|
|
|
const stripValueFlag = Array.from(STRIP_VALUE_FLAGS).find((flag) => argument === flag || argument.startsWith(`${flag}=`));
|
|
if (stripValueFlag) {
|
|
if (argument === stripValueFlag) {
|
|
index += 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
normalizedArgs.push(argument);
|
|
}
|
|
|
|
normalizedArgs.push(
|
|
'--silent',
|
|
'--show-error',
|
|
'--output',
|
|
'-',
|
|
'--write-out',
|
|
`\n${STATUS_MARKER}%{http_code}`,
|
|
);
|
|
|
|
return normalizedArgs;
|
|
}
|
|
|
|
function findUnresolvedTokens(args = []) {
|
|
const unresolved = new Set();
|
|
|
|
args.forEach((argument) => {
|
|
const matches = String(argument || '').match(TOKEN_REGEX) || [];
|
|
matches.forEach((token) => unresolved.add(token));
|
|
});
|
|
|
|
return Array.from(unresolved);
|
|
}
|
|
|
|
function appendChunk(buffer, chunk) {
|
|
const nextValue = `${buffer}${chunk}`;
|
|
if (nextValue.length <= MAX_CAPTURE_LENGTH) return nextValue;
|
|
return nextValue.slice(nextValue.length - MAX_CAPTURE_LENGTH);
|
|
}
|
|
|
|
function parseCurlStdout(stdout = '') {
|
|
const marker = `\n${STATUS_MARKER}`;
|
|
const markerIndex = stdout.lastIndexOf(marker);
|
|
|
|
if (markerIndex < 0) {
|
|
return {
|
|
statusCode: 0,
|
|
body: stdout,
|
|
};
|
|
}
|
|
|
|
const statusText = stdout.slice(markerIndex + marker.length).trim();
|
|
const parsedStatusCode = Number.parseInt(statusText, 10);
|
|
|
|
return {
|
|
statusCode: Number.isFinite(parsedStatusCode) ? parsedStatusCode : 0,
|
|
body: stdout.slice(0, markerIndex),
|
|
};
|
|
}
|
|
|
|
function parseResponseBody(body = '') {
|
|
const normalizedBody = String(body || '').trim();
|
|
if (!normalizedBody) return '';
|
|
|
|
try {
|
|
return JSON.parse(normalizedBody);
|
|
} catch {
|
|
return body;
|
|
}
|
|
}
|
|
|
|
function executeParsedCurl(command, args = [], options = {}) {
|
|
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(command, normalizeExecutionArgs(args), {
|
|
shell: false,
|
|
env: process.env,
|
|
});
|
|
|
|
let stdout = '';
|
|
let stderr = '';
|
|
let timedOut = false;
|
|
let settled = false;
|
|
|
|
const timeout = setTimeout(() => {
|
|
timedOut = true;
|
|
child.kill('SIGTERM');
|
|
setTimeout(() => {
|
|
if (!settled) child.kill('SIGKILL');
|
|
}, 1500).unref();
|
|
}, timeoutMs);
|
|
|
|
child.stdout.on('data', (chunk) => {
|
|
stdout = appendChunk(stdout, chunk.toString());
|
|
});
|
|
|
|
child.stderr.on('data', (chunk) => {
|
|
stderr = appendChunk(stderr, chunk.toString());
|
|
});
|
|
|
|
child.on('error', (error) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
clearTimeout(timeout);
|
|
reject(createExecutionError(`Failed to start curl: ${error.message}`, {
|
|
code: 'CURL_EXECUTION_START_FAILED',
|
|
}));
|
|
});
|
|
|
|
child.on('close', (exitCode, signal) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
clearTimeout(timeout);
|
|
|
|
const parsedStdout = parseCurlStdout(stdout);
|
|
const response = parseResponseBody(parsedStdout.body);
|
|
|
|
if (timedOut) {
|
|
reject(createExecutionError(`curl execution timed out after ${timeoutMs}ms`, {
|
|
code: 'CURL_EXECUTION_TIMEOUT',
|
|
details: {
|
|
timeoutMs,
|
|
stderr: stderr.trim(),
|
|
statusCode: parsedStdout.statusCode,
|
|
},
|
|
}));
|
|
return;
|
|
}
|
|
|
|
if (exitCode !== 0) {
|
|
reject(createExecutionError('curl execution failed', {
|
|
code: 'CURL_EXECUTION_FAILED',
|
|
details: {
|
|
exitCode,
|
|
signal,
|
|
stderr: stderr.trim(),
|
|
statusCode: parsedStdout.statusCode,
|
|
response,
|
|
},
|
|
}));
|
|
return;
|
|
}
|
|
|
|
resolve({
|
|
success: parsedStdout.statusCode >= 200 && parsedStdout.statusCode < 300,
|
|
exitCode,
|
|
signal,
|
|
statusCode: parsedStdout.statusCode,
|
|
response,
|
|
stderr: stderr.trim(),
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
async function executeTemplatedCurl(curlTemplate, tokenValues = {}, options = {}) {
|
|
const parsed = parseCurlCommand(curlTemplate);
|
|
const hydratedArgs = hydrateCurlArgs(parsed.args, tokenValues);
|
|
const unresolvedTokens = findUnresolvedTokens(hydratedArgs);
|
|
|
|
if (unresolvedTokens.length > 0) {
|
|
throw createExecutionError('Stored cURL still contains unresolved execution tokens.', {
|
|
code: 'UNRESOLVED_CURL_TOKENS',
|
|
details: {
|
|
unresolvedTokens,
|
|
},
|
|
});
|
|
}
|
|
|
|
return executeParsedCurl(parsed.command, hydratedArgs, options);
|
|
}
|
|
|
|
module.exports = {
|
|
buildPatchedCurlTemplateFromRequest,
|
|
buildRequestBlueprintFromCurl,
|
|
executeTemplatedCurl,
|
|
normalizeHeaderEntries,
|
|
parseCurlCommand,
|
|
};
|