"use strict";
/**
 * Sink Matcher — Matches tainted variables to dangerous sinks.
 *
 * Scans each line for known dangerous sink patterns, then checks if
 * any tainted variable name appears on that line. Builds complete
 * taint paths from source → propagation chain → sink.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.matchSinks = matchSinks;
const sanitiser_detection_1 = require("./sanitiser-detection");
const sink_patterns_1 = require("./sink-patterns");
// ============================================================================
// Helper Functions
// ============================================================================
/**
 * Check if a tainted variable appears on a line (using word boundaries).
 */
function taintedVarAppearsOnLine(line, taintedVars) {
    for (const tv of taintedVars) {
        const regex = new RegExp(`\\b${(0, sink_patterns_1.escapeRegex)(tv.name)}\\b`);
        if (regex.test(line)) {
            return tv;
        }
    }
    return null;
}
/**
 * Build a taint chain from a tainted variable back to its source.
 * Follows the propagation chain through assignments.
 */
function buildChain(target, allVars) {
    const chain = [target];
    let current = target;
    const visited = new Set();
    visited.add(current.name);
    // Walk back through the propagation chain
    while (current.source) {
        const sourceVar = allVars.find(v => v.name === current.source.variable &&
            v.taintedAt < current.taintedAt &&
            !visited.has(v.name));
        if (!sourceVar)
            break;
        visited.add(sourceVar.name);
        chain.unshift(sourceVar);
        current = sourceVar;
    }
    return chain;
}
/**
 * Compute confidence for a taint path based on source confidence and chain length.
 */
function computePathConfidence(chain, sourceConfidence) {
    const chainLen = chain.length;
    if (sourceConfidence === 'high' && chainLen <= 3)
        return 'high';
    if (sourceConfidence === 'low' || chainLen > 5)
        return 'low';
    return 'medium';
}
/**
 * Check if the line is a parameterised query (where the tainted var
 * is in the parameter array, not interpolated into the query string).
 */
function isParameterisedQueryUsage(line, varName) {
    // Pattern: .query('...', [varName]) — var is in the parameter array
    const paramArrayPattern = new RegExp(`\\.query\\s*\\([^,]+,\\s*\\[[^\\]]*\\b${(0, sink_patterns_1.escapeRegex)(varName)}\\b[^\\]]*\\]`);
    if (paramArrayPattern.test(line))
        return true;
    // Pattern: .where('column', varName) — Knex parameterised
    const knexWherePattern = new RegExp(`\\.where\\s*\\(\\s*['"][^'"]+['"]\\s*,\\s*\\b${(0, sink_patterns_1.escapeRegex)(varName)}\\b`);
    if (knexWherePattern.test(line))
        return true;
    return false;
}
// ============================================================================
// Main Matching Function
// ============================================================================
/**
 * Match tainted variables to dangerous sinks and build taint paths.
 *
 * For each line that matches a sink pattern, checks if any tainted variable
 * appears on that line. Builds a complete TaintPath with source, chain, and sink.
 */
function matchSinks(content, _filePath, taintedVariables, sanitisers) {
    if (taintedVariables.length === 0)
        return [];
    const lines = content.split('\n');
    const taintPaths = [];
    const seen = new Set();
    // Get only unsanitised tainted variables (and all for chain building)
    const activeTaintedVars = taintedVariables.filter(tv => !tv.sanitised);
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const lineNumber = i + 1;
        const trimmed = line.trim();
        // Skip comments
        if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
            continue;
        }
        // Check each sink pattern
        for (const sp of sink_patterns_1.SINK_PATTERNS) {
            if (!sp.pattern.test(line))
                continue;
            // Check if any tainted variable appears on this line
            const taintedVar = taintedVarAppearsOnLine(line, activeTaintedVars);
            if (!taintedVar)
                continue;
            // Dedup: one taint path per source-sink-line combination
            const key = `${taintedVar.source.variable}:${sp.sinkType}:${lineNumber}`;
            if (seen.has(key))
                continue;
            seen.add(key);
            // Build the sink
            const sink = {
                line: lineNumber,
                expression: trimmed,
                sinkType: sp.sinkType,
            };
            // Build the chain from source to this variable
            const chain = buildChain(taintedVar, taintedVariables);
            // Check if sanitised at the sink line itself
            const sinkSanitResult = (0, sanitiser_detection_1.isSanitiserCall)(line, sanitisers);
            const isSanitised = sinkSanitResult.sanitised || taintedVar.sanitised;
            // Special case: parameterised SQL queries
            const isParamQuery = sp.sinkType === 'sql_query' && isParameterisedQueryUsage(line, taintedVar.name);
            const sanitised = isSanitised || isParamQuery;
            // Compute confidence
            const confidence = computePathConfidence(chain, taintedVar.source.confidence);
            taintPaths.push({
                source: taintedVar.source,
                chain,
                sink,
                sanitised,
                confidence,
            });
        }
    }
    return taintPaths;
}
//# sourceMappingURL=sink-matcher.js.map