222 lines
7.2 KiB
PHP
222 lines
7.2 KiB
PHP
<?php
|
|
/**
|
|
* Code Renderer Class
|
|
* Safely renders code content with proper escaping and syntax highlighting
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('WPINC')) {
|
|
die('Direct access not permitted.');
|
|
}
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class MCB_Code_Renderer {
|
|
|
|
/**
|
|
* Render code content safely
|
|
*/
|
|
public function render_code($content, $filename) {
|
|
// Validate content before processing
|
|
$validation = $this->validate_content($content);
|
|
if (is_wp_error($validation)) {
|
|
return '<div class="gcv-error">' . esc_html($validation->get_error_message()) . '</div>';
|
|
}
|
|
|
|
// First, ensure the content is treated as plain text
|
|
// Multiple layers of safety to prevent any code execution
|
|
|
|
// 1. Convert to UTF-8 if needed
|
|
if (!mb_check_encoding($content, 'UTF-8')) {
|
|
$content = mb_convert_encoding($content, 'UTF-8', mb_detect_encoding($content));
|
|
}
|
|
|
|
// 2. Remove any null bytes
|
|
$content = str_replace("\0", '', $content);
|
|
|
|
// 3. HTML encode everything - this is crucial for safety
|
|
$safe_content = htmlspecialchars($content, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8', false);
|
|
|
|
// 4. Additional escaping for JavaScript context
|
|
$safe_content = $this->escape_for_javascript($safe_content);
|
|
|
|
// 5. Get language for syntax highlighting
|
|
$language = $this->detect_language($filename);
|
|
|
|
// 6. Prepare the code block with line numbers
|
|
$lines = explode("\n", $safe_content);
|
|
$formatted_code = $this->format_with_line_numbers($lines);
|
|
|
|
// 7. Wrap in proper HTML structure
|
|
$output = '<div class="gcv-code-container" data-language="' . esc_attr($language) . '">';
|
|
$output .= '<div class="gcv-code-header">';
|
|
$output .= '<span class="gcv-filename">' . esc_html(basename($filename)) . '</span>';
|
|
$output .= '<button class="gcv-copy-btn" data-content="' . esc_attr($safe_content) . '">Copy</button>';
|
|
$output .= '</div>';
|
|
$output .= '<div class="gcv-code-wrapper">';
|
|
$output .= '<pre class="line-numbers"><code class="language-' . esc_attr($language) . '">';
|
|
$output .= $formatted_code;
|
|
$output .= '</code></pre>';
|
|
$output .= '</div>';
|
|
$output .= '</div>';
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Format code with line numbers
|
|
*/
|
|
private function format_with_line_numbers($lines) {
|
|
$output = '';
|
|
$line_count = count($lines);
|
|
$digit_count = strlen((string)$line_count);
|
|
|
|
foreach ($lines as $index => $line) {
|
|
$line_num = $index + 1;
|
|
$padded_num = str_pad($line_num, $digit_count, ' ', STR_PAD_LEFT);
|
|
$output .= '<span class="line-number" data-line="' . $line_num . '">' . $padded_num . '</span>';
|
|
$output .= '<span class="line-content">' . $line . '</span>' . "\n";
|
|
}
|
|
|
|
return rtrim($output);
|
|
}
|
|
|
|
/**
|
|
* Additional escaping for JavaScript context
|
|
*/
|
|
private function escape_for_javascript($content) {
|
|
// Escape any remaining potentially dangerous patterns
|
|
$patterns = array(
|
|
'/<script/i' => '<script',
|
|
'/<\/script/i' => '</script',
|
|
'/javascript:/i' => 'javascript:',
|
|
'/on\w+\s*=/i' => 'on_event=',
|
|
'/<iframe/i' => '<iframe',
|
|
'/<object/i' => '<object',
|
|
'/<embed/i' => '<embed',
|
|
'/<applet/i' => '<applet'
|
|
);
|
|
|
|
foreach ($patterns as $pattern => $replacement) {
|
|
$content = preg_replace($pattern, $replacement, $content);
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Detect programming language from filename
|
|
*/
|
|
private function detect_language($filename) {
|
|
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
|
$name_lower = strtolower($filename);
|
|
|
|
// Map extensions to Prism.js language identifiers
|
|
$language_map = array(
|
|
'php' => 'php',
|
|
'js' => 'javascript',
|
|
'jsx' => 'jsx',
|
|
'ts' => 'typescript',
|
|
'tsx' => 'tsx',
|
|
'py' => 'python',
|
|
'rb' => 'ruby',
|
|
'java' => 'java',
|
|
'c' => 'c',
|
|
'cpp' => 'cpp',
|
|
'cc' => 'cpp',
|
|
'cxx' => 'cpp',
|
|
'h' => 'c',
|
|
'hpp' => 'cpp',
|
|
'cs' => 'csharp',
|
|
'swift' => 'swift',
|
|
'kt' => 'kotlin',
|
|
'go' => 'go',
|
|
'rs' => 'rust',
|
|
'scala' => 'scala',
|
|
'r' => 'r',
|
|
'sql' => 'sql',
|
|
'sh' => 'bash',
|
|
'bash' => 'bash',
|
|
'yml' => 'yaml',
|
|
'yaml' => 'yaml',
|
|
'json' => 'json',
|
|
'xml' => 'xml',
|
|
'html' => 'html',
|
|
'htm' => 'html',
|
|
'css' => 'css',
|
|
'scss' => 'scss',
|
|
'sass' => 'sass',
|
|
'less' => 'less',
|
|
'md' => 'markdown',
|
|
'markdown' => 'markdown',
|
|
'txt' => 'plain',
|
|
'ini' => 'ini',
|
|
'conf' => 'ini',
|
|
'cfg' => 'ini'
|
|
);
|
|
|
|
// Special file names
|
|
if ($name_lower === 'dockerfile') {
|
|
return 'docker';
|
|
}
|
|
if ($name_lower === 'makefile' || $name_lower === 'gnumakefile') {
|
|
return 'makefile';
|
|
}
|
|
if ($name_lower === '.gitignore') {
|
|
return 'git';
|
|
}
|
|
if ($name_lower === '.htaccess') {
|
|
return 'apacheconf';
|
|
}
|
|
if ($name_lower === '.env') {
|
|
return 'bash';
|
|
}
|
|
|
|
return isset($language_map[$extension]) ? $language_map[$extension] : 'plain';
|
|
}
|
|
|
|
/**
|
|
* Sanitize and validate file content before rendering
|
|
*/
|
|
public function validate_content($content) {
|
|
// Check for binary content
|
|
if ($this->is_binary($content)) {
|
|
return new WP_Error('binary_file', 'Binary files cannot be displayed');
|
|
}
|
|
|
|
// Check file size (limit to 1MB for performance)
|
|
if (strlen($content) > 1048576) {
|
|
return new WP_Error('file_too_large', 'File is too large to display (max 1MB)');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if content appears to be binary
|
|
*/
|
|
private function is_binary($content) {
|
|
// Check for null bytes or excessive non-printable characters
|
|
$null_count = substr_count($content, "\0");
|
|
if ($null_count > 0) {
|
|
return true;
|
|
}
|
|
|
|
// Sample first 8192 bytes
|
|
$sample = substr($content, 0, 8192);
|
|
$non_printable = 0;
|
|
|
|
for ($i = 0; $i < strlen($sample); $i++) {
|
|
$char = ord($sample[$i]);
|
|
// Allow common whitespace and printable ASCII
|
|
if ($char < 32 && $char !== 9 && $char !== 10 && $char !== 13) {
|
|
$non_printable++;
|
|
}
|
|
}
|
|
|
|
// If more than 30% non-printable, consider it binary
|
|
return ($non_printable / strlen($sample)) > 0.3;
|
|
}
|
|
}
|