Skip to content

Commit bd5c63c

Browse files
GatsbyJS Botnategiraudeau
GatsbyJS Bot
andauthored
fix(sharp) wrap sharp calls in try/catch to avoid crashing on bad images (#28645) (#29052)
* added try/catch blocks around image processing * fixed return on getImageMetadata (cherry picked from commit 004acf0) Co-authored-by: Nate Giraudeau <[email protected]>
1 parent a7d6614 commit bd5c63c

File tree

2 files changed

+77
-60
lines changed

2 files changed

+77
-60
lines changed

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

+58-48
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

+19-12
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

@@ -49,7 +50,7 @@ const metadataCache = new Map<string, IImageMetadata>()
4950
export async function getImageMetadata(
5051
file: FileNode,
5152
getDominantColor?: boolean
52-
): Promise<IImageMetadata> {
53+
): Promise<IImageMetadata | undefined> {
5354
if (!getDominantColor) {
5455
// If we don't need the dominant color we can use the cheaper size function
5556
const { width, height, type } = await getImageSizeAsync(file)
@@ -59,18 +60,24 @@ export async function getImageMetadata(
5960
if (metadata && process.env.NODE_ENV !== `test`) {
6061
return metadata
6162
}
62-
const pipeline = sharp(file.absolutePath)
6363

64-
const { width, height, density, format } = await pipeline.metadata()
64+
try {
65+
const pipeline = sharp(file.absolutePath)
6566

66-
const { dominant } = await pipeline.stats()
67-
// Fallback in case sharp doesn't support dominant
68-
const dominantColor = dominant
69-
? rgbToHex(dominant.r, dominant.g, dominant.b)
70-
: `#000000`
67+
const { width, height, density, format } = await pipeline.metadata()
68+
69+
const { dominant } = await pipeline.stats()
70+
// Fallback in case sharp doesn't support dominant
71+
const dominantColor = dominant
72+
? rgbToHex(dominant.r, dominant.g, dominant.b)
73+
: `#000000`
74+
75+
metadata = { width, height, density, format, dominantColor }
76+
metadataCache.set(file.internal.contentDigest, metadata)
77+
} catch (err) {
78+
reportError(`Failed to process image ${file.absolutePath}`, err)
79+
}
7180

72-
metadata = { width, height, density, format, dominantColor }
73-
metadataCache.set(file.internal.contentDigest, metadata)
7481
return metadata
7582
}
7683

@@ -149,7 +156,7 @@ export async function generateImageData({
149156

150157
let primaryFormat: ImageFormat | undefined
151158
if (useAuto) {
152-
primaryFormat = normalizeFormat(metadata.format || file.extension)
159+
primaryFormat = normalizeFormat(metadata?.format || file.extension)
153160
} else if (formats.has(`png`)) {
154161
primaryFormat = `png`
155162
} else if (formats.has(`jpg`)) {
@@ -339,7 +346,7 @@ export async function generateImageData({
339346
imageProps.placeholder = {
340347
fallback,
341348
}
342-
} else if (metadata.dominantColor) {
349+
} else if (metadata?.dominantColor) {
343350
imageProps.backgroundColor = metadata.dominantColor
344351
}
345352

0 commit comments

Comments
 (0)