<?php
/**
 * CVE matcher — maps a request path (+ context) to a known WordPress CVE ID.
 *
 * Two rule sources are merged, feed wins on conflict:
 *   1. A small built-in seed of well-known WP core/plugin exploit path
 *      signatures (regex => CVE ID), shipped with the plugin.
 *   2. Optional 'cve_rules' from the Ed25519-verified intel feed
 *      (Shieldforge_Intel_Client::feed()), refreshed centrally without a
 *      plugin update.
 *
 * Regex match only — request content is never executed or evaluated.
 *
 * @package Brainwerk Security Suite
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Shieldforge_Cve_Rules {

    /**
     * Built-in seed: regex (matched against the request path) => CVE ID.
     * A handful of real, well-known WordPress exploit path signatures.
     */
    private static $seed = array(
        // Leftover/un-cleaned installer.php after a migration — unauthenticated RCE.
        '~/wp-content/plugins/duplicator/[^/]*installer\.php~i'                 => 'CVE-2018-25095',
        '~/wp-content/plugins/file-manager/lib/php/connector\.minimal\.php~i'   => 'CVE-2020-25213',
        '~/wp-content/plugins/wp-file-manager/lib/php/connector\.minimal\.php~i' => 'CVE-2020-25213',
        '~/wp-content/plugins/wpdiscuz/.*uploadify\.php~i'                       => 'CVE-2020-24186',
        '~/wp-content/plugins/ait-csv-import-export/.*ajax_gpl\.php~i'          => 'CVE-2021-25003',
    );

    /**
     * Match a request path (+ optional ctx) against the merged rule set.
     * Returns the CVE ID of the most specific (longest regex) match, or null.
     *
     * @param string $request_path
     * @param array  $ctx unused today, reserved for future disambiguation
     * @return string|null
     */
    public static function match( $request_path, array $ctx = array() ) {
        $request_path = (string) $request_path;
        if ( '' === $request_path ) {
            return null;
        }

        $rules = self::merged_rules();
        $best_pattern = '';
        $best_cve     = null;

        foreach ( $rules as $pattern => $cve ) {
            $pattern = (string) $pattern;
            $cve     = (string) $cve;
            if ( '' === $pattern || '' === $cve ) {
                continue;
            }
            // Malformed patterns (own seed or from the feed) must never break
            // the request — suppress and skip.
            if ( 1 !== @preg_match( $pattern, $request_path ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- $pattern may be malformed (own seed or feed-supplied); failure returns false !== 1, handled by the skip below.
                continue;
            }
            if ( strlen( $pattern ) > strlen( $best_pattern ) ) {
                $best_pattern = $pattern;
                $best_cve     = $cve;
            }
        }

        return $best_cve;
    }

    /**
     * Built-in seed merged with feed-provided rules (feed wins on identical
     * pattern keys).
     *
     * @return array<string,string> pattern => CVE ID
     */
    private static function merged_rules() {
        $rules = self::$seed;

        if ( class_exists( 'Shieldforge_Intel_Client' ) ) {
            $payload = Shieldforge_Intel_Client::feed();
            if ( is_array( $payload ) && ! empty( $payload['cve_rules'] ) && is_array( $payload['cve_rules'] ) ) {
                foreach ( $payload['cve_rules'] as $pattern => $cve ) {
                    if ( is_string( $pattern ) && is_string( $cve ) && '' !== $pattern && '' !== $cve ) {
                        $rules[ $pattern ] = $cve;
                    }
                }
            }
        }

        return $rules;
    }
}
