PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.4
Yatra – Travel Booking & Tour Operator Software v3.0.4
3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 All 83 releases
yatra / app / Services / PdfService.php

PdfService.php in Yatra – Travel Booking & Tour Operator Software 3.0.4, at app/Services/PdfService.php

189 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Services;
6
7 class PdfService
8 {
9 public function isAvailable(): bool
10 {
11 return class_exists('Dompdf\\Dompdf') || class_exists('Mpdf\\Mpdf');
12 }
13
14 public function renderHtmlToPdf(string $html, array $options = []): string
15 {
16 $paper = (string) ($options['paper'] ?? 'A4');
17 $orientation = (string) ($options['orientation'] ?? 'portrait');
18 $defaultFont = (string) ($options['default_font'] ?? 'DejaVu Sans');
19
20 if (class_exists('Dompdf\\Dompdf')) {
21 $optionsClass = 'Dompdf\\Options';
22 $dompdfClass = 'Dompdf\\Dompdf';
23
24 $dompdfOptions = class_exists($optionsClass) ? new $optionsClass() : null;
25 if ($dompdfOptions) {
26 // Remote loading is required so PDFs can render the site logo / trip images.
27 // Filter exists so site owners can lock it down to the local filesystem if they
28 // never use remote images and want to fully eliminate SSRF risk.
29 $remoteEnabled = (bool) apply_filters('yatra_pdf_remote_enabled', true);
30 $dompdfOptions->set('isRemoteEnabled', $remoteEnabled);
31 $dompdfOptions->set('isHtml5ParserEnabled', true);
32 $dompdfOptions->set('defaultFont', $defaultFont);
33
34 // Restrict file:// reads to within ABSPATH so a crafted template can't read /etc/passwd
35 // or other files outside the WordPress install.
36 if (defined('ABSPATH')) {
37 $dompdfOptions->set('chroot', [ABSPATH]);
38 }
39
40 // Block file:// and php:// protocols from remote requests (only http/https allowed).
41 $dompdfOptions->set('allowedProtocols', [
42 'http://' => ['rules' => []],
43 'https://' => ['rules' => []],
44 ]);
45
46 // SSRF hardening for remote fetches: short timeout + identifying User-Agent so admins
47 // can spot dompdf traffic in logs. Does not stop SSRF on its own — sites that do not
48 // need remote images should disable via the `yatra_pdf_remote_enabled` filter above.
49 if ($remoteEnabled) {
50 $httpContext = stream_context_create([
51 'http' => [
52 'timeout' => 5,
53 'follow_location' => 0,
54 'user_agent' => 'YatraPDF/' . (defined('YATRA_VERSION') ? YATRA_VERSION : '1.0'),
55 ],
56 'ssl' => [
57 'verify_peer' => true,
58 'verify_peer_name' => true,
59 ],
60 ]);
61 $dompdfOptions->setHttpContext($httpContext);
62 }
63 }
64
65 $dompdf = $dompdfOptions ? new $dompdfClass($dompdfOptions) : new $dompdfClass();
66 $dompdf->loadHtml($html, 'UTF-8');
67 $dompdf->setPaper($paper, $orientation);
68 $dompdf->render();
69 return (string) $dompdf->output();
70 }
71
72 if (class_exists('Mpdf\\Mpdf')) {
73 $mpdfClass = 'Mpdf\\Mpdf';
74 $mpdf = new $mpdfClass(['format' => $paper]);
75 $mpdf->WriteHTML($html);
76 return (string) $mpdf->Output('', 'S');
77 }
78
79 throw new \RuntimeException('PDF engine is not available');
80 }
81
82 public function renderHtmlToPdfSafely(string $html, array $options = []): string
83 {
84 $originalErrorReporting = error_reporting();
85 $originalDisplayErrors = ini_get('display_errors');
86 $originalHtmlErrors = ini_get('html_errors');
87 $startObLevel = ob_get_level();
88
89 ini_set('display_errors', '0');
90 ini_set('html_errors', '0');
91 error_reporting($originalErrorReporting & ~E_DEPRECATED & ~E_USER_DEPRECATED);
92 ob_start();
93
94 $previousErrorHandler = set_error_handler(static function (int $errno) {
95 if ($errno === E_DEPRECATED || $errno === E_USER_DEPRECATED) {
96 return true;
97 }
98 return false;
99 });
100
101 try {
102 return $this->renderHtmlToPdf($html, $options);
103 } finally {
104 if ($previousErrorHandler !== null) {
105 restore_error_handler();
106 }
107
108 while (ob_get_level() > $startObLevel) {
109 ob_end_clean();
110 }
111
112 error_reporting($originalErrorReporting);
113 if ($originalDisplayErrors !== false) {
114 ini_set('display_errors', (string) $originalDisplayErrors);
115 }
116 if ($originalHtmlErrors !== false) {
117 ini_set('html_errors', (string) $originalHtmlErrors);
118 }
119 }
120 }
121
122 public function renderTemplate(string $templatePath, array $data = []): string
123 {
124 $templateFile = YATRA_PLUGIN_PATH . 'templates/' . $templatePath;
125
126 if (!file_exists($templateFile)) {
127 throw new \InvalidArgumentException("Template file not found: {$templateFile}");
128 }
129
130 // Defence in depth: only extract string-keyed entries with valid PHP-identifier names,
131 // and skip keys that would clobber locals already in scope (EXTR_SKIP). Stops a malicious
132 // $data array from injecting arbitrary local variables into the template scope.
133 $safeData = [];
134 foreach ($data as $key => $value) {
135 if (is_string($key) && preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $key)) {
136 $safeData[$key] = $value;
137 }
138 }
139 extract($safeData, EXTR_SKIP);
140
141 // Capture output
142 ob_start();
143 try {
144 include $templateFile;
145 return ob_get_clean();
146 } catch (\Throwable $e) {
147 ob_end_clean();
148 throw $e;
149 }
150 }
151
152 public function renderTemplateToPdf(string $templatePath, array $data = [], array $options = []): string
153 {
154 $html = $this->renderTemplate($templatePath, $data);
155 return $this->renderHtmlToPdf($html, $options);
156 }
157
158 public function renderTemplateToPdfSafely(string $templatePath, array $data = [], array $options = []): string
159 {
160 $html = $this->renderTemplate($templatePath, $data);
161 return $this->renderHtmlToPdfSafely($html, $options);
162 }
163
164 public function outputPdfDownload(string $pdfBinary, string $filename, bool $inline = false): void
165 {
166 // Strip CR/LF (header injection) and any quotes/backslashes from the ASCII fallback name.
167 // Keep an RFC 5987 UTF-8 form so non-ASCII filenames still display correctly in modern browsers.
168 $cleanFilename = preg_replace('/[\r\n"\\\\]/', '', $filename) ?? '';
169 if ($cleanFilename === '') {
170 $cleanFilename = 'document.pdf';
171 }
172 $asciiFilename = preg_replace('/[^\x20-\x7E]/', '_', $cleanFilename) ?? 'document.pdf';
173 $encodedFilename = rawurlencode($cleanFilename);
174
175 header('Content-Type: application/pdf');
176 header(
177 'Content-Disposition: ' . ($inline ? 'inline' : 'attachment')
178 . '; filename="' . $asciiFilename . '"'
179 . "; filename*=UTF-8''" . $encodedFilename
180 );
181 header('Cache-Control: no-cache, no-store, must-revalidate');
182 header('Pragma: no-cache');
183 header('Expires: 0');
184
185 echo $pdfBinary;
186 exit;
187 }
188 }
189