improve exif parsing and error handling (#1084)

This commit is contained in:
Abhinav Kumar 2023-04-26 12:48:44 +05:30 committed by GitHub
commit 865643aa9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 44 additions and 30 deletions

View file

@ -19,9 +19,7 @@ export const FILE_TYPE_LIB_MISSED_FORMATS: FileTypeInfo[] = [
export const KNOWN_NON_MEDIA_FORMATS = ['xmp', 'html', 'txt']; export const KNOWN_NON_MEDIA_FORMATS = ['xmp', 'html', 'txt'];
export const EXIFLESS_FORMATS = ['image/gif']; export const EXIFLESS_FORMATS = ['gif', 'bmp'];
export const EXIF_LIBRARY_UNSUPPORTED_FORMATS = ['image/webp'];
// this is the chunk size of the un-encrypted file which is read and encrypted before uploading it as a single part. // this is the chunk size of the un-encrypted file which is read and encrypted before uploading it as a single part.
export const MULTIPART_PART_SIZE = 20 * 1024 * 1024; export const MULTIPART_PART_SIZE = 20 * 1024 * 1024;

View file

@ -1,8 +1,4 @@
import { import { EXIFLESS_FORMATS, NULL_LOCATION } from 'constants/upload';
EXIFLESS_FORMATS,
EXIF_LIBRARY_UNSUPPORTED_FORMATS,
NULL_LOCATION,
} from 'constants/upload';
import { Location } from 'types/upload'; import { Location } from 'types/upload';
import exifr from 'exifr'; import exifr from 'exifr';
import piexif from 'piexifjs'; import piexif from 'piexifjs';
@ -11,6 +7,8 @@ import { logError } from 'utils/sentry';
import { getUnixTimeInMicroSeconds } from 'utils/time'; import { getUnixTimeInMicroSeconds } from 'utils/time';
import { CustomError } from 'utils/error'; import { CustomError } from 'utils/error';
const EXIFR_UNSUPPORTED_FILE_FORMAT_MESSAGE = 'Unknown file format';
type ParsedEXIFData = Record<string, any> & type ParsedEXIFData = Record<string, any> &
Partial<{ Partial<{
DateTimeOriginal: Date; DateTimeOriginal: Date;
@ -37,6 +35,9 @@ export async function getParsedExifData(
tags?: string[] tags?: string[]
): Promise<ParsedEXIFData> { ): Promise<ParsedEXIFData> {
try { try {
if (EXIFLESS_FORMATS.includes(fileTypeInfo.exactType)) {
return null;
}
const exifData: RawEXIFData = await exifr.parse(receivedFile, { const exifData: RawEXIFData = await exifr.parse(receivedFile, {
reviveValues: false, reviveValues: false,
tiff: true, tiff: true,
@ -56,10 +57,7 @@ export async function getParsedExifData(
: exifData; : exifData;
return parseExifData(filteredExifData); return parseExifData(filteredExifData);
} catch (e) { } catch (e) {
if (!EXIFLESS_FORMATS.includes(fileTypeInfo.mimeType)) { if (e.message === EXIFR_UNSUPPORTED_FILE_FORMAT_MESSAGE) {
if (
EXIF_LIBRARY_UNSUPPORTED_FORMATS.includes(fileTypeInfo.mimeType)
) {
logError(e, 'exif library unsupported format', { logError(e, 'exif library unsupported format', {
fileType: fileTypeInfo.exactType, fileType: fileTypeInfo.exactType,
}); });
@ -67,11 +65,10 @@ export async function getParsedExifData(
logError(e, 'get parsed exif data failed', { logError(e, 'get parsed exif data failed', {
fileType: fileTypeInfo.exactType, fileType: fileTypeInfo.exactType,
}); });
}
}
throw e; throw e;
} }
} }
}
function parseExifData(exifData: RawEXIFData): ParsedEXIFData { function parseExifData(exifData: RawEXIFData): ParsedEXIFData {
if (!exifData) { if (!exifData) {
@ -112,15 +109,15 @@ function parseExifData(exifData: RawEXIFData): ParsedEXIFData {
function parseEXIFDate(dateTimeString: string) { function parseEXIFDate(dateTimeString: string) {
try { try {
if (typeof dateTimeString !== 'string') { if (typeof dateTimeString !== 'string' || dateTimeString === '') {
throw Error(CustomError.NOT_A_DATE); throw Error(CustomError.NOT_A_DATE);
} }
// Check and parse date in the format YYYYMMDD // Check and parse date in the format YYYYMMDD
if (dateTimeString.length === 8) { if (dateTimeString.length === 8) {
const year = parseInt(dateTimeString.slice(0, 4), 10); const year = Number(dateTimeString.slice(0, 4));
const month = parseInt(dateTimeString.slice(4, 6), 10); const month = Number(dateTimeString.slice(4, 6));
const day = parseInt(dateTimeString.slice(6, 8), 10); const day = Number(dateTimeString.slice(6, 8));
if ( if (
!Number.isNaN(year) && !Number.isNaN(year) &&
!Number.isNaN(month) && !Number.isNaN(month) &&
@ -134,15 +131,25 @@ function parseEXIFDate(dateTimeString: string) {
} }
const [year, month, day, hour, minute, second] = dateTimeString const [year, month, day, hour, minute, second] = dateTimeString
.match(/\d+/g) .match(/\d+/g)
.map((component) => parseInt(component, 10)); .map(Number);
if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) { if (
typeof year === 'undefined' ||
Number.isNaN(year) ||
typeof month === 'undefined' ||
Number.isNaN(month) ||
typeof day === 'undefined' ||
Number.isNaN(day)
) {
throw Error(CustomError.NOT_A_DATE); throw Error(CustomError.NOT_A_DATE);
} }
let date: Date; let date: Date;
if ( if (
typeof hour === 'undefined' ||
Number.isNaN(hour) || Number.isNaN(hour) ||
typeof minute === 'undefined' ||
Number.isNaN(minute) || Number.isNaN(minute) ||
typeof second === 'undefined' ||
Number.isNaN(second) Number.isNaN(second)
) { ) {
date = new Date(year, month - 1, day); date = new Date(year, month - 1, day);
@ -168,8 +175,13 @@ export function parseEXIFLocation(
gpsLongitudeRef: string gpsLongitudeRef: string
) { ) {
try { try {
if (!gpsLatitude || !gpsLongitude) { if (
return NULL_LOCATION; !Array.isArray(gpsLatitudeRef) ||
!Array.isArray(gpsLongitudeRef) ||
gpsLatitude.length !== 3 ||
gpsLongitude.length !== 3
) {
throw Error(CustomError.NOT_A_LOCATION);
} }
const latitude = convertDMSToDD( const latitude = convertDMSToDD(
gpsLatitude[0], gpsLatitude[0],
@ -207,13 +219,16 @@ function convertDMSToDD(
} }
export function getEXIFLocation(exifData: ParsedEXIFData): Location { export function getEXIFLocation(exifData: ParsedEXIFData): Location {
if (!exifData.latitude || !exifData.longitude) { if (!exifData || (!exifData.latitude && exifData.latitude !== 0)) {
return NULL_LOCATION; return NULL_LOCATION;
} }
return { latitude: exifData.latitude, longitude: exifData.longitude }; return { latitude: exifData.latitude, longitude: exifData.longitude };
} }
export function getEXIFTime(exifData: ParsedEXIFData): number { export function getEXIFTime(exifData: ParsedEXIFData): number {
if (!exifData) {
return null;
}
const dateTime = const dateTime =
exifData.DateTimeOriginal ?? exifData.DateTimeOriginal ??
exifData.DateCreated ?? exifData.DateCreated ??

View file

@ -40,6 +40,7 @@ export const CustomError = {
NO_METADATA: 'no metadata', NO_METADATA: 'no metadata',
TOO_LARGE_LIVE_PHOTO_ASSETS: 'too large live photo assets', TOO_LARGE_LIVE_PHOTO_ASSETS: 'too large live photo assets',
NOT_A_DATE: 'not a date', NOT_A_DATE: 'not a date',
NOT_A_LOCATION: 'not a location',
FILE_ID_NOT_FOUND: 'file with id not found', FILE_ID_NOT_FOUND: 'file with id not found',
WEAK_DEVICE: 'password decryption failed on the device', WEAK_DEVICE: 'password decryption failed on the device',
INCORRECT_PASSWORD: 'incorrect password', INCORRECT_PASSWORD: 'incorrect password',