Skip to content

Commit d7c079e

Browse files
AngelFQCclaude
andcommitted
Security: reuse PDF::html_to_pdf for the Wiki PDF export
Drop the dedicated static helper and render the wiki page through the existing PDF::html_to_pdf(), whose mPDF instance is already built with SafeMpdfHttpClient::container(). The page CSS is written first as header CSS (html_to_pdf writes the page content in body mode, which ignores inline <style> blocks) and the body is passed as a content item with complete_style=false to keep the course header/footer/watermark out, as the original export did. Dompdf (the second SSRF sink) stays removed. Refs GHSA-x3j9-q879-46vr Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 789b26c commit d7c079e

2 files changed

Lines changed: 31 additions & 51 deletions

File tree

public/main/inc/lib/pdf.lib.php

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -577,44 +577,6 @@ public static function singlePageHtmlToPdfDownload(
577577
exit;
578578
}
579579

580-
/**
581-
* Renders a self-contained HTML document to a downloadable PDF.
582-
*
583-
* Reuses the SSRF-guarded mPDF instance built by the constructor and writes
584-
* the given, already-styled HTML as-is, without format_pdf()'s course
585-
* header/footer/watermark decoration. Intended for callers that build their
586-
* own complete HTML document (e.g. the wiki page export).
587-
*
588-
* @param string $html Complete HTML document to render
589-
* @param string $fileName Output file name (without extension)
590-
* @param string $title Optional PDF metadata title
591-
* @param int $margin Page margin in mm applied to the four sides
592-
*
593-
* @throws MpdfException
594-
*/
595-
public static function htmlToPdfDownload(
596-
string $html,
597-
string $fileName,
598-
string $title = '',
599-
int $margin = 15
600-
): void {
601-
$pdf = new self('A4', 'P', [
602-
'left' => $margin,
603-
'right' => $margin,
604-
'top' => $margin,
605-
'bottom' => $margin,
606-
]);
607-
608-
if ('' !== $title) {
609-
$pdf->pdf->SetTitle($title);
610-
}
611-
612-
@$pdf->pdf->WriteHTML($html);
613-
614-
$pdf->pdf->Output(api_replace_dangerous_char($fileName).'.pdf', Destination::DOWNLOAD);
615-
exit;
616-
}
617-
618580
/**
619581
* Gets the watermark from the platform or a course.
620582
*

public/main/wiki/wiki.inc.php

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5381,28 +5381,46 @@ private function renderPdfFromHtmlDirect(string $title, string $content, string
53815381
$safeTitle = trim($title) !== '' ? $title : 'wiki_page';
53825382
$fileName = preg_replace('/\s+/', '_', (string) api_replace_dangerous_char($safeTitle));
53835383

5384-
// Wrap content (keep structure simple for HTML→PDF engines)
5385-
$html = '<!DOCTYPE html><html lang="'.htmlspecialchars(api_get_language_isocode()).'"><head>'
5386-
.'<meta charset="'.htmlspecialchars(api_get_system_encoding()).'">'
5387-
.'<title>'.htmlspecialchars($safeTitle).'</title>'
5388-
.'<style>'.$css.'</style>'
5389-
.'</head><body>'
5390-
.'<div class="wiki-title"><h1>'.htmlspecialchars($safeTitle).'</h1></div>'
5391-
.'<div class="wiki-content">'.$content.'</div>'
5392-
.'</body></html>';
5384+
$body = '<div class="wiki-title"><h1>'.htmlspecialchars($safeTitle).'</h1></div>'
5385+
.'<div class="wiki-content">'.$content.'</div>';
53935386

5394-
// Render through the shared PDF class, whose mPDF instance routes remote
5395-
// asset fetches through the SSRF-guarded HTTP client. This replaces the
5387+
// Render through the shared PDF class: its mPDF instance routes remote
5388+
// asset fetches through the SSRF-guarded HTTP client, replacing the
53965389
// former direct mPDF/Dompdf instantiation (Dompdf's isRemoteEnabled was
5397-
// the second SSRF sink).
5390+
// the second SSRF sink). complete_style=false keeps the course
5391+
// header/footer/watermark out of the wiki export, as before.
53985392
try {
5399-
PDF::htmlToPdfDownload($html, $fileName, $safeTitle, 12);
5393+
$pdf = new PDF('A4', 'P', [
5394+
'left' => 12,
5395+
'right' => 12,
5396+
'top' => 12,
5397+
'bottom' => 12,
5398+
]);
5399+
// Write the CSS as header CSS: html_to_pdf renders the page content
5400+
// in body mode (HTML_BODY), which ignores inline <style> blocks.
5401+
$pdf->pdf->WriteHTML($css, \Mpdf\HTMLParserMode::HEADER_CSS);
5402+
$pdf->html_to_pdf(
5403+
[['title' => $safeTitle, 'content' => $body]],
5404+
$fileName,
5405+
$courseCode,
5406+
false,
5407+
false,
5408+
false
5409+
);
54005410
} catch (\Throwable $e) {
54015411
// Continue to final fallback
54025412
}
54035413

54045414
// --- Final fallback: deliver HTML as download (not PDF) ---
54055415
$downloadName = $fileName.'.pdf';
5416+
$html = '<!DOCTYPE html><html lang="'.htmlspecialchars(api_get_language_isocode()).'"><head>'
5417+
.'<meta charset="'.htmlspecialchars(api_get_system_encoding()).'">'
5418+
.'<title>'.htmlspecialchars($safeTitle).'</title>'
5419+
.'<style>'.$css.'</style>'
5420+
.'</head><body>'
5421+
.'<div class="wiki-title"><h1>'.htmlspecialchars($safeTitle).'</h1></div>'
5422+
.'<div class="wiki-content">'.$content.'</div>'
5423+
.'</body></html>';
54065424
// Clean buffers to avoid header issues
54075425
if (function_exists('ob_get_level')) {
54085426
while (ob_get_level() > 0) { @ob_end_clean(); }

0 commit comments

Comments
 (0)