Есть код это проводник по файловой структуре Но этот скрипт показывает только первые папки а подпапки и файлы в них не показывает При открытии по папке открывает еще раз первые папки внутри родительской папки первый 1 файл <? if(empty($_COOKIE['emcid'])){ header("HTTP/1.0 404 Not Found"); exit; } error_reporting(E_STRICT | E_ALL); ini_set("display_errors",-1); ?> <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Проводник файлов и парсер писем</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } #folder-tree { width: 30%; float: left; border: 1px solid #ccc; padding: 10px; } #file-list { width: 65%; float: right; border: 1px solid #ccc; padding: 10px; } #folder-tree { color: #000000; background-color: #ffffff; width: 30%; float: left; border: 1px solid #ccc; padding: 10px; } #file-list { color: #000000; background-color: #ffffff; width: 65%; float: right; border: 1px solid #ccc; padding: 10px; } .folder, .file { cursor: pointer; padding: 5px; } .folder:hover, .file:hover { background-color: #f0f0f0; } .email-content { margin-top: 20px; padding: 10px; border: 1px solid #ccc; } .header { margin-bottom: 10px; } .header span { font-weight: bold; } .upload-section { margin-bottom: 20px; } .folder-toggle::before { content: '▶'; margin-right: 5px; } .folder-toggle.open::before { content: '▼'; } ul { list-style: none; padding-left: 20px; } </style> </head> <body> <h1>Проводник файлов и парсер писем</h1> <!-- Секция загрузки файлов --> <div class="upload-section"> <h3>Загрузить файл .eml или ZIP-архив</h3> <input type="file" id="file-upload" accept=".eml,.zip"> <button onclick="uploadFile()">Загрузить</button> </div> <!-- Проводник папок --> <div id="folder-tree"> <h3>Структура папок</h3> <div id="tree"></div> </div> <!-- Список файлов --> <div id="file-list"> <h3>Содержимое папки</h3> <div id="files"></div> <div id="email-content" class="email-content" style="display: none;"></div> </div> <script> // Загрузка структуры папок // function loadFolderTree() { // fetch('_explorer_int1.php?action=get_folder_tree') // .then(response => response.json()) // .then(data => { // document.getElementById('tree').innerHTML = renderTree(data); // }) // .catch(error => console.error('Ошибка загрузки структуры папок:', error)); // } // Загрузка структуры папок function loadFolderTree(path = '', parentElement = document.getElementById('tree')) { fetch(`_explorer_int1.php?action=get_folder_tree&path=${encodeURIComponent(path)}`) .then(response => response.json()) .then(data => { parentElement.innerHTML = renderTree(data, path, parentElement); }) .catch(error => console.error('Ошибка загрузки структуры папок:', error)); } // Рендеринг дерева папок /*function renderTree(data, path = '') { let html = '<ul>'; for (const item of data) { const fullPath = path ? `${path}/${item.name}` : item.name; if (item.type === 'folder') { html += `<li class="folder" onclick="loadFiles('${fullPath}')">${item.name}`; if (item.children) { html += renderTree(item.children, fullPath); } html += '</li>'; } } html += '</ul>'; return html; }*/ function renderTree(data, path = '', parentElement) { let html = '<ul>'; for (const item of data) { const fullPath = path ? `${path}/${item.name}` : item.name; if (item.type === 'folder') { const hasChildren = item.hasChildren; html += ` <li> <span class="folder${hasChildren ? ' folder-toggle' : ''}" data-path="${fullPath}" onclick="toggleFolder(this, '${fullPath}')"> ${item.name} </span> <div class="subfolder" style="display: none;"></div> </li>`; } } html += '</ul>'; return html; } // Переключение папки (развернуть/свернуть) function toggleFolder(element, path) { const subfolderDiv = element.parentElement.querySelector('.subfolder'); const isOpen = element.classList.contains('open'); if (isOpen) { // Сворачиваем папку subfolderDiv.style.display = 'none'; element.classList.remove('open'); } else { // Разворачиваем папку if (!subfolderDiv.innerHTML) { // Загружаем подпапки, если они еще не загружены loadFolderTree(path, subfolderDiv); } subfolderDiv.style.display = 'block'; element.classList.add('open'); // Загружаем файлы для этой папки loadFiles(path); } } // Загрузка списка файлов в папке function loadFiles(path) { fetch(`_explorer_int1.php?action=get_files&path=${encodeURIComponent(path)}`) .then(response => response.json()) .then(data => { let html = '<ul>'; for (const file of data) { html += `<li class="file" onclick="openFile('${file.path}')">${file.name}</li>`; } html += '</ul>'; document.getElementById('files').innerHTML = html; document.getElementById('email-content').style.display = 'none'; }) .catch(error => console.error('Ошибка загрузки файлов:', error)); } // Открытие файла function openFile(path) { if (path.endsWith('.eml')) { fetch(`_explorer_int1.php?action=parse_eml&path=${encodeURIComponent(path)}`) .then(response => response.json()) .then(data => { const contentDiv = document.getElementById('email-content'); contentDiv.innerHTML = ` <h3>Содержимое письма</h3> <div class="header"><span>От:</span> ${data.headers.From || 'Не указано'}</div> <div class="header"><span>Кому:</span> ${data.headers.To || 'Не указано'}</div> <div class="header"><span>Тема:</span> ${data.headers.Subject || 'Без темы'}</div> <div class="header"><span>Дата:</span> ${data.headers.Date || 'Не указана'}</div> <h4>Служебные заголовки:</h4> <ul> ${Object.entries(data.headers).map(([key, value]) => ` <li><strong>${key}:</strong> ${value} <br> <small>${getHeaderDescription(key)}</small> </li> `).join('')} </ul> <h4>Сообщение:</h4> <div>${data.text_parts.length ? data.text_parts.join('<hr>') : 'Нет текстового содержимого'}</div> ${data.html_parts.length ? `<h4>HTML содержимое:</h4><div>${data.html_parts.join('<hr>')}</div>` : ''} ${data.attachments.length ? ` <h4>Вложения:</h4> <ul> ${data.attachments.map(att => `<li>${att.filename} (${att.size} байт)</li>`).join('')} </ul> ` : ''} `; contentDiv.style.display = 'block'; }) .catch(error => console.error('Ошибка парсинга EML:', error)); } else { window.open(`_explorer_int1.php?action=download&path=${encodeURIComponent(path)}`, '_blank'); } } // Загрузка файла function uploadFile() { const input = document.getElementById('file-upload'); if (!input.files.length) { alert('Выберите файл .eml или .zip'); return; } const formData = new FormData(); formData.append('file', input.files[0]); fetch('_explorer_int1.php?action=upload_file', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { alert('Файл загружен и обработан'); loadFiles(data.path || ''); loadFolderTree(); // Обновляем корневое дерево } else { alert('Ошибка загрузки: ' + data.error); } }) .catch(error => console.error('Ошибка загрузки:', error)); } // Описания заголовков function getHeaderDescription(header) { const descriptions = { 'From': 'Адрес отправителя письма.', 'To': 'Адрес получателя письма.', 'Subject': 'Тема письма.', 'Date': 'Дата и время отправки письма.', 'Message-ID': 'Уникальный идентификатор письма.', 'Content-Type': 'Тип содержимого письма (например, text/plain или multipart/mixed).', 'Content-Transfer-Encoding': 'Кодировка содержимого (например, base64, quoted-printable).', 'MIME-Version': 'Версия MIME, используемая в письме.' }; return descriptions[header] || 'Дополнительный заголовок письма.'; } // Инициализация loadFolderTree(); </script> </body> </html> файл 2 <?php header('Content-Type: application/json'); // Обработка серверных запросов $action = isset($_GET['action']) ? $_GET['action'] : ''; if ($action === 'get_folder_tree') { $base_dir = 'attachments'; // Базовая директория //$path = isset($_GET['path']) ? preg_replace('/\.\.\//', '', $_GET['path']) : ''; // Защита от path traversal //$full_path = $base_dir = $base_dir . ($path ? '/' . $path : ''); //echo json_encode(get_folder_tree($full_path)); header('Content-Type: application/json'); echo json_encode(get_folder_tree($base_dir)); exit; } if ($action === 'get_files') { $path = isset($_GET['path']) ? $_GET['path'] : ''; $full_path = "attachments/$path"; header('Content-Type: application/json'); echo json_encode(get_files($full_path)); exit; } if ($action === 'parse_eml') { $path = isset($_GET['path']) ? $_GET['path'] : ''; $full_path = "attachments/$path"; header('Content-Type: application/json'); echo json_encode(parse_eml_file($full_path)); exit; } if ($action === 'download') { $path = isset($_GET['path']) ? $_GET['path'] : ''; $full_path = "attachments/$path"; if (file_exists($full_path)) { header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($full_path) . '"'); readfile($full_path); } exit; } if ($action === 'upload_file') { header('Content-Type: application/json'); $response = ['success' => false]; if (isset($_FILES['file'])) { $file = $_FILES['file']; $upload_dir = 'attachments/uploads'; if (!file_exists($upload_dir)) { mkdir($upload_dir, 0777, true); } $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); $target_path = "$upload_dir/" . uniqid() . ".$ext"; if (move_uploaded_file($file['tmp_name'], $target_path)) { if ($ext === 'zip') { $zip = new ZipArchive(); if ($zip->open($target_path) === true) { $zip->extractTo($upload_dir); $zip->close(); } } $response = ['success' => true, 'path' => 'uploads']; } else { $response['error'] = 'Ошибка загрузки файла'; } } echo json_encode($response); exit; } // Функция для получения структуры папок function get_folder_tree($dir) { $result = []; if (!is_dir($dir)) return $result; $items = scandir($dir); foreach ($items as $item) { if ($item === '.' || $item === '..') continue; $path = "$dir/$item"; if (is_dir($path)) { $result[] = [ 'name' => $item, 'type' => 'folder', 'hasChildren' => has_subfolders($path) // Признак наличия подпапок 'children' => get_folder_tree($path) ]; } } return $result; } // Проверка наличия подпапок function has_subfolders($dir) { $items = scandir($dir); foreach ($items as $item) { if ($item === '.' || $item === '..') continue; if (is_dir("$dir/$item")) return true; } return false; } // Функция для получения списка файлов в папке function get_files($dir) { $result = []; if (is_dir($dir)) { $items = scandir($dir); foreach ($items as $item) { if ($item === '.' || $item === '..') continue; $path = "$dir/$item"; if (is_file($path)) { $result[] = [ 'name' => $item, 'path' => str_replace('attachments/', '', $path) ]; } } } return $result; } // Функция для парсинга EML файла function parse_eml_file($file_path) { $result = [ 'headers' => [], 'text_parts' => [], 'html_parts' => [], 'attachments' => [] ]; $content = file_get_contents($file_path); if ($content === false) { return $result; } // Разделение на заголовки и тело $parts = explode("\r\n\r\n", $content, 2); if (count($parts) < 2) { $result['headers'] = $content; return $result; } $headers = $parts[0]; $body = $parts[1]; // Парсинг заголовков $header_lines = explode("\r\n", $headers); $current_header = ''; foreach ($header_lines as $line) { if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) { $current_header = trim($matches[1]); $result['headers'][$current_header] = trim($matches[2]); } elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) { $result['headers'][$current_header] .= ' ' . trim($matches[1]); } } // Извлечение кодировки $charset = 'UTF-8'; if (isset($result['headers']['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) { $charset = $matches[1]; } // Декодирование заголовков foreach ($result['headers'] as $key => $value) { $result['headers'][$key] = mb_decode_mimeheader($value); if ($charset !== 'UTF-8') { $result['headers'][$key] = mb_convert_encoding($result['headers'][$key], 'UTF-8', $charset); } } // Парсинг тела письма parse_eml_body($body, $result, $charset); return $result; } // Функция для парсинга тела EML function parse_eml_body($body, &$result, $charset, $boundary = null) { if (!$boundary && isset($result['headers']['Content-Type']) && preg_match('/boundary=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) { $boundary = $matches[1]; } if ($boundary) { $parts = explode("--$boundary", $body); foreach ($parts as $part) { $part = trim($part); if (!$part || $part === '--') continue; // Разделение части на заголовки и содержимое $sub_parts = explode("\r\n\r\n", $part, 2); if (count($sub_parts) < 2) continue; $sub_headers = parse_sub_headers($sub_parts[0]); $sub_body = $sub_parts[1]; // Определение кодировки содержимого $sub_charset = $charset; if (isset($sub_headers['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) { $sub_charset = $matches[1]; } // Декодирование содержимого $encoding = isset($sub_headers['Content-Transfer-Encoding']) ? strtolower($sub_headers['Content-Transfer-Encoding']) : ''; switch ($encoding) { case 'base64': $sub_body = base64_decode($sub_body); break; case 'quoted-printable': $sub_body = quoted_printable_decode($sub_body); break; } // Конверсия кодировки if ($sub_charset !== 'UTF-8') { $sub_body = mb_convert_encoding($sub_body, 'UTF-8', $sub_charset); } // Обработка типа содержимого if (isset($sub_headers['Content-Type'])) { if (stripos($sub_headers['Content-Type'], 'text/plain') !== false) { $result['text_parts'][] = $sub_body; } elseif (stripos($sub_headers['Content-Type'], 'text/html') !== false) { $result['html_parts'][] = $sub_body; } elseif (stripos($sub_headers['Content-Type'], 'multipart') !== false) { $sub_boundary = ''; if (preg_match('/boundary=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) { $sub_boundary = $matches[1]; } parse_eml_body($sub_body, $result, $sub_charset, $sub_boundary); } else { // Обработка вложений $filename = ''; if (isset($sub_headers['Content-Disposition']) && preg_match('/filename=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Disposition'], $matches)) { $filename = $matches[1]; } elseif (isset($sub_headers['Content-Type']) && preg_match('/name=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) { $filename = $matches[1]; } if ($filename) { $result['attachments'][] = [ 'filename' => mb_decode_mimeheader($filename), 'size' => strlen($sub_body) ]; } } } } } else { // Если нет boundary, считаем тело текстом $encoding = isset($result['headers']['Content-Transfer-Encoding']) ? strtolower($result['headers']['Content-Transfer-Encoding']) : ''; switch ($encoding) { case 'base64': $body = base64_decode($body); break; case 'quoted-printable': $body = quoted_printable_decode($body); break; } if ($charset !== 'UTF-8') { $body = mb_convert_encoding($body, 'UTF-8', $charset); } $result['text_parts'][] = $body; } } // Парсинг подзаголовков function parse_sub_headers($header_text) { $headers = []; $header_lines = explode("\r\n", $header_text); $current_header = ''; foreach ($header_lines as $line) { if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) { $current_header = trim($matches[1]); $headers[$current_header] = trim($matches[2]); } elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) { $headers[$current_header] .= ' ' . trim($matches[1]); } } return $headers; }

Проблема в вашем коде связана с тем, что скрипт не корректно отображает вложенные папки и файлы в них из-за ошибок в обработке путей и логики загрузки дерева папок. Давайте разберем проблему и предложим исправления.

Проблемы в коде:

  1. Некорректная обработка путей в get_folder_tree:
    • В файле _explorer_int1.php функция get_folder_tree не учитывает текущий путь ($_GET['path']), из-за чего всегда возвращается структура корневой директории attachments. Это приводит к тому, что при открытии подпапки отображаются только корневые папки.
    • Проверка защиты от path traversal (preg_replace('/\.\.\//', '', $_GET['path'])) закомментирована, что также может вызывать проблемы с безопасностью.
  2. Отсутствие рекурсивной загрузки подпапок:
    • В функции renderTree вы добавляете элементы <div class="subfolder"> для подпапок, но при загрузке подпапок через loadFolderTree не передается корректный путь, из-за чего дерево не строится рекурсивно.
  3. Логика toggleFolder:
    • Функция toggleFolder загружает подпапки, но не обновляет их содержимое должным образом, если они уже были загружены. Это может приводить к дублированию или отсутствию отображения вложенных папок.
  4. Некорректная работа с файлами в подпапках:
    • При клике на папку вызывается loadFiles, но путь к файлам может быть некорректным, так как он не всегда правильно формируется из-за ошибок в обработке путей.

Исправленный код

1. Исправление _explorer_int1.php

Обновим функцию get_folder_tree, чтобы она учитывала переданный путь и корректно возвращала структуру подпапок.

php
<?php
header('Content-Type: application/json');
// Обработка серверных запросов
$action = isset($_GET['action']) ? $_GET['action'] : '';
if ($action === 'get_folder_tree') {
$base_dir = 'attachments'; // Базовая директория
$path = isset($_GET['path']) ? preg_replace('/\.\.\//', '', $_GET['path']) : ''; // Защита от path traversal
$full_path = $base_dir . ($path ? '/' . $path : '');
header('Content-Type: application/json');
echo json_encode(get_folder_tree($full_path));
exit;
}
if ($action === 'get_files') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
header('Content-Type: application/json');
echo json_encode(get_files($full_path));
exit;
}
if ($action === 'parse_eml') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
header('Content-Type: application/json');
echo json_encode(parse_eml_file($full_path));
exit;
}
if ($action === 'download') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
if (file_exists($full_path)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($full_path) . '"');
readfile($full_path);
}
exit;
}
if ($action === 'upload_file') {
header('Content-Type: application/json');
$response = ['success' => false];
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$upload_dir = 'attachments/uploads';
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$target_path = "$upload_dir/" . uniqid() . ".$ext";
if (move_uploaded_file($file['tmp_name'], $target_path)) {
if ($ext === 'zip') {
$zip = new ZipArchive();
if ($zip->open($target_path) === true) {
$zip->extractTo($upload_dir);
$zip->close();
}
}
$response = ['success' => true, 'path' => 'uploads'];
} else {
$response['error'] = 'Ошибка загрузки файла';
}
}
echo json_encode($response);
exit;
}
// Функция для получения структуры папок
function get_folder_tree($dir) {
$result = [];
if (!is_dir($dir)) return $result;
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = "$dir/$item";
if (is_dir($path)) {
$result[] = [
'name' => $item,
'type' => 'folder',
'hasChildren' => has_subfolders($path)
];
}
}
return $result;
}
// Проверка наличия подпапок
function has_subfolders($dir) {
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
if (is_dir("$dir/$item")) return true;
}
return false;
}
// Функция для получения списка файлов в папке
function get_files($dir) {
$result = [];
if (is_dir($dir)) {
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = "$dir/$item";
if (is_file($path)) {
$result[] = [
'name' => $item,
'path' => str_replace('attachments/', '', $path)
];
}
}
}
return $result;
}
// Функция для парсинга EML файла
function parse_eml_file($file_path) {
$result = [
'headers' => [],
'text_parts' => [],
'html_parts' => [],
'attachments' => []
];
$content = file_get_contents($file_path);
if ($content === false) {
return $result;
}
// Разделение на заголовки и тело
$parts = explode("\r\n\r\n", $content, 2);
if (count($parts) < 2) {
$result['headers'] = $content;
return $result;
}
$headers = $parts[0];
$body = $parts[1];
// Парсинг заголовков
$header_lines = explode("\r\n", $headers);
$current_header = '';
foreach ($header_lines as $line) {
if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) {
$current_header = trim($matches[1]);
$result['headers'][$current_header] = trim($matches[2]);
} elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) {
$result['headers'][$current_header] .= ' ' . trim($matches[1]);
}
}
// Извлечение кодировки
$charset = 'UTF-8';
if (isset($result['headers']['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) {
$charset = $matches[1];
}
// Декодирование заголовков
foreach ($result['headers'] as $key => $value) {
$result['headers'][$key] = mb_decode_mimeheader($value);
if ($charset !== 'UTF-8') {
$result['headers'][$key] = mb_convert_encoding($result['headers'][$key], 'UTF-8', $charset);
}
}
// Парсинг тела письма
parse_eml_body($body, $result, $charset);
return $result;
}
// Функция для парсинга тела EML
function parse_eml_body($body, &$result, $charset, $boundary = null) {
if (!$boundary && isset($result['headers']['Content-Type']) && preg_match('/boundary=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) {
$boundary = $matches[1];
}
if ($boundary) {
$parts = explode("--$boundary", $body);
foreach ($parts as $part) {
$part = trim($part);
if (!$part || $part === '--') continue;
// Разделение части на заголовки и содержимое
$sub_parts = explode("\r\n\r\n", $part, 2);
if (count($sub_parts) < 2) continue;
$sub_headers = parse_sub_headers($sub_parts[0]);
$sub_body = $sub_parts[1];
// Определение кодировки содержимого
$sub_charset = $charset;
if (isset($sub_headers['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$sub_charset = $matches[1];
}
// Декодирование содержимого
$encoding = isset($sub_headers['Content-Transfer-Encoding']) ? strtolower($sub_headers['Content-Transfer-Encoding']) : '';
switch ($encoding) {
case 'base64':
$sub_body = base64_decode($sub_body);
break;
case 'quoted-printable':
$sub_body = quoted_printable_decode($sub_body);
break;
}
// Конверсия кодировки
if ($sub_charset !== 'UTF-8') {
$sub_body = mb_convert_encoding($sub_body, 'UTF-8', $sub_charset);
}
// Обработка типа содержимого
if (isset($sub_headers['Content-Type'])) {
if (stripos($sub_headers['Content-Type'], 'text/plain') !== false) {
$result['text_parts'][] = $sub_body;
} elseif (stripos($sub_headers['Content-Type'], 'text/html') !== false) {
$result['html_parts'][] = $sub_body;
} elseif (stripos($sub_headers['Content-Type'], 'multipart') !== false) {
$sub_boundary = '';
if (preg_match('/boundary=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$sub_boundary = $matches[1];
}
parse_eml_body($sub_body, $result, $sub_charset, $sub_boundary);
} else {
// Обработка вложений
$filename = '';
if (isset($sub_headers['Content-Disposition']) && preg_match('/filename=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Disposition'], $matches)) {
$filename = $matches[1];
} elseif (isset($sub_headers['Content-Type']) && preg_match('/name=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$filename = $matches[1];
}
if ($filename) {
$result['attachments'][] = [
'filename' => mb_decode_mimeheader($filename),
'size' => strlen($sub_body)
];
}
}
}
}
} else {
// Если нет boundary, считаем тело текстом
$encoding = isset($result['headers']['Content-Transfer-Encoding']) ? strtolower($result['headers']['Content-Transfer-Encoding']) : '';
switch ($encoding) {
case 'base64':
$body = base64_decode($body);
break;
case 'quoted-printable':
$body = quoted_printable_decode($body);
break;
}
if ($charset !== 'UTF-8') {
$body = mb_convert_encoding($body, 'UTF-8', $charset);
}
$result['text_parts'][] = $body;
}
}
// Парсинг подзаголовков
function parse_sub_headers($header_text) {
$headers = [];
$header_lines = explode("\r\n", $header_text);
$current_header = '';
foreach ($header_lines as $line) {
if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) {
$current_header = trim($matches[1]);
$headers[$current_header] = trim($matches[2]);
} elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) {
$headers[$current_header] .= ' ' . trim($matches[1]);
}
}
return $headers;
}

Основные изменения:

  • Восстановлена обработка параметра path в get_folder_tree с защитой от path traversal.
  • full_path теперь формируется с учетом переданного пути, что позволяет загружать структуру подпапок.

2. Исправление клиентской части (HTML и JavaScript)

Обновим JavaScript в первом файле, чтобы правильно отображать дерево папок и файлы.

html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Проводник файлов и парсер писем</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#folder-tree { width: 30%; float: left; border: 1px solid #ccc; padding: 10px; }
#file-list { width: 65%; float: right; border: 1px solid #ccc; padding: 10px; }
.folder, .file { cursor: pointer; padding: 5px; }
.folder:hover, .file:hover { background-color: #f0f0f0; }
.email-content { margin-top: 20px; padding: 10px; border: 1px solid #ccc; }
.header { margin-bottom: 10px; }
.header span { font-weight: bold; }
.upload-section { margin-bottom: 20px; }
.folder-toggle::before { content: '▶'; margin-right: 5px; }
.folder-toggle.open::before { content: '▼'; }
ul { list-style: none; padding-left: 20px; }
</style>
</head>
<body>
<h1>Проводник файлов и парсер писем</h1>
<!-- Секция загрузки файлов -->
<div class="upload-section">
<h3>Загрузить файл .eml или ZIP-архив</h3>
<input type="file" id="file-upload" accept=".eml,.zip">
<button onclick="uploadFile()">Загрузить</button>
</div>
<!-- Проводник папок -->
<div id="folder-tree">
<h3>Структура папок</h3>
<div id="tree"></div>
</div>
<!-- Список файлов -->
<div id="file-list">
<h3>Содержимое папки</h3>
<div id="files"></div>
<div id="email-content" class="email-content" style="display: none;"></div>
</div>
<script>
// Загрузка структуры папок
function loadFolderTree(path = '', parentElement = document.getElementById('tree')) {
fetch(`_explorer_int1.php?action=get_folder_tree&path=${encodeURIComponent(path)}`)
.then(response => response.json())
.then(data => {
parentElement.innerHTML = renderTree(data, path);
})
.catch(error => console.error('Ошибка загрузки структуры папок:', error));
}
// Рендеринг дерева папок
function renderTree(data, path = '') {
let html = '<ul>';
for (const item of data) {
const fullPath = path ? `${path}/${item.name}` : item.name;
if (item.type === 'folder') {
html += `
<li>
<span class="folder${item.hasChildren ? ' folder-toggle' : ''}"
data-path="${fullPath}"
onclick="toggleFolder(this, '${fullPath}')">
${item.name}
</span>
<div class="subfolder" style="display: none;"></div>
</li>`;
}
}
html += '</ul>';
return html;
}
// Переключение папки (развернуть/свернуть)
function toggleFolder(element, path) {
const subfolderDiv = element.parentElement.querySelector('.subfolder');
const isOpen = element.classList.contains('open');
if (isOpen) {
// Сворачиваем папку
subfolderDiv.style.display = 'none';
element.classList.remove('open');
} else {
// Разворачиваем папку
if (!subfolderDiv.innerHTML) {
// Загружаем подпапки, если они еще не загружены
fetch(`_explorer_int1.php?action=get_folder_tree&path=${encodeURIComponent(path)}`)
.then(response => response.json())
.then(data => {
subfolderDiv.innerHTML = renderTree(data, path);
subfolderDiv.style.display = 'block';
element.classList.add('open');
// Загружаем файлы для этой папки
loadFiles(path);
})
.catch(error => console.error('Ошибка загрузки подпапок:', error));
} else {
subfolderDiv.style.display = 'block';
element.classList.add('open');
loadFiles(path);
}
}
}
// Загрузка списка файлов в папке
function loadFiles(path) {
fetch(`_explorer_int1.php?action=get_files&path=${encodeURIComponent(path)}`)
.then(response => response.json())
.then(data => {
let html = '<ul>';
for (const file of data) {
html += `<li class="file" onclick="openFile('${file.path}')">${file.name}</li>`;
}
html += '</ul>';
document.getElementById('files').innerHTML = html;
document.getElementById('email-content').style.display = 'none';
})
.catch(error => console.error('Ошибка загрузки файлов:', error));
}
// Открытие файла
function openFile(path) {
if (path.endsWith('.eml')) {
fetch(`_explorer_int1.php?action=parse_eml&path=${encodeURIComponent(path)}`)
.then(response => response.json())
.then(data => {
const contentDiv = document.getElementById('email-content');
contentDiv.innerHTML = `
<h3>Содержимое письма</h3>
<div class="header"><span>От:</span> ${data.headers.From || 'Не указано'}</div>
<div class="header"><span>Кому:</span> ${data.headers.To || 'Не указано'}</div>
<div class="header"><span>Тема:</span> ${data.headers.Subject || 'Без темы'}</div>
<div class="header"><span>Дата:</span> ${data.headers.Date || 'Не указана'}</div>
<h4>Служебные заголовки:</h4>
<ul>
${Object.entries(data.headers).map(([key, value]) => `
<li><strong>${key}:</strong> ${value} <br>
<small>${getHeaderDescription(key)}</small>
</li>
`).join('')}
</ul>
<h4>Сообщение:</h4>
<div>${data.text_parts.length ? data.text_parts.join('<hr>') : 'Нет текстового содержимого'}</div>
${data.html_parts.length ? `<h4>HTML содержимое:</h4><div>${data.html_parts.join('<hr>')}</div>` : ''}
${data.attachments.length ? `
<h4>Вложения:</h4>
<ul>
${data.attachments.map(att => `<li>${att.filename} (${att.size} байт)</li>`).join('')}
</ul>
` : ''}
`;
contentDiv.style.display = 'block';
})
.catch(error => console.error('Ошибка парсинга EML:', error));
} else {
window.open(`_explorer_int1.php?action=download&path=${encodeURIComponent(path)}`, '_blank');
}
}
// Загрузка файла
function uploadFile() {
const input = document.getElementById('file-upload');
if (!input.files.length) {
alert('Выберите файл .eml или .zip');
return;
}
const formData = new FormData();
formData.append('file', input.files[0]);
fetch('_explorer_int1.php?action=upload_file', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Файл загружен и обработан');
loadFiles(data.path || '');
loadFolderTree(); // Обновляем корневое дерево
} else {
alert('Ошибка загрузки: ' + data.error);
}
})
.catch(error => console.error('Ошибка загрузки:', error));
}
// Описания заголовков
function getHeaderDescription(header) {
const descriptions = {
'From': 'Адрес отправителя письма.',
'To': 'Адрес получателя письма.',
'Subject': 'Тема письма.',
'Date': 'Дата и время отправки письма.',
'Message-ID': 'Уникальный идентификатор письма.',
'Content-Type': 'Тип содержимого письма (например, text/plain или multipart/mixed).',
'Content-Transfer-Encoding': 'Кодировка содержимого (например, base64, quoted-printable).',
'MIME-Version': 'Версия MIME, используемая в письме.'
};
return descriptions[header] || 'Дополнительный заголовок письма.';
}
// Инициализация
loadFolderTree();
</script>
</body>
</html>

Основные изменения:

  • Упрощена функция loadFolderTree, чтобы она корректно передавала путь в запросе.
  • В функции toggleFolder добавлена проверка, чтобы не загружать содержимое уже открытых папок повторно, но при этом обновлять файлы в текущей папке.
  • Исправлена функция renderTree, чтобы она корректно формировала HTML для вложенных папок.

Объяснение исправлений

  1. Корректная обработка путей:
    • В _explorer_int1.php теперь учитывается параметр path, что позволяет загружать структуру подпапок для конкретного пути.
    • Защита от path traversal (preg_replace('/\.\.\//', '', $_GET['path'])) включена, чтобы предотвратить доступ к файлам вне директории attachments.
  2. Динамическая загрузка подпапок:
    • Функция toggleFolder теперь загружает подпапки только при первом открытии папки, а при повторном открытии просто показывает/скрывает уже загруженное содержимое.
  3. Обновление списка файлов:
    • При каждом открытии папки вызывается loadFiles, чтобы отобразить файлы в текущей папке.
  4. Оптимизация renderTree:
    • Убрана избыточная рекурсия в renderTree, так как подпапки теперь загружаются динамически через toggleFolder.

Дополнительные рекомендации

  1. Безопасность:
    • Убедитесь, что директория attachments защищена от несанкционированного доступа.
    • Проверьте права доступа к файлам и папкам, чтобы избежать уязвимостей.
  2. Обработка ошибок:
    • Добавьте более детализированную обработку ошибок в JavaScript (например, отображение сообщения пользователю, если сервер вернул ошибку).
  3. Производительность:
    • Если структура папок очень большая, подумайте о кэшировании результатов get_folder_tree на стороне сервера, чтобы снизить нагрузку.
  4. UX/UI:
    • Добавьте индикатор загрузки (например, спиннер) при загрузке подпапок или файлов.
    • Рассмотрите возможность отображения иконок для папок и файлов для улучшения визуального восприятия.

Тестирование

  1. Убедитесь, что папка attachments существует и содержит тестовые подпапки и файлы.
  2. Проверьте, что при клике на папку отображаются её подпапки и файлы.
  3. Убедитесь, что файлы .eml корректно парсятся, а другие файлы скачиваются.
  4. Протестируйте загрузку .zip и .eml файлов, чтобы убедиться, что они корректно обрабатываются и отображаются в проводнике.

Если возникнут дополнительные вопросы или потребуется дальнейшая помощь, напишите!

1,8s
Добавить дату к папкам и файлам и отсортировать сначала самые новые

Для добавления даты к папкам и файлам, а также сортировки по дате (сначала самые новые), нужно обновить как серверную часть (_explorer_int1.php), так и клиентскую часть (HTML/JS). Мы будем использовать время последней модификации (mtime) для файлов и папок, чтобы отображать дату и сортировать по ней.

Изменения в коде

1. Обновление _explorer_int1.php

Нужно модифицировать функции get_folder_tree и get_files, чтобы они возвращали дату последней модификации для каждой папки и файла. Также добавим сортировку по дате (от новых к старым).

php
<?php
header('Content-Type: application/json');
// Обработка серверных запросов
$action = isset($_GET['action']) ? $_GET['action'] : '';
if ($action === 'get_folder_tree') {
$base_dir = 'attachments'; // Базовая директория
$path = isset($_GET['path']) ? preg_replace('/\.\.\//', '', $_GET['path']) : ''; // Защита от path traversal
$full_path = $base_dir . ($path ? '/' . $path : '');
header('Content-Type: application/json');
echo json_encode(get_folder_tree($full_path));
exit;
}
if ($action === 'get_files') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
header('Content-Type: application/json');
echo json_encode(get_files($full_path));
exit;
}
if ($action === 'parse_eml') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
header('Content-Type: application/json');
echo json_encode(parse_eml_file($full_path));
exit;
}
if ($action === 'download') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
if (file_exists($full_path)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($full_path) . '"');
readfile($full_path);
}
exit;
}
if ($action === 'upload_file') {
header('Content-Type: application/json');
$response = ['success' => false];
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$upload_dir = 'attachments/uploads';
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$target_path = "$upload_dir/" . uniqid() . ".$ext";
if (move_uploaded_file($file['tmp_name'], $target_path)) {
if ($ext === 'zip') {
$zip = new ZipArchive();
if ($zip->open($target_path) === true) {
$zip->extractTo($upload_dir);
$zip->close();
}
}
$response = ['success' => true, 'path' => 'uploads'];
} else {
$response['error'] = 'Ошибка загрузки файла';
}
}
echo json_encode($response);
exit;
}
// Функция для получения структуры папок
function get_folder_tree($dir) {
$result = [];
if (!is_dir($dir)) return $result;
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = "$dir/$item";
if (is_dir($path)) {
$mtime = filemtime($path);
$result[] = [
'name' => $item,
'type' => 'folder',
'hasChildren' => has_subfolders($path),
'mtime' => $mtime,
'date' => date('Y-m-d H:i:s', $mtime)
];
}
}
// Сортировка по времени модификации (от новых к старым)
usort($result, function($a, $b) {
return $b['mtime'] <=> $a['mtime'];
});
return $result;
}
// Проверка наличия подпапок
function has_subfolders($dir) {
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
if (is_dir("$dir/$item")) return true;
}
return false;
}
// Функция для получения списка файлов в папке
function get_files($dir) {
$result = [];
if (is_dir($dir)) {
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = "$dir/$item";
if (is_file($path)) {
$mtime = filemtime($path);
$result[] = [
'name' => $item,
'path' => str_replace('attachments/', '', $path),
'mtime' => $mtime,
'date' => date('Y-m-d H:i:s', $mtime)
];
}
}
}
// Сортировка по времени модификации (от новых к старым)
usort($result, function($a, $b) {
return $b['mtime'] <=> $a['mtime'];
});
return $result;
}
// Функция для парсинга EML файла
function parse_eml_file($file_path) {
$result = [
'headers' => [],
'text_parts' => [],
'html_parts' => [],
'attachments' => []
];
$content = file_get_contents($file_path);
if ($content === false) {
return $result;
}
// Разделение на заголовки и тело
$parts = explode("\r\n\r\n", $content, 2);
if (count($parts) < 2) {
$result['headers'] = $content;
return $result;
}
$headers = $parts[0];
$body = $parts[1];
// Парсинг заголовков
$header_lines = explode("\r\n", $headers);
$current_header = '';
foreach ($header_lines as $line) {
if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) {
$current_header = trim($matches[1]);
$result['headers'][$current_header] = trim($matches[2]);
} elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) {
$result['headers'][$current_header] .= ' ' . trim($matches[1]);
}
}
// Извлечение кодировки
$charset = 'UTF-8';
if (isset($result['headers']['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) {
$charset = $matches[1];
}
// Декодирование заголовков
foreach ($result['headers'] as $key => $value) {
$result['headers'][$key] = mb_decode_mimeheader($value);
if ($charset !== 'UTF-8') {
$result['headers'][$key] = mb_convert_encoding($result['headers'][$key], 'UTF-8', $charset);
}
}
// Парсинг тела письма
parse_eml_body($body, $result, $charset);
return $result;
}
// Функция для парсинга тела EML
function parse_eml_body($body, &$result, $charset, $boundary = null) {
if (!$boundary && isset($result['headers']['Content-Type']) && preg_match('/boundary=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) {
$boundary = $matches[1];
}
if ($boundary) {
$parts = explode("--$boundary", $body);
foreach ($parts as $part) {
$part = trim($part);
if (!$part || $part === '--') continue;
// Разделение части на заголовки и содержимое
$sub_parts = explode("\r\n\r\n", $part, 2);
if (count($sub_parts) < 2) continue;
$sub_headers = parse_sub_headers($sub_parts[0]);
$sub_body = $sub_parts[1];
// Определение кодировки содержимого
$sub_charset = $charset;
if (isset($sub_headers['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$sub_charset = $matches[1];
}
// Декодирование содержимого
$encoding = isset($sub_headers['Content-Transfer-Encoding']) ? strtolower($sub_headers['Content-Transfer-Encoding']) : '';
switch ($encoding) {
case 'base64':
$sub_body = base64_decode($sub_body);
break;
case 'quoted-printable':
$sub_body = quoted_printable_decode($sub_body);
break;
}
// Конверсия кодировки
if ($sub_charset !== 'UTF-8') {
$sub_body = mb_convert_encoding($sub_body, 'UTF-8', $sub_charset);
}
// Обработка типа содержимого
if (isset($sub_headers['Content-Type'])) {
if (stripos($sub_headers['Content-Type'], 'text/plain') !== false) {
$result['text_parts'][] = $sub_body;
} elseif (stripos($sub_headers['Content-Type'], 'text/html') !== false) {
$result['html_parts'][] = $sub_body;
} elseif (stripos($sub_headers['Content-Type'], 'multipart') !== false) {
$sub_boundary = '';
if (preg_match('/boundary=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$sub_boundary = $matches[1];
}
parse_eml_body($sub_body, $result, $sub_charset, $sub_boundary);
} else {
// Обработка вложений
$filename = '';
if (isset($sub_headers['Content-Disposition']) && preg_match('/filename=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Disposition'], $matches)) {
$filename = $matches[1];
} elseif (isset($sub_headers['Content-Type']) && preg_match('/name=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$filename = $matches[1];
}
if ($filename) {
$result['attachments'][] = [
'filename' => mb_decode_mimeheader($filename),
'size' => strlen($sub_body)
];
}
}
}
}
} else {
// Если нет boundary, считаем тело текстом
$encoding = isset($result['headers']['Content-Transfer-Encoding']) ? strtolower($result['headers']['Content-Transfer-Encoding']) : '';
switch ($encoding) {
case 'base64':
$body = base64_decode($body);
break;
case 'quoted-printable':
$body = quoted_printable_decode($body);
break;
}
if ($charset !== 'UTF-8') {
$body = mb_convert_encoding($body, 'UTF-8', $charset);
}
$result['text_parts'][] = $body;
}
}
// Парсинг подзаголовков
function parse_sub_headers($header_text) {
$headers = [];
$header_lines = explode("\r\n", $header_text);
$current_header = '';
foreach ($header_lines as $line) {
if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) {
$current_header = trim($matches[1]);
$headers[$current_header] = trim($matches[2]);
} elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) {
$headers[$current_header] .= ' ' . trim($matches[1]);
}
}
return $headers;
}

Основные изменения:

  • В функции get_folder_tree добавлены поля mtime (время модификации в формате Unix timestamp) и date (форматированная дата) для каждой папки.
  • В функции get_files добавлены те же поля для файлов.
  • Добавлена сортировка с помощью usort, которая сравнивает mtime в порядке убывания (от новых к старым).

2. Обновление клиентской части (HTML/JS)

Нужно обновить функции renderTree и loadFiles, чтобы отображать дату рядом с папками и файлами. Также добавим немного стилей для улучшения читаемости.

index.html
html
text
**Основные изменения**:
- В CSS добавлен класс `.date` для отображения даты в сером цвете и меньшим шрифтом. Также изменены стили `.folder` и `.file`, чтобы дата отображалась справа от имени.
- В функции `renderTree` добавлено отображение даты для папок (`item.date`).
- В функции `loadFiles` добавлено отображение даты для файлов (`file.date`).
- Сортировка выполняется на сервере, поэтому клиентская часть просто использует полученные данные.
---
### Объяснение изменений
1. **Добавление даты**:
- В `_explorer_int1.php` для каждой папки и файла добавлены поля `mtime` (Unix timestamp) и `date` (форматированная дата `YYYY-MM-DD HH:MM:SS`).
- На клиенте дата отображается рядом с именем папки или файла в формате, возвращаемом сервером.
2. **Сортировка по дате**:
- Сортировка реализована на сервере с помощью функции `usort`, которая сравнивает `mtime` в порядке убывания (`$b['mtime'] <=> $a['mtime']`).
- Это гарантирует, что самые новые папки и файлы будут отображаться первыми.
3. **Улучшение интерфейса**:
- Дата отображается справа от имени в папке или файле, чтобы не загромождать интерфейс.
- Стили `.folder` и `.file` обновлены для использования `display: flex`, чтобы обеспечить аккуратное размещение имени и даты.
---
### Дополнительные рекомендации
1. **Форматирование даты**:
- Текущий формат даты (`Y-m-d H:i:s`) может быть неудобным для пользователей. Можно настроить формат на клиенте с помощью JavaScript (например, использовать библиотеку `moment.js` или `Intl.DateTimeFormat`) для отображения даты в локализованном формате.
- Пример:
```javascript
const formattedDate = new Intl.DateTimeFormat('ru-RU', { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(item.mtime * 1000));
  1. Кэширование: Если структура папок редко меняется, можно кэшировать результаты get_folder_tree на сервере, чтобы уменьшить нагрузку.
  2. UX:
    • Рассмотрите возможность добавления переключателя для сортировки по имени или дате.
    • Добавьте всплывающую подсказку (tooltip) с дополнительной информацией о файле (например, размер файла) при наведении.

Тестирование

  1. Убедитесь, что в папке attachements есть файлы и папки с разными датами модификации.
  2. Проверьте, что папки и файлы отображаются с датами и сортируются по убыванию даты (сначала новые).
  3. Убедитесь, что при загрузке новых файлов они появляются вверху списка.
  4. Проверьте, что функциональность парсинга .eml и скачивания файлов осталась без изменений.

Если нужны дополнительные улучшения или возникнут вопросы, напишите!

4s
Как Grok может помочь?