diff --git a/web/apps/photos/src/services/upload/upload-service.ts b/web/apps/photos/src/services/upload/upload-service.ts index 7c478320e4..55838ac1c1 100644 --- a/web/apps/photos/src/services/upload/upload-service.ts +++ b/web/apps/photos/src/services/upload/upload-service.ts @@ -33,7 +33,7 @@ import { import { EncryptedMagicMetadata } from "@/new/photos/types/magicMetadata"; import { detectFileTypeInfoFromChunk } from "@/new/photos/utils/detect-type"; import { readStream } from "@/new/photos/utils/native-stream"; -import { ensure } from "@/utils/ensure"; +import { ensure, ensureInteger, ensureNumber } from "@/utils/ensure"; import { CustomError, handleUploadError } from "@ente/shared/error"; import { addToCollection } from "services/collectionService"; import { @@ -985,24 +985,33 @@ const extractImageOrVideoMetadata = async ( tryParseEpochMicrosecondsFromFileName(fileName) ?? modificationTime; } + // To avoid introducing malformed data into the metadata fields (which the + // other clients might not expect and handle), we have extra "ensure" checks + // here that act as a safety valve if somehow the TypeScript type is lying. + // + // There is no deterministic sample we found that necessitated adding these + // extra checks, but we did get one user with a list in the width field of + // the metadata (it should've been an integer). The most probable theory is + // that somehow it made its way in through malformed Exif. + const metadata: Metadata = { fileType, title: fileName, - creationTime, - modificationTime, + creationTime: ensureInteger(creationTime), + modificationTime: ensureInteger(modificationTime), hash, }; const location = parsedMetadataJSON?.location ?? parsedMetadata?.location; if (location) { - metadata.latitude = location.latitude; - metadata.longitude = location.longitude; + metadata.latitude = ensureNumber(location.latitude); + metadata.longitude = ensureNumber(location.longitude); } if (parsedMetadata) { const { width: w, height: h } = parsedMetadata; - if (w) publicMagicMetadata.w = w; - if (h) publicMagicMetadata.h = h; + if (w) publicMagicMetadata.w = ensureInteger(w); + if (h) publicMagicMetadata.h = ensureInteger(h); } return { metadata, publicMagicMetadata }; diff --git a/web/packages/utils/ensure.ts b/web/packages/utils/ensure.ts index d37ccf359c..a7508eb8ad 100644 --- a/web/packages/utils/ensure.ts +++ b/web/packages/utils/ensure.ts @@ -20,3 +20,23 @@ export const ensureString = (v: unknown): string => { throw new Error(`Expected a string, instead found ${String(v)}`); return v; }; + +/** + * Throw an exception if the given value is not a number. + */ +export const ensureNumber = (v: unknown): number => { + if (typeof v != "number") + throw new Error(`Expected a number, instead found ${String(v)}`); + return v; +}; + +/** + * Throw an exception if the given value is not an integral number. + */ +export const ensureInteger = (v: unknown): number => { + if (typeof v != "number") + throw new Error(`Expected a number, instead found ${String(v)}`); + if (!Number.isInteger(v)) + throw new Error(`Expected an integer, instead found ${v}`); + return v; +};