<?php
/**
 * Security Handler Class
 * Implements OWASP security best practices
 */

// Prevent direct access
if (!defined('WPINC')) {
    die('Direct access not permitted.');
}

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

class MCB_Security {
    
    /**
     * Initialize security features
     */
    public static function init() {
        // Add security headers
        add_action('send_headers', array(__CLASS__, 'add_security_headers'));

        // Add Content Security Policy
        add_action('wp_head', array(__CLASS__, 'add_csp_meta'));

        // Note: Global input sanitization removed - sanitize data at point of use instead
        // This prevents conflicts with other plugins (e.g., WPForms)
    }
    
    /**
     * Add security headers
     */
    public static function add_security_headers() {
        // Only add headers on pages with our plugin
        if (!self::is_plugin_active_on_page()) {
            return;
        }
        
        // X-Content-Type-Options
        header('X-Content-Type-Options: nosniff');
        
        // X-Frame-Options
        header('X-Frame-Options: SAMEORIGIN');
        
        // X-XSS-Protection (legacy but still useful)
        header('X-XSS-Protection: 1; mode=block');
        
        // Referrer Policy
        header('Referrer-Policy: strict-origin-when-cross-origin');
    }
    
    /**
     * Add Content Security Policy meta tag
     */
    public static function add_csp_meta() {
        if (!self::is_plugin_active_on_page()) {
            return;
        }
        
        // Strict CSP for code viewer areas
        $csp = "default-src 'self'; " .
               "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " .  // Needed for Prism.js
               "style-src 'self' 'unsafe-inline'; " .  // Needed for inline styles
               "img-src 'self' data: https:; " .
               "connect-src 'self'; " .
               "font-src 'self' data:; " .
               "object-src 'none'; " .
               "base-uri 'self'; " .
               "form-action 'self'; " .
               "frame-ancestors 'self';";
        
        echo '<meta http-equiv="Content-Security-Policy" content="' . esc_attr($csp) . '">' . "\n";
    }
    
    /**
     * Check if plugin is active on current page
     */
    private static function is_plugin_active_on_page() {
        global $post;
        
        if (!is_a($post, 'WP_Post')) {
            return false;
        }
        
        return has_shortcode($post->post_content, 'maple_code_block');
    }
    
    /**
     * Sanitize global input arrays
     */
    public static function sanitize_global_input() {
        // Sanitize $_GET
        if (!empty($_GET)) {
            $_GET = self::sanitize_array($_GET);
        }
        
        // Sanitize $_POST
        if (!empty($_POST)) {
            $_POST = self::sanitize_array($_POST);
        }
        
        // Sanitize $_REQUEST
        if (!empty($_REQUEST)) {
            $_REQUEST = self::sanitize_array($_REQUEST);
        }
        
        // Sanitize $_COOKIE
        if (!empty($_COOKIE)) {
            $_COOKIE = self::sanitize_array($_COOKIE);
        }
    }
    
    /**
     * Recursively sanitize array
     */
    private static function sanitize_array($array) {
        foreach ($array as $key => $value) {
            if (is_array($value)) {
                $array[$key] = self::sanitize_array($value);
            } else {
                // Remove null bytes
                $value = str_replace(chr(0), '', $value);
                
                // Strip tags and encode special chars
                $array[$key] = htmlspecialchars(strip_tags($value), ENT_QUOTES, 'UTF-8');
            }
        }
        
        return $array;
    }
    
    /**
     * Validate repository format
     * Supports: owner/repo, platform:owner/repo, or full URLs
     */
    public static function validate_repo_format($repo) {
        // Remove any whitespace
        $repo = trim($repo);
        
        // If it's a full URL, validate it
        if (strpos($repo, 'https://') === 0 || strpos($repo, 'http://') === 0) {
            // Check if it's from a supported platform
            $supported_domains = array(
                'github.com',
                'gitlab.com', 
                'bitbucket.org',
                'codeberg.org'
            );
            
            $parsed = parse_url($repo);
            if (!$parsed || !isset($parsed['host'])) {
                return false;
            }
            
            $domain_valid = false;
            foreach ($supported_domains as $domain) {
                if (strpos($parsed['host'], $domain) !== false) {
                    $domain_valid = true;
                    break;
                }
            }
            
            if (!$domain_valid) {
                return false;
            }
            
            // Extract path and validate format
            if (!isset($parsed['path'])) {
                return false;
            }
            
            $path = trim($parsed['path'], '/');
            $parts = explode('/', $path);
            
            // Need at least owner/repo
            if (count($parts) < 2) {
                return false;
            }
            
            // Validate owner and repo names
            $owner = $parts[0];
            $repo_name = $parts[1];
            
            if (!preg_match('/^[a-zA-Z0-9\-_]+$/', $owner) || 
                !preg_match('/^[a-zA-Z0-9\-_\.]+$/', $repo_name)) {
                return false;
            }
            
            return true;
        }
        
        // Check for platform prefix (e.g., gitlab:owner/repo)
        if (strpos($repo, ':') !== false) {
            list($platform, $repo_path) = explode(':', $repo, 2);
            
            // Validate platform
            $valid_platforms = array('github', 'gitlab', 'bitbucket', 'codeberg');
            if (!in_array($platform, $valid_platforms)) {
                return false;
            }
            
            // Validate repo path
            if (!preg_match('/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_\.]+$/', $repo_path)) {
                return false;
            }
            
            return true;
        }
        
        // Standard format: owner/repo
        if (!preg_match('/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_\.]+$/', $repo)) {
            return false;
        }
        
        // Check length limits
        $parts = explode('/', $repo);
        if (count($parts) !== 2) {
            return false;
        }
        
        // Owner: 1-39 characters (GitHub limit)
        if (strlen($parts[0]) < 1 || strlen($parts[0]) > 39) {
            return false;
        }
        
        // Repo name: 1-100 characters (GitHub limit)
        if (strlen($parts[1]) < 1 || strlen($parts[1]) > 100) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Validate file path
     */
    public static function validate_file_path($path) {
        // Remove leading/trailing slashes
        $path = trim($path, '/');
        
        // Check for path traversal attempts
        $dangerous_patterns = array(
            '..',
            '//',
            '\\',
            '.git',
            '.env',
            'wp-config',
            '.htaccess',
            '.htpasswd'
        );
        
        foreach ($dangerous_patterns as $pattern) {
            if (stripos($path, $pattern) !== false) {
                return false;
            }
        }
        
        // Only allow safe characters
        if (!preg_match('/^[a-zA-Z0-9\-_\.\/]+$/', $path)) {
            return false;
        }
        
        // Check path depth (max 10 levels)
        if (substr_count($path, '/') > 10) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Generate secure random token
     */
    public static function generate_token($length = 32) {
        if (function_exists('random_bytes')) {
            return bin2hex(random_bytes($length));
        } elseif (function_exists('openssl_random_pseudo_bytes')) {
            return bin2hex(openssl_random_pseudo_bytes($length));
        } else {
            // Fallback to less secure method
            return wp_generate_password($length * 2, false, false);
        }
    }
    
    /**
     * Log security events
     */
    public static function log_security_event($event_type, $details = array()) {
        $log_entry = array(
            'timestamp' => current_time('mysql'),
            'event_type' => $event_type,
            'ip_address' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '',
            'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
            'details' => $details
        );
        
        // Store in WordPress transient for review (expires in 7 days)
        $logs = get_transient('mcb_security_logs');
        if (!is_array($logs)) {
            $logs = array();
        }
        
        // Keep only last 100 entries
        if (count($logs) >= 100) {
            array_shift($logs);
        }
        
        $logs[] = $log_entry;
        set_transient('mcb_security_logs', $logs, 7 * DAY_IN_SECONDS);
        
        // For critical events, also log to error log
        if (in_array($event_type, array('invalid_nonce', 'rate_limit_exceeded', 'path_traversal_attempt'))) {
            error_log('GCV Security Event: ' . json_encode($log_entry));
        }
    }
}

// Initialize security features
MCB_Security::init();
