isEnabled()) { return PdfRenderResult::failure( PdfRenderResult::RENDERER_DISABLED, 'PDF renderer is disabled.', correlationId: $correlationId, ); } return $this->client->healthCheck($correlationId); } public function renderHtml(PdfRenderRequest $request): PdfRenderResult { if (! $this->isEnabled()) { return PdfRenderResult::failure( PdfRenderResult::RENDERER_DISABLED, 'PDF renderer is disabled.', correlationId: $request->correlationId, ); } if (trim($request->html) === '') { return PdfRenderResult::failure( PdfRenderResult::INVALID_REQUEST, 'PDF renderer requires server-generated HTML.', correlationId: $request->correlationId, ); } if (strlen($request->html) > $this->maxHtmlBytes()) { return PdfRenderResult::failure( PdfRenderResult::PAYLOAD_TOO_LARGE, 'PDF HTML payload exceeds the configured size limit.', correlationId: $request->correlationId, ); } foreach ($request->assets as $filename => $contents) { if (! $this->isFlatAssetFilename((string) $filename)) { return PdfRenderResult::failure( PdfRenderResult::INVALID_ASSET, 'PDF assets must use flat relative filenames.', correlationId: $request->correlationId, ); } if (strlen($contents) > $this->maxAssetBytes()) { return PdfRenderResult::failure( PdfRenderResult::PAYLOAD_TOO_LARGE, 'PDF asset payload exceeds the configured size limit.', correlationId: $request->correlationId, ); } } $result = $this->client->render($request); if ($result->successful() && $result->pdfBytes !== null && strlen($result->pdfBytes) > $this->maxOutputBytes()) { return PdfRenderResult::failure( PdfRenderResult::OUTPUT_TOO_LARGE, 'PDF renderer output exceeds the configured size limit.', statusCode: $result->statusCode, correlationId: $result->correlationId, ); } return $result; } private function isEnabled(): bool { return (bool) config('tenantpilot.pdf_renderer.enabled', false) && config('tenantpilot.pdf_renderer.driver', 'gotenberg') === 'gotenberg'; } private function maxHtmlBytes(): int { return max(1, (int) config('tenantpilot.pdf_renderer.max_html_bytes', 1024 * 1024)); } private function maxAssetBytes(): int { return max(1, (int) config('tenantpilot.pdf_renderer.max_asset_bytes', 2 * 1024 * 1024)); } private function maxOutputBytes(): int { return max(1, (int) config('tenantpilot.pdf_renderer.max_output_bytes', 10 * 1024 * 1024)); } private function isFlatAssetFilename(string $filename): bool { $filename = trim($filename); return $filename !== '' && $filename === basename($filename) && ! str_contains($filename, "\0") && $filename !== 'index.html'; } }