<?php
// This file can be executed via auto_prepend_file before WordPress loads.
if (!defined('ABSPATH')) {
    $vulnity_firewall_script_filename = filter_input(INPUT_SERVER, 'SCRIPT_FILENAME', FILTER_UNSAFE_RAW);
    if (basename((string) $vulnity_firewall_script_filename) === 'bootstrap.php') {
        exit;
    }

    $vulnity_doc_root_input = filter_input(INPUT_SERVER, 'DOCUMENT_ROOT', FILTER_UNSAFE_RAW);
    $vulnity_doc_root = is_string($vulnity_doc_root_input) ? $vulnity_doc_root_input : '';
    if (function_exists('wp_unslash')) {
        $vulnity_doc_root = wp_unslash($vulnity_doc_root);
    }
    $vulnity_doc_root = preg_replace('/[^A-Za-z0-9:\\/._\\-\\\\]/', '', (string) $vulnity_doc_root);
    $vulnity_doc_root = rtrim($vulnity_doc_root, '/\\');
    if ($vulnity_doc_root !== '') {
        $vulnity_doc_root .= '/';
        if (file_exists($vulnity_doc_root . 'wp-load.php')) {
            define('ABSPATH', $vulnity_doc_root);
        } elseif (file_exists($vulnity_doc_root . 'wp/wp-load.php')) {
            define('ABSPATH', $vulnity_doc_root . 'wp/');
        }
    }

    if (!defined('ABSPATH') && is_string($vulnity_firewall_script_filename) && $vulnity_firewall_script_filename !== '') {
        $vulnity_script_dir = dirname($vulnity_firewall_script_filename);
        for ($vulnity_i = 0; $vulnity_i < 6; $vulnity_i++) {
            if (file_exists($vulnity_script_dir . '/wp-load.php')) {
                define('ABSPATH', rtrim($vulnity_script_dir, '/\\') . '/');
                break;
            }
            $vulnity_parent = dirname($vulnity_script_dir);
            if ($vulnity_parent === $vulnity_script_dir) {
                break;
            }
            $vulnity_script_dir = $vulnity_parent;
        }
    }

    if (!defined('ABSPATH')) {
        $vulnity_candidate_abspath = dirname(__DIR__, 4);
        $vulnity_candidate_abspath = rtrim((string) $vulnity_candidate_abspath, '/\\') . '/';
        if (is_dir($vulnity_candidate_abspath . 'wp-includes')) {
            define('ABSPATH', $vulnity_candidate_abspath);
        }
    }
}

if (!defined('ABSPATH')) {
    // Fail-safe: do nothing if WordPress context cannot be resolved.
    exit;
}

if (!function_exists('vulnity_firewall_sanitize_text')) {
    function vulnity_firewall_sanitize_text($value) {
        if (function_exists('wp_unslash')) {
            $value = wp_unslash($value);
        }

        if (function_exists('sanitize_text_field')) {
            return sanitize_text_field($value);
        }

        $value = (string) $value;

        return preg_replace('/[^A-Za-z0-9:\.\-_, ]/', '', $value);
    }
}

if (!function_exists('vulnity_firewall_trailingslashit')) {
    function vulnity_firewall_trailingslashit($path) {
        return rtrim((string) $path, "/\\") . '/';
    }
}

if (!function_exists('vulnity_firewall_normalize_ip')) {
    function vulnity_firewall_normalize_ip($value) {
        if (is_array($value)) {
            $value = reset($value);
        }

        if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
            return '';
        }

        $value = trim((string) $value);
        if ($value === '') {
            return '';
        }

        $value = trim($value, "\"' \t\n\r\0\x0B");

        if (strlen($value) > 2 && $value[0] === '[' && substr($value, -1) === ']') {
            $value = substr($value, 1, -1);
        }

        if (filter_var($value, FILTER_VALIDATE_IP)) {
            return $value;
        }

        if (strpos($value, ':') !== false && substr_count($value, ':') === 1) {
            $parts = explode(':', $value, 2);
            if (isset($parts[0]) && filter_var($parts[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
                return $parts[0];
            }
        }

        return '';
    }
}

if (!function_exists('vulnity_firewall_ip_matches_cidr')) {
    function vulnity_firewall_ip_matches_cidr($ip, $entry) {
        if (!is_string($ip) || !is_string($entry) || strpos($entry, '/') === false) {
            return false;
        }

        list($subnet, $prefix_length) = array_pad(explode('/', trim($entry), 2), 2, '');
        $subnet = vulnity_firewall_normalize_ip($subnet);

        if ($subnet === '' || $prefix_length === '' || !is_numeric($prefix_length)) {
            return false;
        }

        $ip_bin = @inet_pton($ip);
        $subnet_bin = @inet_pton($subnet);

        if ($ip_bin === false || $subnet_bin === false || strlen($ip_bin) !== strlen($subnet_bin)) {
            return false;
        }

        $prefix_length = (int) $prefix_length;
        $max_bits = strlen($ip_bin) * 8;

        if ($prefix_length < 0 || $prefix_length > $max_bits) {
            return false;
        }

        $full_bytes = intdiv($prefix_length, 8);
        $remaining_bits = $prefix_length % 8;

        if ($full_bytes > 0 && substr($ip_bin, 0, $full_bytes) !== substr($subnet_bin, 0, $full_bytes)) {
            return false;
        }

        if ($remaining_bits === 0) {
            return true;
        }

        $mask = (0xFF << (8 - $remaining_bits)) & 0xFF;

        return (ord($ip_bin[$full_bytes]) & $mask) === (ord($subnet_bin[$full_bytes]) & $mask);
    }
}

if (!function_exists('vulnity_firewall_ip_matches_proxy_entries')) {
    function vulnity_firewall_ip_matches_proxy_entries($ip, $entries) {
        if (!is_string($ip) || $ip === '' || !is_array($entries)) {
            return false;
        }

        foreach ($entries as $entry) {
            if (!is_string($entry)) {
                continue;
            }

            $entry = trim($entry);
            if ($entry === '') {
                continue;
            }

            $normalized_entry = vulnity_firewall_normalize_ip($entry);
            if ($normalized_entry !== '' && $normalized_entry === $ip) {
                return true;
            }

            if (strpos($entry, '/') !== false && vulnity_firewall_ip_matches_cidr($ip, $entry)) {
                return true;
            }
        }

        return false;
    }
}

if (!function_exists('vulnity_firewall_is_private_or_reserved_ip')) {
    function vulnity_firewall_is_private_or_reserved_ip($ip) {
        $normalized_ip = vulnity_firewall_normalize_ip($ip);

        if ($normalized_ip === '') {
            return false;
        }

        return filter_var(
            $normalized_ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
        ) === false;
    }
}

if (!function_exists('vulnity_firewall_parse_forwarded_list_header')) {
    function vulnity_firewall_parse_forwarded_list_header($header_value) {
        if (!is_string($header_value) || $header_value === '') {
            return array();
        }

        $ips = array();
        $parts = preg_split('/\s*,\s*/', $header_value, -1, PREG_SPLIT_NO_EMPTY);

        foreach ($parts as $part) {
            $ip = vulnity_firewall_normalize_ip($part);
            if ($ip !== '') {
                $ips[] = $ip;
            }
        }

        return $ips;
    }
}

if (!function_exists('vulnity_firewall_parse_forwarded_header_ips')) {
    function vulnity_firewall_parse_forwarded_header_ips($header_value) {
        if (!is_string($header_value) || $header_value === '') {
            return array();
        }

        $ips = array();
        if (preg_match_all('/for=(?:"?\[?)([A-Fa-f0-9:\.]+)(?:\]?)(?::\d+)?/i', $header_value, $matches)) {
            foreach ($matches[1] as $match) {
                $ip = vulnity_firewall_normalize_ip($match);
                if ($ip !== '') {
                    $ips[] = $ip;
                }
            }
        }

        return $ips;
    }
}

if (!function_exists('vulnity_firewall_resolve_forwarded_chain_ip')) {
    function vulnity_firewall_resolve_forwarded_chain_ip($chain, $trusted_proxy_entries, $trust_private_hops = false) {
        if (!is_array($chain) || empty($chain)) {
            return '';
        }

        $normalized_chain = array();
        foreach ($chain as $entry) {
            $ip = vulnity_firewall_normalize_ip($entry);
            if ($ip !== '') {
                $normalized_chain[] = $ip;
            }
        }

        if (empty($normalized_chain)) {
            return '';
        }

        $leftmost_ip = $normalized_chain[0];

        for ($index = count($normalized_chain) - 1; $index >= 0; $index--) {
            $candidate_ip = $normalized_chain[$index];
            $is_trusted_hop = vulnity_firewall_ip_matches_proxy_entries($candidate_ip, $trusted_proxy_entries);

            if (
                !$is_trusted_hop &&
                $trust_private_hops &&
                vulnity_firewall_is_private_or_reserved_ip($candidate_ip)
            ) {
                $is_trusted_hop = true;
            }

            if (!$is_trusted_hop) {
                return $candidate_ip;
            }
        }

        return $leftmost_ip;
    }
}

if (!function_exists('vulnity_firewall_esc_html')) {
    function vulnity_firewall_esc_html($value) {
        if (function_exists('esc_html')) {
            return esc_html($value);
        }

        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
}

if (!function_exists('vulnity_firewall_esc_url')) {
    function vulnity_firewall_esc_url($value) {
        if (function_exists('esc_url')) {
            return esc_url($value);
        }

        $sanitized = filter_var((string) $value, FILTER_SANITIZE_URL);
        return is_string($sanitized) ? $sanitized : '';
    }
}

if (!function_exists('vulnity_firewall_wp_kses')) {
    function vulnity_firewall_wp_kses($value, $allowed = array()) {
        if (function_exists('wp_kses')) {
            return wp_kses($value, $allowed);
        }

        return $value;
    }
}

if (!function_exists('vulnity_firewall_plugins_url')) {
    function vulnity_firewall_plugins_url($relative_path, $file) {
        if (function_exists('plugins_url')) {
            return plugins_url($relative_path, $file);
        }

        $relative_path = ltrim((string) $relative_path, '/\\');
        $file_dir = dirname($file);
        $target = realpath($file_dir . DIRECTORY_SEPARATOR . $relative_path);

        if ($target === false) {
            return '';
        }

        $doc_root_input = filter_input(INPUT_SERVER, 'DOCUMENT_ROOT', FILTER_UNSAFE_RAW);
        $doc_root_raw = is_string($doc_root_input) ? $doc_root_input : '';
        if (function_exists('wp_unslash')) {
            $doc_root_raw = wp_unslash($doc_root_raw);
        }
        $doc_root = rtrim(vulnity_firewall_sanitize_text($doc_root_raw), '/\\');
        if ($doc_root === '') {
            return '';
        }

        $doc_root_norm = str_replace('\\', '/', $doc_root);
        $target_norm = str_replace('\\', '/', $target);

        if (strpos($target_norm, $doc_root_norm) !== 0) {
            return '';
        }

        $path = '/' . ltrim(substr($target_norm, strlen($doc_root_norm)), '/');

        $host_input = filter_input(INPUT_SERVER, 'HTTP_HOST', FILTER_UNSAFE_RAW);
        $host_raw = is_string($host_input) ? $host_input : '';
        if (function_exists('wp_unslash')) {
            $host_raw = wp_unslash($host_raw);
        }
        $host = vulnity_firewall_sanitize_text($host_raw);
        if ($host === '') {
            return $path;
        }

        $https_value = filter_input(INPUT_SERVER, 'HTTPS', FILTER_UNSAFE_RAW);
        $scheme = (!empty($https_value) && $https_value !== 'off') ? 'https' : 'http';
        return $scheme . '://' . $host . $path;
    }
}

if (!function_exists('vulnity_firewall_get_uploads_dir')) {
    function vulnity_firewall_get_uploads_dir() {
        if (function_exists('wp_upload_dir')) {
            $uploads = wp_upload_dir(null, false);
            if (is_array($uploads) && !empty($uploads['basedir'])) {
                return vulnity_firewall_trailingslashit($uploads['basedir']);
            }
        }

        if (defined('WP_CONTENT_DIR')) {
            return vulnity_firewall_trailingslashit(WP_CONTENT_DIR) . 'uploads/';
        }

        $content_dir = dirname(__DIR__, 3);
        return vulnity_firewall_trailingslashit($content_dir) . 'uploads/';
    }
}

if (!function_exists('vulnity_firewall_get_content_dir')) {
    function vulnity_firewall_get_content_dir() {
        if (defined('WP_CONTENT_DIR')) {
            return vulnity_firewall_trailingslashit(WP_CONTENT_DIR);
        }

        $content_dir = dirname(__DIR__, 3);
        return vulnity_firewall_trailingslashit($content_dir);
    }
}

if (!function_exists('vulnity_validate_style_path')) {
    function vulnity_validate_style_path($style_path) {
        $real_path = realpath($style_path);

        if (false === $real_path) {
            return false;
        }

        $plugin_base = defined('VULNITY_PLUGIN_DIR') ? VULNITY_PLUGIN_DIR : vulnity_firewall_trailingslashit(dirname(__DIR__));

        if (strpos($real_path, vulnity_firewall_trailingslashit($plugin_base)) !== 0) {
            return false;
        }

        return $real_path;
    }
}

if (!function_exists('vulnity_firewall_get_style_markup')) {
    function vulnity_firewall_get_style_markup($style_url, $style_path = '') {
        $handle       = 'vulnity-firewall-style';
        $markup       = '';
        $safe_url     = vulnity_firewall_esc_url($style_url);

        if ($safe_url !== '' && function_exists('wp_register_style') && function_exists('wp_enqueue_style')) {
            $version   = defined('VULNITY_VERSION') ? VULNITY_VERSION : null;

            wp_register_style($handle, $safe_url, array(), $version);
            wp_enqueue_style($handle);

            if (function_exists('wp_print_styles')) {
                ob_start();
                wp_print_styles($handle);
                $markup = ob_get_clean();
            }
        }

        if ($markup !== '' || $style_path === '') {
            return $markup;
        }

        $validated_path = vulnity_validate_style_path($style_path);
        if ($validated_path === false || !is_readable($validated_path)) {
            return $markup;
        }

        $css = file_get_contents($validated_path);
        if ($css === false) {
            return $markup;
        }

        $css = trim($css);
        if ($css === '') {
            return $markup;
        }

        return '<style>' . htmlspecialchars($css, ENT_QUOTES, 'UTF-8') . '</style>';
    }
}

if (!function_exists('vulnity_firewall_render_block_page')) {
    function vulnity_firewall_render_block_page() {
        if (defined('VULNITY_FIREWALL_BYPASS')) {
            return;
        }

        if (PHP_SAPI === 'cli') {
            return;
        }

        $content_dir = vulnity_firewall_get_content_dir();

        $candidate_files = array(
            vulnity_firewall_trailingslashit($content_dir) . 'vulnity-data/firewall/blocks.php',
        );

        $data_file = '';

        foreach ($candidate_files as $candidate_file) {
            if (file_exists($candidate_file)) {
                $data_file = $candidate_file;
                break;
            }
        }

        if ($data_file === '') {
            return;
        }

        $firewall_data = @include $data_file; // phpcs:ignore -- Internal plugin data file.

        if (!is_array($firewall_data)) {
            return;
        }

        $remote_addr     = '';
        $remote_addr_raw = filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_FULL_SPECIAL_CHARS );

        if ( null !== $remote_addr_raw ) {
            if ( function_exists( 'wp_unslash' ) ) {
                $remote_addr_raw = wp_unslash( $remote_addr_raw );
            }

            if ( is_array( $remote_addr_raw ) ) {
                $remote_addr_raw = reset( $remote_addr_raw );
            }

            if ( function_exists( 'sanitize_text_field' ) ) {
                $remote_addr = sanitize_text_field( $remote_addr_raw );
            } else {
                $remote_addr = vulnity_firewall_sanitize_text( $remote_addr_raw );
            }
        }

        $client_ip = filter_var( $remote_addr, FILTER_VALIDATE_IP ) ? $remote_addr : 'unknown';

        $trusted_proxies = array();
        if (isset($firewall_data['trusted_proxies']) && is_array($firewall_data['trusted_proxies'])) {
            $trusted_proxies = $firewall_data['trusted_proxies'];
        }

        $remote_is_private_proxy = ($client_ip !== 'unknown') && vulnity_firewall_is_private_or_reserved_ip($client_ip);
        $remote_is_trusted_proxy = $remote_is_private_proxy || vulnity_firewall_ip_matches_proxy_entries($client_ip, $trusted_proxies);

        if ($client_ip !== 'unknown' && $remote_is_trusted_proxy) {
            $chain_headers = array(
                'HTTP_X_FORWARDED_FOR',
                'HTTP_X_FORWARDED',
                'HTTP_X_CLUSTER_CLIENT_IP',
                'HTTP_FORWARDED_FOR',
            );

            foreach ($chain_headers as $header) {
                $header_raw_value = filter_input(INPUT_SERVER, $header, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
                if (null !== $header_raw_value) {
                    if (function_exists('wp_unslash')) {
                        $header_raw_value = wp_unslash($header_raw_value);
                    }

                    if (is_array($header_raw_value)) {
                        $header_raw_value = reset($header_raw_value);
                    }
                }

                $chain = vulnity_firewall_parse_forwarded_list_header(
                    is_string($header_raw_value) ? $header_raw_value : ''
                );
                $resolved_ip = vulnity_firewall_resolve_forwarded_chain_ip(
                    $chain,
                    $trusted_proxies,
                    $remote_is_private_proxy
                );

                if ($resolved_ip !== '') {
                    $client_ip = $resolved_ip;
                    break;
                }
            }
        }

        if ($client_ip !== 'unknown' && $remote_is_trusted_proxy) {
            $forwarded_raw_value = filter_input(INPUT_SERVER, 'HTTP_FORWARDED', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
            if (null !== $forwarded_raw_value) {
                if (function_exists('wp_unslash')) {
                    $forwarded_raw_value = wp_unslash($forwarded_raw_value);
                }

                if (is_array($forwarded_raw_value)) {
                    $forwarded_raw_value = reset($forwarded_raw_value);
                }
            }

            $forwarded_chain = vulnity_firewall_parse_forwarded_header_ips(
                is_string($forwarded_raw_value) ? $forwarded_raw_value : ''
            );
            $resolved_forwarded_ip = vulnity_firewall_resolve_forwarded_chain_ip(
                $forwarded_chain,
                $trusted_proxies,
                $remote_is_private_proxy
            );

            if ($resolved_forwarded_ip !== '') {
                $client_ip = $resolved_forwarded_ip;
            }
        }

        if (
            $client_ip !== 'unknown' &&
            vulnity_firewall_ip_matches_proxy_entries($remote_addr, $trusted_proxies)
        ) {
            foreach (array('HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP') as $header) {
                $header_raw_value = filter_input(INPUT_SERVER, $header, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
                if (null !== $header_raw_value) {
                    if (function_exists('wp_unslash')) {
                        $header_raw_value = wp_unslash($header_raw_value);
                    }

                    if (is_array($header_raw_value)) {
                        $header_raw_value = reset($header_raw_value);
                    }
                }

                $candidate_ip = vulnity_firewall_normalize_ip(
                    is_string($header_raw_value) ? $header_raw_value : ''
                );
                if ($candidate_ip !== '') {
                    $client_ip = $candidate_ip;
                    break;
                }
            }
        }

        if ($client_ip === 'unknown') {
            return;
        }

        $whitelist = isset($firewall_data['whitelist']) && is_array($firewall_data['whitelist']) ? $firewall_data['whitelist'] : array();
        if (in_array($client_ip, $whitelist, true)) {
            return;
        }

        $blocked_ips = isset($firewall_data['blocked_ips']) && is_array($firewall_data['blocked_ips']) ? $firewall_data['blocked_ips'] : array();
        if (!isset($blocked_ips[$client_ip])) {
            return;
        }

        $block = $blocked_ips[$client_ip];
        $expires_at = isset($block['expires_at']) ? (int) $block['expires_at'] : 0;

        if ($expires_at !== 0 && $expires_at <= time()) {
            return;
        }

        $reason = isset($block['reason']) ? $block['reason'] : 'Security block';
        $blocked_at = isset($block['blocked_at']) ? $block['blocked_at'] : '';
        $display_expires = isset($block['display_expires']) ? $block['display_expires'] : null;

        if (!headers_sent()) {
            header('HTTP/1.1 403 Forbidden');
            header('X-Blocked-By: Vulnity Firewall');
            header('Cache-Control: no-cache, no-store, must-revalidate');
            header("Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'");
            header('X-Content-Type-Options: nosniff');
            header('X-Frame-Options: DENY');
        }

        $reason_escaped = vulnity_firewall_esc_html($reason);
        $ip_escaped = vulnity_firewall_esc_html($client_ip);
        $blocked_at_escaped = vulnity_firewall_esc_html($blocked_at);

        $expires_message = '';
        if (!empty($display_expires) || !empty($block['permanent'])) {
            $expires_message = '<div class="d"><div class="l">Status</div><div class="v">Blocked</div></div>';
        }

        $logo_url = vulnity_firewall_plugins_url('../assets/Logo_Vulnity.png', __FILE__);
        $style_url = vulnity_firewall_plugins_url('../assets/block-page.css', __FILE__);
        $style_path = dirname(__DIR__) . '/assets/block-page.css';
        $logo_url_escaped = vulnity_firewall_esc_url($logo_url);
        $style_markup = vulnity_firewall_get_style_markup($style_url, $style_path);
        $expires_message_escaped = vulnity_firewall_wp_kses(
            $expires_message,
            array(
                'div' => array(
                    'class' => true,
                ),
            )
        );

        $reference = substr(md5($client_ip . $blocked_at . $reason), 0, 8);
        $reference_escaped = vulnity_firewall_esc_html($reference);

        $html_template = implode('', array(
            '<html>',
            '<head>',
            '<meta charset="UTF-8">',
            '<meta name="viewport" content="width=device-width,initial-scale=1">',
            '<title>Access Denied</title>',
            '%1$s',
            '</head>',
            '<body>',
            '<div class="c">',
            '    <div class="logo"><img src="%2$s" alt="Vulnity Security Logo"></div>',
            '    <h1>Access Denied</h1>',
            '    <div class="st">Your access has been blocked by Vulnity Security</div>',
            '    <div class="b">',
            '        <div class="d"><div class="l">Your IP Address</div><div class="v">%3$s</div></div>',
            '        <div class="d"><div class="l">Block Reason</div><div class="v">%4$s</div></div>',
            '        <div class="d"><div class="l">Blocked Since</div><div class="v">%5$s</div></div>',
            '        %6$s',
            '    </div>',
            '    <div class="f"><strong>Protected by Vulnity Security</strong><br>Reference: %7$s</div>',
            '</div>',
            '</body>',
            '</html>'
        ));

        $final_output = sprintf(
            $html_template,
            $style_markup,
            $logo_url_escaped,
            $ip_escaped,
            $reason_escaped,
            $blocked_at_escaped,
            $expires_message_escaped,
            $reference_escaped
        );

        $safe_final_output = vulnity_firewall_wp_kses(
            $final_output,
            array(
                'html'   => array(),
                'head'   => array(),
                'meta'   => array(
                    'charset' => true,
                    'name'    => true,
                    'content' => true,
                ),
                'title'  => array(),
                'link'   => array(
                    'rel'  => true,
                    'href' => true,
                ),
                'body'   => array(),
                'div'    => array(
                    'class' => true,
                ),
                'img'    => array(
                    'src' => true,
                    'alt' => true,
                ),
                'h1'     => array(),
                'strong' => array(),
                'br'     => array(),
            )
        );
        echo '<!DOCTYPE html>';
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Sanitized above with vulnity_firewall_wp_kses (wp_kses wrapper with bootstrap-safe fallback).
        echo $safe_final_output;

        exit;
    }
}

vulnity_firewall_render_block_page();
