From cff4727d4bd9ad81d044b0d26f0194d65223fc40 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Thu, 18 May 2023 13:27:13 -0400 Subject: [PATCH] fix(previews): Don't crash on animated WEBP images Fixes #30029 and #37263 libgd handles animated WEBP images poorly and generates a meaningless error message as a result. We were returning a 500 error for these preview requests (web) and a fatal error at the command-line (occ). Now we bypass libgd if the we detect an animated WEBP image (and simply don't generate the preview). No more 500 error. Should fix occ too. Signed-off-by: Josh Richards --- lib/private/legacy/OC_Image.php | 51 +++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/private/legacy/OC_Image.php b/lib/private/legacy/OC_Image.php index 48f1812038ba0..8853dbdd4dedd 100644 --- a/lib/private/legacy/OC_Image.php +++ b/lib/private/legacy/OC_Image.php @@ -748,9 +748,56 @@ public function loadFromFile($imagePath = false) { if (!$this->checkImageSize($imagePath)) { return false; } - $this->resource = @imagecreatefromwebp($imagePath); + + // Check for animated header before generating preview since libgd does not handle them well + // Adapted from here: https://stackoverflow.com/a/68491679/4085517 (stripped to only to check for animations + added additional error checking) + // Header format details here: https://developers.google.com/speed/webp/docs/riff_container + + // Load up the header data, if any + $fp = fopen($imagePath, 'rb'); + if (!$fp) { + return false; + } + $data = fread($fp, 90); + if (!$data) { + return false; + } + fclose($fp); + unset($fp); + + $headerFormat = 'A4Riff/' . // get n string + 'I1Filesize/' . // get integer (file size but not actual size) + 'A4Webp/' . // get n string + 'A4Vp/' . // get n string + 'A74Chunk'; + + $header = unpack($headerFormat, $data); + unset($data, $headerFormat); + if (!$header) { + return false; + } + + // Check if we're really dealing with a valid WEBP header rather than just one suffixed ".webp" + if (!isset($header['Riff']) || strtoupper($header['Riff']) !== 'RIFF') { + return false; + } + if (!isset($header['Webp']) || strtoupper($header['Webp']) !== 'WEBP') { + return false; + } + if (!isset($header['Vp']) || strpos(strtoupper($header['Vp']), 'VP8') === false) { + return false; + } + + // Check for animation indicators + if (strpos(strtoupper($header['Chunk']), 'ANIM') !== false || strpos(strtoupper($header['Chunk']), 'ANMF') !== false) { + // Animated so don't let it reach libgd + $this->logger->debug('OC_Image->loadFromFile, animated WEBP images not supported: ' . $imagePath, ['app' => 'core']); + } else { + // We're safe so give it to libgd + $this->resource = @imagecreatefromwebp($imagePath); + } } else { - $this->logger->debug('OC_Image->loadFromFile, webp images not supported: ' . $imagePath, ['app' => 'core']); + $this->logger->debug('OC_Image->loadFromFile, WEBP images not supported: ' . $imagePath, ['app' => 'core']); } break; /*