Skip to content

Commit 004acf0

Browse files
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
1 parent bf6f264 commit 004acf0

File tree

2 files changed

+77
-60
lines changed

2 files changed

+77
-60
lines changed

packages/gatsby-plugin-sharp/src/duotone.js

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const sharp = require(`./safe-sharp`)
2+
const { reportError } = require(`./report-error`)
23

34
module.exports = async function duotone(duotone, format, pipeline) {
45
const duotoneGradient = createDuotoneGradient(
@@ -13,38 +14,42 @@ module.exports = async function duotone(duotone, format, pipeline) {
1314
quality: pipeline.options.jpegQuality,
1415
}
1516

16-
const duotoneImage = await pipeline
17-
.raw()
18-
.toBuffer({ resolveWithObject: true })
19-
.then(({ data, info }) => {
20-
for (let i = 0; i < data.length; i = i + info.channels) {
21-
const r = data[i + 0]
22-
const g = data[i + 1]
23-
const b = data[i + 2]
17+
try {
18+
const duotoneImage = await pipeline
19+
.raw()
20+
.toBuffer({ resolveWithObject: true })
21+
.then(({ data, info }) => {
22+
for (let i = 0; i < data.length; i = i + info.channels) {
23+
const r = data[i + 0]
24+
const g = data[i + 1]
25+
const b = data[i + 2]
2426

25-
// @see https://en.wikipedia.org/wiki/Relative_luminance
26-
const avg = Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b)
27+
// @see https://en.wikipedia.org/wiki/Relative_luminance
28+
const avg = Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b)
2729

28-
data[i + 0] = duotoneGradient[avg][0]
29-
data[i + 1] = duotoneGradient[avg][1]
30-
data[i + 2] = duotoneGradient[avg][2]
31-
}
30+
data[i + 0] = duotoneGradient[avg][0]
31+
data[i + 1] = duotoneGradient[avg][1]
32+
data[i + 2] = duotoneGradient[avg][2]
33+
}
3234

33-
return sharp(data, {
34-
raw: info,
35-
}).toFormat(format, { ...options })
36-
})
35+
return sharp(data, {
36+
raw: info,
37+
}).toFormat(format, { ...options })
38+
})
3739

38-
if (duotone.opacity) {
39-
return overlayDuotone(
40-
duotoneImage,
41-
pipeline,
42-
duotone.opacity,
43-
format,
44-
options
45-
)
46-
} else {
47-
return duotoneImage
40+
if (duotone.opacity) {
41+
return overlayDuotone(
42+
duotoneImage,
43+
pipeline,
44+
duotone.opacity,
45+
format,
46+
options
47+
)
48+
} else {
49+
return duotoneImage
50+
}
51+
} catch (err) {
52+
return null
4853
}
4954
}
5055

@@ -100,25 +105,30 @@ async function overlayDuotone(
100105
percentGrey
101106
)
102107

103-
const duotoneWithTransparency = await duotoneImage
104-
.joinChannel(percentTransparency, {
105-
raw: { width: info.width, height: info.height, channels: 1 },
106-
})
107-
.raw()
108-
.toBuffer()
108+
try {
109+
const duotoneWithTransparency = await duotoneImage
110+
.joinChannel(percentTransparency, {
111+
raw: { width: info.width, height: info.height, channels: 1 },
112+
})
113+
.raw()
114+
.toBuffer()
109115

110-
return await originalImage
111-
.composite([
112-
{
113-
input: duotoneWithTransparency,
114-
blend: `over`,
115-
raw: { width: info.width, height: info.height, channels: 4 },
116-
},
117-
])
118-
.toBuffer({ resolveWithObject: true })
119-
.then(({ data, info }) =>
120-
sharp(data, {
121-
raw: info,
122-
}).toFormat(format, { ...options })
123-
)
116+
return await originalImage
117+
.composite([
118+
{
119+
input: duotoneWithTransparency,
120+
blend: `over`,
121+
raw: { width: info.width, height: info.height, channels: 4 },
122+
},
123+
])
124+
.toBuffer({ resolveWithObject: true })
125+
.then(({ data, info }) =>
126+
sharp(data, {
127+
raw: info,
128+
}).toFormat(format, { ...options })
129+
)
130+
} catch (err) {
131+
reportError(`Failed to process image ${originalImage}`, err)
132+
return originalImage
133+
}
124134
}

packages/gatsby-plugin-sharp/src/image-data.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { rgbToHex, calculateImageSizes, getSrcSet, getSizes } from "./utils"
66
import { traceSVG, getImageSizeAsync, base64, batchQueueImageResizing } from "."
77
import sharp from "./safe-sharp"
88
import { createTransformObject } from "./plugin-options"
9+
import { reportError } from "./report-error"
910

1011
const DEFAULT_BLURRED_IMAGE_WIDTH = 20
1112

@@ -30,7 +31,7 @@ const metadataCache = new Map<string, IImageMetadata>()
3031
export async function getImageMetadata(
3132
file: FileNode,
3233
getDominantColor?: boolean
33-
): Promise<IImageMetadata> {
34+
): Promise<IImageMetadata | undefined> {
3435
if (!getDominantColor) {
3536
// If we don't need the dominant color we can use the cheaper size function
3637
const { width, height, type } = await getImageSizeAsync(file)
@@ -40,18 +41,24 @@ export async function getImageMetadata(
4041
if (metadata && process.env.NODE_ENV !== `test`) {
4142
return metadata
4243
}
43-
const pipeline = sharp(file.absolutePath)
4444

45-
const { width, height, density, format } = await pipeline.metadata()
45+
try {
46+
const pipeline = sharp(file.absolutePath)
4647

47-
const { dominant } = await pipeline.stats()
48-
// Fallback in case sharp doesn't support dominant
49-
const dominantColor = dominant
50-
? rgbToHex(dominant.r, dominant.g, dominant.b)
51-
: `#000000`
48+
const { width, height, density, format } = await pipeline.metadata()
49+
50+
const { dominant } = await pipeline.stats()
51+
// Fallback in case sharp doesn't support dominant
52+
const dominantColor = dominant
53+
? rgbToHex(dominant.r, dominant.g, dominant.b)
54+
: `#000000`
55+
56+
metadata = { width, height, density, format, dominantColor }
57+
metadataCache.set(file.internal.contentDigest, metadata)
58+
} catch (err) {
59+
reportError(`Failed to process image ${file.absolutePath}`, err)
60+
}
5261

53-
metadata = { width, height, density, format, dominantColor }
54-
metadataCache.set(file.internal.contentDigest, metadata)
5562
return metadata
5663
}
5764

@@ -143,7 +150,7 @@ export async function generateImageData({
143150

144151
let primaryFormat: ImageFormat | undefined
145152
if (useAuto) {
146-
primaryFormat = normalizeFormat(metadata.format || file.extension)
153+
primaryFormat = normalizeFormat(metadata?.format || file.extension)
147154
} else if (formats.has(`png`)) {
148155
primaryFormat = `png`
149156
} else if (formats.has(`jpg`)) {
@@ -334,7 +341,7 @@ export async function generateImageData({
334341
imageProps.placeholder = {
335342
fallback,
336343
}
337-
} else if (metadata.dominantColor) {
344+
} else if (metadata?.dominantColor) {
338345
imageProps.backgroundColor = metadata.dominantColor
339346
}
340347

0 commit comments

Comments
 (0)