From 004acf0feb6836eee6557e5f67c4b2253a172251 Mon Sep 17 00:00:00 2001 From: Nate Giraudeau <50968964+nategiraudeau@users.noreply.github.com> Date: Thu, 14 Jan 2021 08:29:10 -0800 Subject: [PATCH] fix(sharp) wrap sharp calls in try/catch to avoid crashing on bad images (#28645) * added try/catch blocks around image processing * fixed return on getImageMetadata --- packages/gatsby-plugin-sharp/src/duotone.js | 106 ++++++++++-------- .../gatsby-plugin-sharp/src/image-data.ts | 31 +++-- 2 files changed, 77 insertions(+), 60 deletions(-) diff --git a/packages/gatsby-plugin-sharp/src/duotone.js b/packages/gatsby-plugin-sharp/src/duotone.js index 977d8e3b35ac6..7e6ed6bcdbfd9 100644 --- a/packages/gatsby-plugin-sharp/src/duotone.js +++ b/packages/gatsby-plugin-sharp/src/duotone.js @@ -1,4 +1,5 @@ const sharp = require(`./safe-sharp`) +const { reportError } = require(`./report-error`) module.exports = async function duotone(duotone, format, pipeline) { const duotoneGradient = createDuotoneGradient( @@ -13,38 +14,42 @@ module.exports = async function duotone(duotone, format, pipeline) { quality: pipeline.options.jpegQuality, } - const duotoneImage = await pipeline - .raw() - .toBuffer({ resolveWithObject: true }) - .then(({ data, info }) => { - for (let i = 0; i < data.length; i = i + info.channels) { - const r = data[i + 0] - const g = data[i + 1] - const b = data[i + 2] + try { + const duotoneImage = await pipeline + .raw() + .toBuffer({ resolveWithObject: true }) + .then(({ data, info }) => { + for (let i = 0; i < data.length; i = i + info.channels) { + const r = data[i + 0] + const g = data[i + 1] + const b = data[i + 2] - // @see https://en.wikipedia.org/wiki/Relative_luminance - const avg = Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b) + // @see https://en.wikipedia.org/wiki/Relative_luminance + const avg = Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b) - data[i + 0] = duotoneGradient[avg][0] - data[i + 1] = duotoneGradient[avg][1] - data[i + 2] = duotoneGradient[avg][2] - } + data[i + 0] = duotoneGradient[avg][0] + data[i + 1] = duotoneGradient[avg][1] + data[i + 2] = duotoneGradient[avg][2] + } - return sharp(data, { - raw: info, - }).toFormat(format, { ...options }) - }) + return sharp(data, { + raw: info, + }).toFormat(format, { ...options }) + }) - if (duotone.opacity) { - return overlayDuotone( - duotoneImage, - pipeline, - duotone.opacity, - format, - options - ) - } else { - return duotoneImage + if (duotone.opacity) { + return overlayDuotone( + duotoneImage, + pipeline, + duotone.opacity, + format, + options + ) + } else { + return duotoneImage + } + } catch (err) { + return null } } @@ -100,25 +105,30 @@ async function overlayDuotone( percentGrey ) - const duotoneWithTransparency = await duotoneImage - .joinChannel(percentTransparency, { - raw: { width: info.width, height: info.height, channels: 1 }, - }) - .raw() - .toBuffer() + try { + const duotoneWithTransparency = await duotoneImage + .joinChannel(percentTransparency, { + raw: { width: info.width, height: info.height, channels: 1 }, + }) + .raw() + .toBuffer() - return await originalImage - .composite([ - { - input: duotoneWithTransparency, - blend: `over`, - raw: { width: info.width, height: info.height, channels: 4 }, - }, - ]) - .toBuffer({ resolveWithObject: true }) - .then(({ data, info }) => - sharp(data, { - raw: info, - }).toFormat(format, { ...options }) - ) + return await originalImage + .composite([ + { + input: duotoneWithTransparency, + blend: `over`, + raw: { width: info.width, height: info.height, channels: 4 }, + }, + ]) + .toBuffer({ resolveWithObject: true }) + .then(({ data, info }) => + sharp(data, { + raw: info, + }).toFormat(format, { ...options }) + ) + } catch (err) { + reportError(`Failed to process image ${originalImage}`, err) + return originalImage + } } diff --git a/packages/gatsby-plugin-sharp/src/image-data.ts b/packages/gatsby-plugin-sharp/src/image-data.ts index e3f1e8e33ddaf..57cffad36fd3b 100644 --- a/packages/gatsby-plugin-sharp/src/image-data.ts +++ b/packages/gatsby-plugin-sharp/src/image-data.ts @@ -6,6 +6,7 @@ import { rgbToHex, calculateImageSizes, getSrcSet, getSizes } from "./utils" import { traceSVG, getImageSizeAsync, base64, batchQueueImageResizing } from "." import sharp from "./safe-sharp" import { createTransformObject } from "./plugin-options" +import { reportError } from "./report-error" const DEFAULT_BLURRED_IMAGE_WIDTH = 20 @@ -30,7 +31,7 @@ const metadataCache = new Map() export async function getImageMetadata( file: FileNode, getDominantColor?: boolean -): Promise { +): Promise { if (!getDominantColor) { // If we don't need the dominant color we can use the cheaper size function const { width, height, type } = await getImageSizeAsync(file) @@ -40,18 +41,24 @@ export async function getImageMetadata( if (metadata && process.env.NODE_ENV !== `test`) { return metadata } - const pipeline = sharp(file.absolutePath) - const { width, height, density, format } = await pipeline.metadata() + try { + const pipeline = sharp(file.absolutePath) - const { dominant } = await pipeline.stats() - // Fallback in case sharp doesn't support dominant - const dominantColor = dominant - ? rgbToHex(dominant.r, dominant.g, dominant.b) - : `#000000` + const { width, height, density, format } = await pipeline.metadata() + + const { dominant } = await pipeline.stats() + // Fallback in case sharp doesn't support dominant + const dominantColor = dominant + ? rgbToHex(dominant.r, dominant.g, dominant.b) + : `#000000` + + metadata = { width, height, density, format, dominantColor } + metadataCache.set(file.internal.contentDigest, metadata) + } catch (err) { + reportError(`Failed to process image ${file.absolutePath}`, err) + } - metadata = { width, height, density, format, dominantColor } - metadataCache.set(file.internal.contentDigest, metadata) return metadata } @@ -143,7 +150,7 @@ export async function generateImageData({ let primaryFormat: ImageFormat | undefined if (useAuto) { - primaryFormat = normalizeFormat(metadata.format || file.extension) + primaryFormat = normalizeFormat(metadata?.format || file.extension) } else if (formats.has(`png`)) { primaryFormat = `png` } else if (formats.has(`jpg`)) { @@ -334,7 +341,7 @@ export async function generateImageData({ imageProps.placeholder = { fallback, } - } else if (metadata.dominantColor) { + } else if (metadata?.dominantColor) { imageProps.backgroundColor = metadata.dominantColor }