'use strict'; /** * Validate a webhook URL. * Returns { ok: true, url } or { ok: false, reason }. */ function validateWebhookUrl(raw) { if (!raw) return { ok: true, url: null }; let url; try { url = new URL(raw); } catch { return { ok: false, reason: 'Invalid URL format' }; } if (!['http:', 'https:'].includes(url.protocol)) { return { ok: false, reason: 'Webhook URL must use http or https' }; } const host = url.hostname.toLowerCase(); if ( host === 'localhost' || host === '::1' || /^127\./.test(host) || /^10\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host) || /^169\.254\./.test(host) || /^fe80:/i.test(host) ) { return { ok: false, reason: 'Webhook URL must not point to a private/internal address' }; } return { ok: true, url }; } /** * Replace {{param}} placeholders in a command string. * Throws if a substituted value contains unsafe characters. */ function applyParams(command, params) { return command.replace(/\{\{(\w+)\}\}/g, (match, key) => { if (!(key in params)) return match; const val = String(params[key]).trim(); if (!/^[a-zA-Z0-9._:@\-\/]+$/.test(val)) { throw new Error(`Unsafe value for workflow parameter "${key}"`); } return val; }); } /** * Evaluate a condition expression safely using vm.runInNewContext. * Returns boolean (false on error). */ function evalCondition(condition, state, params) { const vm = require('vm'); try { const context = vm.createContext({ state, params, promptResponse: state.promptResponse }); return !!vm.runInNewContext(condition, context, { timeout: 100 }); } catch (e) { console.warn(`[Workflow] evalCondition error (treated as false): ${e.message} — condition: ${condition}`); return false; } } /** * Calculate the next run date for a scheduled command. */ function calculateNextRun(scheduleType, scheduleValue) { const cronParser = require('cron-parser'); const now = new Date(); if (scheduleType === 'interval') { const minutes = parseInt(scheduleValue); if (isNaN(minutes) || minutes <= 0) throw new Error(`Invalid interval value: ${scheduleValue}`); return new Date(now.getTime() + minutes * 60000); } else if (scheduleType === 'daily') { const [hours, minutes] = scheduleValue.split(':').map(Number); if (isNaN(hours) || isNaN(minutes)) throw new Error(`Invalid daily time format: ${scheduleValue}`); const next = new Date(now); next.setHours(hours, minutes, 0, 0); if (next <= now) next.setDate(next.getDate() + 1); return next; } else if (scheduleType === 'hourly') { const hours = parseInt(scheduleValue); if (isNaN(hours) || hours <= 0) throw new Error(`Invalid hourly value: ${scheduleValue}`); return new Date(now.getTime() + hours * 3600000); } else if (scheduleType === 'cron') { const interval = cronParser.CronExpressionParser.parse(scheduleValue, { currentDate: now }); return interval.next().toDate(); } throw new Error(`Unknown schedule type: ${scheduleType}`); } module.exports = { validateWebhookUrl, applyParams, evalCondition, calculateNextRun };