Некоторые особенности работы с 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 - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.
Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.