Некоторые особенности работы с mhtml на PHP
Делаю новый сервис - сохранение страниц сайтов - и сохраняю эти самые страницы в формате mhtml. За сохранение отвечает расширение, отправляет на сервер -–все в порядке, все нормально. А вот потом, после сохранения, сервис дает возможность либо скачать страницы, либо посмотреть их сразу в браузере, не отходя от кассы. И вот тут есть некоторые нюансы, о которых я и расскажу в заметке ниже.
Сам по себе файл формата mhtml – это грубо говоря обычный текстовый файл, в который засунуты все ресурсы сразу. Не только код страницы, но стили объектов, картинки. Последние версии браузеров «из коробки» умеют рендерить этот файл и отображать его практически 1 в 1 с вариантом на сервере. Ну и я почему-то подумал, что и у меня проблем не возникнет. Однако, они возникли.
Если просто вывести код mhtml на сервере на PHP с помощью команды echo, то мы увидим что-то такое
Здесь я решил не изобретать велосипед, а поискать готовые библиотеки. Для PHP ничего бесплатного и простого не нашел, поэтому решил начать ковырять. Посмотрев исходный код файла mhml, будет видно, то он разделе на блоки, разделяемые
------MultipartBoundary—
Самое первое, что нас интересует – это блок с Content-Type: text/html – очевидно, что это текст страницы. Извлекаем его с помощью своей функции
function extractHtmlFromMhtml($mhtmlContent) {
// Найти границу
$boundary = '------MultipartBoundary--';
if (strpos($mhtmlContent, $boundary) === false) {
$boundary = '------MultipartBoundary--';
}
// Разделить файл на части
$parts = explode($boundary, $mhtmlContent);
// Пройтись по частям и найти HTML часть
foreach ($parts as $part) {
// Пропустить пустые части
if (trim($part) === '') {
continue;
}
// Найти заголовок Content-Type
$contentTypePos = strpos($part, 'Content-Type:');
if ($contentTypePos !== false) {
$contentTypeLine = substr($part, $contentTypePos, strpos($part, "\n", $contentTypePos) - $contentTypePos);
if (strpos($contentTypeLine, 'text/html') !== false) {
// Найдена HTML часть
$htmlStart = strpos($part, '<html');
$htmlEnd = strrpos($part, '</html>') + 7;
return substr($part, $htmlStart, $htmlEnd - $htmlStart);
}
}
}
return null;
}
После выделения нужного куска с помощью этой функции, картина меняется на такую
Дальше в исходнике в блоке с html мы видим вот такую строку
Content-Transfer-Encoding: quoted-printableОчевидно, что это способ кодирования данных. В PHP есть встроенная функция, которая может работать с таким методом. Задействуем её
$mhtml_content = "исходник mhtml"; //извлекаем блок html $mhtml_content_html = extractHtmlFromMhtml($mhtml_content); //перекодируем $mhtml_content_q = quoted_printable_decode($mhtml_content_html); echo $mhtml_content_q;И получаем практически оригинал
Но не совсем. Если посмотреть в исходный код страницы, то увидим, что обращение идет ко внешним файлам – картинок, стилей, скриптов. А это мало того, что небезопасно, так еще и ненадежно – зачем нам хранить у себя копию страницы, если всё равно обращаемся к источнику? А если источник будет недоступен.
Поэтому обрезаем все внешние источники для страницы например так
header("Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:");
Тут мы разрешаем встроенные стили и картинки через data.
И теперь снова страница выглядит совсем не очень хорошо:
Сначала встроим стили. Используем например такую функцию:
function extractStylesFromMhtml($mhtmlContent) {
// Найти границу
$boundary = '------MultipartBoundary--';
// Разделить файл на части
$parts = explode($boundary, $mhtmlContent);
$style = '<style type="text/css">';
// Пройтись по частям и найти CSS части
foreach ($parts as $part) {
// Пропустить пустые части
if (trim($part) === '') {
continue;
}
// Найти заголовок Content-Type
$contentTypePos = strpos($part, 'Content-Type: text/css');
if ($contentTypePos !== false) {
$lines = explode("\n", $part);
$chet = 0;
foreach ($lines as $line){
$chet++;
if ($chet > 4){
$style .= $line."\n";
}
}
}
}
$style .= "</style>";
return $style;
}
Смотрим на результат:
Теперь еще извлечем картинки. Для написания функции извлечения картинок из mhtml стоит учитывать исходный формат, а также убрать все переносы строк.
//html
$mhtml_content_html = extractHtmlFromMhtml($mhtml_content);
$mhtml_content_q = quoted_printable_decode($mhtml_content_html);
//извлекаем картинки
$art = extractImagesFromMhtml($mhtml_content);
foreach ($art as $img => $base){
//echo $img."<br>";
$mhtml_content_q = str_replace($img, $base, $mhtml_content_й);
}
echo $mhtml_content_q;
//стили
$style = extractStylesFromMhtml($mhtml_content);
$style = quoted_printable_decode($style);
echo $style;
И в итоге получаем вот такую страницу у себя:
Как видите, все же не все картинки загрузились. Более того, в некоторых случаях необходимы будут доработки в плане кодировок, но и этот код, что мы уже сделали выведет более-менее корректно наверно процентов 90 сохраненных вами страниц.
Так что не так уж страшен формат mhml. Есть вопросы? Пишите, за небольшую плату с удовольствием проконсультирую.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.
Программы на заказ
Отзывы
Контакты