From 5c9bb543601c832f0e58969db3063d9bf04659b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=B4me=20Chilliet?= <come.chilliet@nextcloud.com>
Date: Mon, 11 Dec 2023 11:34:12 +0100
Subject: [PATCH] Catch and log error thrown while parsing dates from metadata
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
---
 .../OriginalDateTimeMetadataProvider.php      | 38 +++++++++++++++----
 1 file changed, 31 insertions(+), 7 deletions(-)

diff --git a/lib/Listener/OriginalDateTimeMetadataProvider.php b/lib/Listener/OriginalDateTimeMetadataProvider.php
index 37b38a8e9..fdc8f9485 100644
--- a/lib/Listener/OriginalDateTimeMetadataProvider.php
+++ b/lib/Listener/OriginalDateTimeMetadataProvider.php
@@ -28,12 +28,15 @@
 use OCP\EventDispatcher\IEventListener;
 use OCP\Files\File;
 use OCP\FilesMetadata\Event\MetadataLiveEvent;
+use Psr\Log\LoggerInterface;
 
 /**
  * @template-implements IEventListener<MetadataLiveEvent>
  */
 class OriginalDateTimeMetadataProvider implements IEventListener {
-	public function __construct() {
+	public function __construct(
+		private LoggerInterface $logger,
+	) {
 	}
 
 	public array $regexpToDateFormatMap = [
@@ -43,6 +46,27 @@ public function __construct() {
 		"/^([0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{4})/" => "Y-m-d-G-i-s",
 	];
 
+	private function dateToTimestamp(string $format, string $date, File $node): int|false {
+		try {
+			$dateTime = DateTime::createFromFormat($format, $date);
+			if ($dateTime !== false) {
+				return $dateTime->getTimestamp();
+			}
+			return false;
+		} catch (\Throwable $t) {
+			/* Date comes from user data and may trigger ValueError or DateRangeError */
+			$this->logger->warning(
+				'Failed to parse date {date} for file {path}',
+				[
+					'date' => $date,
+					'path' => $node->getPath(),
+					'exception' => $t,
+				]
+			);
+			return false;
+		}
+	}
+
 	public function handle(Event $event): void {
 		if (!($event instanceof MetadataLiveEvent)) {
 			return;
@@ -63,9 +87,9 @@ public function handle(Event $event): void {
 		// Try to use EXIF data.
 		if ($metadata->hasKey('photos-exif') && array_key_exists('DateTimeOriginal', $metadata->getArray('photos-exif'))) {
 			$rawDateTimeOriginal = $metadata->getArray('photos-exif')['DateTimeOriginal'];
-			$dateTimeOriginal = DateTime::createFromFormat("Y:m:d G:i:s", $rawDateTimeOriginal);
-			if ($dateTimeOriginal !== false) {
-				$metadata->setInt('photos-original_date_time', $dateTimeOriginal->getTimestamp(), true);
+			$timestampOriginal = $this->dateToTimestamp("Y:m:d G:i:s", $rawDateTimeOriginal, $node);
+			if ($timestampOriginal !== false) {
+				$metadata->setInt('photos-original_date_time', $timestampOriginal, true);
 				return;
 			}
 		}
@@ -80,9 +104,9 @@ public function handle(Event $event): void {
 				continue;
 			}
 
-			$dateTimeOriginal = DateTime::createFromFormat($format, $matches[1]);
-			if ($dateTimeOriginal !== false) {
-				$metadata->setInt('photos-original_date_time', $dateTimeOriginal->getTimestamp(), true);
+			$timestampOriginal = $this->dateToTimestamp($format, $matches[1], $node);
+			if ($timestampOriginal !== false) {
+				$metadata->setInt('photos-original_date_time', $timestampOriginal, true);
 				return;
 			}
 		}