<?php
/**
 * Banned URL Controller
 * 
 * Handles immediate IP blocking for accessing banned/trapped URLs.
 * This is a security trap feature - if someone accesses a URL that doesn't 
 * exist (or shouldn't be accessed), they are immediately blocked.
 * 
 * Common use cases:
 * - Trap URLs for known vulnerability scanners (e.g., /phpmyadmin, /admin, /.env)
 * - Honeypot URLs to catch automated bots
 * - URLs being probed by attackers
 * 
 * @package VMPFence
 * @subpackage Firewall\Controllers
 * @since 2.0.0
 */

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

class VMPFence_Banned_URL_Controller {

    /**
     * Check if current request matches a banned URL pattern
     * 
     * @since 2.0.0
     * @param string $request_uri The REQUEST_URI from $_SERVER
     * @param string $ip The IP address making the request
     * @return bool True if URL is banned and IP should be blocked
     */
    public static function is_banned_url($request_uri, $ip) {
        // Get banned URLs from config
        $banned_urls = VMPFence_Config::get('blocked_urls', '');
        if (empty($banned_urls)) {
            return false;
        }

        // Parse URLs - can be comma-separated or newline-separated
        $urls = self::parse_url_list($banned_urls);
        if (empty($urls)) {
            return false;
        }

        // Check each URL pattern against the request URI
        foreach ($urls as $pattern) {
            if (self::url_matches_pattern($request_uri, $pattern)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Parse URL list from config into array
     * 
     * Supports:
     * - Comma-separated URLs
     * - Newline-separated URLs
     * - Mixed (commas and newlines)
     * 
     * @since 2.0.0
     * @param string $url_list Raw URL list from config
     * @return array Array of URL patterns
     */
    private static function parse_url_list($url_list) {
        // Split by multiple delimiters: newlines, commas, and spaces
        // This handles all cases: newline-separated, comma-separated, or space-separated
        $urls = preg_split('/[\s,]+/', $url_list, -1, PREG_SPLIT_NO_EMPTY);
        
        // Clean up each URL
        $cleaned_urls = array();
        foreach ($urls as $url) {
            $url = trim($url);
            
            // Skip empty entries
            if (empty($url)) {
                continue;
            }
            
            // Validate URL starts with / (must be relative)
            // Exception: Multisite wildcard URLs can start with *
            if (!self::is_valid_url_pattern($url)) {
                continue;
            }
            
            $cleaned_urls[] = $url;
        }
        
        return $cleaned_urls;
    }

    /**
     * Validate URL pattern format
     * 
     * Valid patterns:
     * - /example-url
     * - /path/to/page
     * - /wildcard-*
     * - Asterisk slash multisite-url (for WordPress Multisite)
     * 
     * @since 2.0.0
     * @param string $pattern URL pattern to validate
     * @return bool True if valid pattern
     */
    private static function is_valid_url_pattern($pattern) {
        // Must start with / or */
        if (!preg_match('#^(\*/|/)#', $pattern)) {
            return false;
        }
        
        return true;
    }

    /**
     * Check if request URI matches banned URL pattern
     * 
     * Supports:
     * - Exact match: /admin
     * - Wildcard at end: /admin-asterisk
     * - Wildcard in middle: /path/asterisk/file
     * - Multisite wildcard: asterisk/admin (matches any domain + /admin)
     * 
     * Pattern matching is case-insensitive.
     * 
     * @since 2.0.0
     * @param string $request_uri The REQUEST_URI being checked
     * @param string $pattern The banned URL pattern
     * @return bool True if matches
     */
    private static function url_matches_pattern($request_uri, $pattern) {
        // Convert pattern to regex
        $regex = self::pattern_to_regex($pattern);
        
        // Match against request URI (case-insensitive)
        if (preg_match($regex, $request_uri)) {
            return true;
        }
        
        return false;
    }

    /**
     * Convert URL pattern to regex
     * 
     * Handles:
     * - Asterisk wildcards
     * - Multisite domain wildcards (asterisk slash)
     * - Regex special characters escaping
     * 
     * 
     * @since 2.0.0
     * @param string $pattern URL pattern with wildcards
     * @return string Regex pattern
     */
    private static function pattern_to_regex($pattern) {
        // Handle multisite wildcard at start (*/path matches any domain + path)
        $multisite_wildcard = false;
        if (strpos($pattern, '*/') === 0) {
            $multisite_wildcard = true;
            $pattern = substr($pattern, 2); // Remove */ prefix
        }
        
        // Escape regex special characters (but not asterisks yet)
        $pattern = preg_quote($pattern, '/');
        
        // Convert asterisks to .* (match any characters)
        $pattern = str_replace('\*', '.*', $pattern);
        
        // Convert spaces to \s (whitespace)
        $pattern = str_replace(' ', '\s', $pattern);
        
        // Build final regex
        if ($multisite_wildcard) {
            // For multisite: match anything before the pattern
            // Example: */admin matches example.com/admin, sub.example.com/admin
            $regex = '/.*' . $pattern . '$/i';
        } else {
            // Standard: match from start to end
            $regex = '/^' . $pattern . '$/i';
        }
        
        return $regex;
    }

    /**
     * Block IP that accessed banned URL
     * 
     * Creates a temporary IP block with the configured duration from rate limiting settings.
     * Uses the same blocking mechanism as rate limiting.
     * 
     * @since 2.0.0
     * @param string $ip IP address to block
     * @param string $matched_pattern The URL pattern that was matched
     * @param string $request_uri The actual URI that was accessed
     * @return bool True if block was created successfully
     */
    public static function block_ip_for_banned_url($ip, $matched_pattern, $request_uri) {
        global $wpdb;
        
        if (empty($ip)) {
            return false;
        }
        
        // Get block duration from rate limiting config
        // "When blocked via this option, an IP address will be blocked for the duration 
        // you have specified with the option "How long is an IP address blocked when it 
        // breaks a rule" found under the "Rate Limiting" rules."
        $block_duration_minutes = VMPFence_Config::get('rate_block_duration');
        $block_duration_seconds = $block_duration_minutes * 60;
        
        // Convert IP to binary format (same as rate limiting)
        $ip_binary = inet_pton($ip);
        if ($ip_binary === false) {
            return false;
        }
        
        // Create block reason
        $reason = sprintf(
            'Accessed banned URL: %s (pattern: %s)',
            $request_uri,
            $matched_pattern
        );
        
        $current_time = time();
        $expiration = $current_time + $block_duration_seconds;
        
        // Insert into blocks table (type = 2 for banned URL blocking)
        // Same table structure as rate limiting blocks
        $blocks_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKS);
        
        $result = $wpdb->query(
            $wpdb->prepare(
                "INSERT INTO {$blocks_table} 
                 (type, IP, blockedTime, reason, expiration, lastAttempt, blockedHits) 
                 VALUES (2, %s, %d, %s, %d, %d, 0)
                 ON DUPLICATE KEY UPDATE 
                 blockedTime = %d, reason = %s, expiration = %d, lastAttempt = %d, blockedHits = blockedHits + 1",
                $ip_binary, $current_time, $reason, $expiration, $current_time,
                $current_time, $reason, $expiration, $current_time
            )
        );
        
        // Log to WAF log for historical tracking ONLY on new blocks (INSERT)
        // ON DUPLICATE KEY UPDATE returns: 1 for INSERT, 2 for UPDATE, 0 for no change
        // This prevents duplicate log entries for the same blocking event
        if ($result === 1 && class_exists('VMPFence_WAF_Log')) {
            VMPFence_WAF_Log::log_attack(array(
                'action' => 'block',
                'actionDescription' => 'Banned URL access: ' . $matched_pattern,
                'severity' => 2, // medium severity
                'logMsg' => sprintf('Blocked IP %s for accessing banned URL: %s', $ip, $request_uri),
                'IP' => $ip_binary,
                'actionData' => wp_json_encode(array(
                    'block_type' => 'banned_url',
                    'matched_pattern' => $matched_pattern,
                    'request_uri' => $request_uri,
                    'duration_minutes' => $block_duration_minutes
                ))
            ));
        }
        
        if ($result !== false) {
            
            // TODO: Send alert notification (future enhancement)
            // do_action('vmpfence_banned_url_block', $ip, $matched_pattern, $request_uri);
            
            return true;
        }
        
        return false;
    }

    /**
     * Get statistics about banned URL blocks
     * 
     * @since 2.0.0
     * @return array Statistics data
     */
    public static function get_stats() {
        global $wpdb;
        
        $table = $wpdb->prefix . 'vmpfence_blocked_ips';
        
        // Count blocks due to banned URLs (reason contains "Accessed banned URL")
        $total_blocks = $wpdb->get_var(
            "SELECT COUNT(*) FROM {$table} WHERE reason LIKE '%Accessed banned URL%'"
        );
        
        // Get most recent blocks
        $recent_blocks = $wpdb->get_results(
            "SELECT ip_address, reason, blocked_at 
             FROM {$table} 
             WHERE reason LIKE '%Accessed banned URL%'
             ORDER BY blocked_at DESC 
             LIMIT 10",
            ARRAY_A
        );
        
        return array(
            'total_blocks' => (int) $total_blocks,
            'recent_blocks' => $recent_blocks,
        );
    }

    /**
     * Clear all banned URL blocks
     * 
     * @since 2.0.0
     * @return int Number of blocks cleared
     */
    public static function clear_all_blocks() {
        global $wpdb;
        
        $table = $wpdb->prefix . 'vmpfence_blocked_ips';
        
        $result = $wpdb->query(
            "DELETE FROM {$table} WHERE reason LIKE '%Accessed banned URL%'"
        );
                
        return (int) $result;
    }
}
