All files / src/lib/scriptEventsHandlers trustedHandler.ts

100% Statements 64/64
100% Branches 9/9
100% Functions 14/14
100% Lines 63/63

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195                      9x                       9x 19x     19x           19x     1x   3x 3x 1x         19x     19x     36x 36x 35x   1x           19x 17x 36x   17x       19x     3x       19x         19x           9x         12070x     12070x 12070x 12070x   12070x 19x       12070x 12070x     12000x 11997x   3x         12070x 24140x     12070x               12070x 12067x   8x       12062x     12058x 12058x     12058x     12058x       198x 198x 195x         3x       12058x             12058x   23x 23x 22x   1x         12058x 175x 175x   6x         12058x   12058x   12x      
/**
 * Trusted Script Event Handler Loader
 *
 * Executes handler code using native eval for trusted contexts.
 */
 
import type {
  FileReaderFn,
  ScriptEventHandler,
  ScriptEventHandlerWithCleanup,
} from "lib/scriptEventsHandlers/handlerTypes";
import {
  detectModuleSystem,
  createMockRequire,
  validateHandlerMetadata,
  createHandlerBase,
  finalizeHandler,
  noReadFileFn,
} from "./handlerCommon";
 
/**
 * Converts ES modules to CommonJS for eval compatibility.
 */
const convertESMToCommonJS = (code: string): string => {
  let convertedCode = code;
 
  // Convert export default statements
  convertedCode = convertedCode.replace(
    /export\s+default\s+(.+?)(?:;|$)/gm,
    "module.exports = $1;",
  );
 
  // Convert named exports
  convertedCode = convertedCode.replace(
    /export\s*\{\s*([^}]+)\s*\}\s*;?/gm,
    (match, exports) => {
      const exportList = exports
        .split(",")
        .map((exp: string) => exp.trim())
        .filter((exp: string) => exp);
      return `module.exports = { ${exportList.join(", ")} };`;
    },
  );
 
  // Track individual exports to build a combined module.exports
  const individualExports: string[] = [];
 
  // Convert individual export declarations like "export const id = 'value'"
  convertedCode = convertedCode.replace(
    /export\s+(const|let|var|function|class)\s+(\w+)(\s*=\s*[^;]+)?/gm,
    (match, keyword, name, assignment) => {
      individualExports.push(name);
      if (assignment) {
        return `${keyword} ${name}${assignment}`;
      } else {
        return `${keyword} ${name}`;
      }
    },
  );
 
  // If we found individual exports, add them to module.exports
  if (individualExports.length > 0) {
    const exportsAssignment = individualExports
      .map((name) => `${name}`)
      .join(", ");
    convertedCode += `\nmodule.exports = { ${exportsAssignment} };`;
  }
 
  // Convert import statements to require calls
  convertedCode = convertedCode.replace(
    /import\s+(\w+)\s+from\s+['"](.+?)['"];?\s*$/gm,
    (match, varName, moduleName) => {
      return `const _mod_${varName} = require("${moduleName}"); const ${varName} = _mod_${varName}.default || _mod_${varName}.${varName} || _mod_${varName};`;
    },
  );
 
  convertedCode = convertedCode.replace(
    /import\s*\{\s*([^}]+)\s*\}\s*from\s+['"](.+?)['"];?\s*$/gm,
    'const { $1 } = require("$2");',
  );
 
  return convertedCode;
};
 
/**
 * Loads script event handlers using native eval for trusted code execution.
 */
export const loadScriptEventHandlerFromTrustedString = async (
  code: string,
  filename: string,
  readFile: FileReaderFn = noReadFileFn,
): Promise<ScriptEventHandlerWithCleanup> => {
  try {
    // Execute handler code with ES module conversion
    let metadata: unknown;
    try {
      const moduleSystem = detectModuleSystem(code);
      let processedCode = code;
 
      if (moduleSystem === "module") {
        processedCode = convertESMToCommonJS(code);
      }
 
      // Create controlled sandbox with mock modules
      const mockRequire = createMockRequire(readFile);
      const sandbox = {
        module: { exports: {} },
        require: (moduleName: string): unknown => {
          if (mockRequire[moduleName]) {
            return mockRequire[moduleName];
          }
          throw new Error(`Module '${moduleName}' not found in mock modules`);
        },
      };
 
      // Prepare sandbox variable declarations
      const contextSetup = Object.keys(sandbox)
        .map((key) => `var ${key} = arguments[0].${key};`)
        .join("\n");
 
      const wrappedCode = `
        ${contextSetup}
        ${processedCode}
        return module.exports;
      `;
 
      // Execute code with eval (must be trusted)
      // eslint-disable-next-line no-new-func
      const vmFunction = new Function(wrappedCode);
      metadata = vmFunction.call(null, sandbox);
    } catch (error) {
      throw error;
    }
 
    // Validate required handler metadata
    validateHandlerMetadata(metadata, filename);
 
    // Store module exports for function calling
    const moduleExports = metadata as Record<string, unknown>;
    const hasAutoLabelFunction = typeof moduleExports.autoLabel === "function";
 
    // No-op cleanup for eval-based execution
    const cleanup = () => {};
 
    // Direct function calls without VM overhead
    const callModuleFunction = (
      functionName: string,
      args: unknown[],
    ): unknown => {
      const targetFunction = moduleExports[functionName];
      if (typeof targetFunction === "function") {
        return (targetFunction as (...args: unknown[]) => unknown).apply(
          moduleExports,
          args,
        );
      }
      throw new Error(`Function '${functionName}' not found in module exports`);
    };
 
    // Create base handler using shared utilities
    const handler = createHandlerBase(
      metadata as Record<string, unknown>,
      hasAutoLabelFunction,
      cleanup,
    ) as ScriptEventHandler & { cleanup: () => void };
 
    // Add execution-specific methods
    handler.autoLabel = hasAutoLabelFunction
      ? (lookup: (key: string) => string, input: Record<string, unknown>) => {
          try {
            const result = callModuleFunction("autoLabel", [lookup, input]);
            return String(result);
          } catch (error) {
            throw error;
          }
        }
      : undefined;
 
    handler.compile = (input: unknown, helpers: unknown) => {
      try {
        callModuleFunction("compile", [input, helpers]);
      } catch (error) {
        throw error;
      }
    };
 
    // Finalize handler with lookup tables and validation
    finalizeHandler(handler);
 
    return handler;
  } catch (error) {
    throw error;
  }
};