diff --git a/app/Services/ExportService.php b/app/Services/ExportService.php index ada2261e4..01e87f167 100644 --- a/app/Services/ExportService.php +++ b/app/Services/ExportService.php @@ -9,14 +9,16 @@ class ExportService { protected $entityRepo; + protected $imageService; /** * ExportService constructor. * @param $entityRepo */ - public function __construct(EntityRepo $entityRepo) + public function __construct(EntityRepo $entityRepo, ImageService $imageService) { $this->entityRepo = $entityRepo; + $this->imageService = $imageService; } /** @@ -24,6 +26,7 @@ class ExportService * Includes required CSS & image content. Images are base64 encoded into the HTML. * @param Page $page * @return mixed|string + * @throws \Throwable */ public function pageToContainedHtml(Page $page) { @@ -38,6 +41,7 @@ class ExportService * Convert a chapter to a self-contained HTML file. * @param Chapter $chapter * @return mixed|string + * @throws \Throwable */ public function chapterToContainedHtml(Chapter $chapter) { @@ -56,6 +60,7 @@ class ExportService * Convert a book to a self-contained HTML file. * @param Book $book * @return mixed|string + * @throws \Throwable */ public function bookToContainedHtml(Book $book) { @@ -71,6 +76,7 @@ class ExportService * Convert a page to a PDF file. * @param Page $page * @return mixed|string + * @throws \Throwable */ public function pageToPdf(Page $page) { @@ -85,6 +91,7 @@ class ExportService * Convert a chapter to a PDF file. * @param Chapter $chapter * @return mixed|string + * @throws \Throwable */ public function chapterToPdf(Chapter $chapter) { @@ -103,6 +110,7 @@ class ExportService * Convert a book to a PDF file * @param Book $book * @return string + * @throws \Throwable */ public function bookToPdf(Book $book) { @@ -118,6 +126,7 @@ class ExportService * Convert normal webpage HTML to a PDF. * @param $html * @return string + * @throws \Exception */ protected function htmlToPdf($html) { @@ -146,45 +155,14 @@ class ExportService // Replace image src with base64 encoded image strings if (isset($imageTagsOutput[0]) && count($imageTagsOutput[0]) > 0) { foreach ($imageTagsOutput[0] as $index => $imgMatch) { - $oldImgString = $imgMatch; + $oldImgTagString = $imgMatch; $srcString = $imageTagsOutput[2][$index]; - $isLocal = strpos(trim($srcString), 'http') !== 0; - if ($isLocal) { - $pathString = public_path(trim($srcString, '/')); - } else { - $pathString = $srcString; + $imageEncoded = $this->imageService->imageUriToBase64($srcString); + if ($imageEncoded === null) { + $imageEncoded = $srcString; } - - // Attempt to find local files even if url not absolute - $base = baseUrl('/'); - if (strpos($srcString, $base) === 0) { - $isLocal = true; - $relString = str_replace($base, '', $srcString); - $pathString = public_path(trim($relString, '/')); - } - - if ($isLocal && !file_exists($pathString)) { - continue; - } - try { - if ($isLocal) { - $imageContent = file_get_contents($pathString); - } else { - $ch = curl_init(); - curl_setopt_array($ch, [CURLOPT_URL => $pathString, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]); - $imageContent = curl_exec($ch); - $err = curl_error($ch); - curl_close($ch); - if ($err) { - throw new \Exception("Image fetch failed, Received error: " . $err); - } - } - $imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent); - $newImageString = str_replace($srcString, $imageEncoded, $oldImgString); - } catch (\ErrorException $e) { - $newImageString = ''; - } - $htmlContent = str_replace($oldImgString, $newImageString, $htmlContent); + $newImgTagString = str_replace($srcString, $imageEncoded, $oldImgTagString); + $htmlContent = str_replace($oldImgTagString, $newImgTagString, $htmlContent); } } diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index c2e915e2d..589bf870a 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -321,6 +321,52 @@ class ImageService extends UploadService return $image; } + /** + * Convert a image URI to a Base64 encoded string. + * Attempts to find locally via set storage method first. + * @param string $uri + * @return null|string + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + public function imageUriToBase64(string $uri) + { + $isLocal = strpos(trim($uri), 'http') !== 0; + + // Attempt to find local files even if url not absolute + $base = baseUrl('/'); + if (!$isLocal && strpos($uri, $base) === 0) { + $isLocal = true; + $uri = str_replace($base, '', $uri); + } + + $imageData = null; + + if ($isLocal) { + $uri = trim($uri, '/'); + $storage = $this->getStorage(); + if ($storage->exists($uri)) { + $imageData = $storage->get($uri); + } + } else { + try { + $ch = curl_init(); + curl_setopt_array($ch, [CURLOPT_URL => $uri, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]); + $imageData = curl_exec($ch); + $err = curl_error($ch); + curl_close($ch); + if ($err) { + throw new \Exception("Image fetch failed, Received error: " . $err); + } + } catch (\Exception $e) {} + } + + if ($imageData === null) { + return null; + } + + return 'data:image/' . pathinfo($uri, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageData); + } + /** * Gets a public facing url for an image by checking relevant environment variables. * @param string $filePath diff --git a/tests/ImageTest.php b/tests/ImageTest.php index 8c96ae925..49912ec4c 100644 --- a/tests/ImageTest.php +++ b/tests/ImageTest.php @@ -106,6 +106,29 @@ class ImageTest extends TestCase } } + public function test_secure_images_included_in_exports() + { + config()->set('filesystems.default', 'local_secure'); + $this->asEditor(); + $galleryFile = $this->getTestImage('my-secure-test-upload'); + $page = Page::first(); + $expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m-M') . '/my-secure-test-upload'); + + $upload = $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []); + $imageUrl = json_decode($upload->getContent(), true)['url']; + $page->html .= ""; + $page->save(); + $upload->assertStatus(200); + + $encodedImageContent = base64_encode(file_get_contents($expectedPath)); + $export = $this->get($page->getUrl('/export/html')); + $this->assertTrue(str_contains($export->getContent(), $encodedImageContent), 'Uploaded image in export content'); + + if (file_exists($expectedPath)) { + unlink($expectedPath); + } + } + public function test_system_images_remain_public() { config()->set('filesystems.default', 'local_secure');