Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ project adheres to [Semantic Versioning](http://semver.org/).
### Changed
### Added
### Fixed
* Load images from Node.js object URLs (#2525)

3.2.3
==================
Expand Down
33 changes: 23 additions & 10 deletions lib/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
const bindings = require('./bindings')
const Image = module.exports = bindings.Image
const util = require('util')
const { resolveObjectURL } = require('node:buffer')

const { GetSource, SetSource } = bindings

Object.defineProperty(Image.prototype, 'src', {
/**
* src setter. Valid values:
* * `data:` URI
* * Node.js object URL
* * Local file path
* * HTTP or HTTPS URL
* * Buffer containing image data (i.e. not a `data:` URI stored in a Buffer)
Expand All @@ -28,22 +30,34 @@ Object.defineProperty(Image.prototype, 'src', {
* @api public
*/
set (val) {
const onerror = err => {
if (typeof this.onerror === 'function') {
this.onerror(err)
} else {
throw err
}
}

if (typeof val === 'string') {
if (/^\s*data:/.test(val)) { // data: URI
const commaI = val.indexOf(',')
// 'base64' must come before the comma
const isBase64 = val.lastIndexOf('base64', commaI) !== -1
const content = val.slice(commaI + 1)
setSource(this, Buffer.from(content, isBase64 ? 'base64' : 'utf8'), val)
} else if (/^\s*https?:\/\//.test(val)) { // remote URL
const onerror = err => {
if (typeof this.onerror === 'function') {
this.onerror(err)
} else {
throw err
}
} else if (/^\s*blob:nodedata:/.test(val)) { // Node.js object URL
const blob = resolveObjectURL(val)
if (!blob) {
onerror(new Error('Invalid object URL'))
return
}

blob.arrayBuffer()
.then(data => {
setSource(this, Buffer.from(data), val)
})
.catch(onerror)
} else if (/^\s*https?:\/\//.test(val)) { // remote URL
fetch(val, {
method: 'GET',
headers: { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36' }
Expand All @@ -64,9 +78,8 @@ Object.defineProperty(Image.prototype, 'src', {
} else if (Buffer.isBuffer(val)) {
setSource(this, val)
} else {
const err = new Error("Invalid image source")
if (typeof this.onerror === 'function') this.onerror(err)
else throw err
const err = new Error('Invalid image source')
onerror(err)
}
},

Expand Down
30 changes: 30 additions & 0 deletions test/image.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const assertRejects = require('assert-rejects')
const fs = require('fs')
const path = require('path')
const os = require('os')
const { Blob } = require('node:buffer')
const { URL } = require('node:url')

const { createCanvas, loadImage, rsvgVersion, Image } = require('../')
const HAVE_SVG = rsvgVersion !== undefined
Expand Down Expand Up @@ -90,6 +92,34 @@ describe('Image', function () {
})
})

it('loads PNG object URL', async function () {
const objectURL = URL.createObjectURL(new Blob([
fs.readFileSync(pngClock)
], { type: 'image/png' }))

try {
const img = await loadImage(objectURL)
assert.strictEqual(img.src, objectURL)
assert.strictEqual(img.width, 320)
assert.strictEqual(img.height, 320)

const canvas = createCanvas(img.width, img.height)
const ctx = canvas.getContext('2d')
assert.doesNotThrow(() => ctx.drawImage(img, 0, 0))
} finally {
URL.revokeObjectURL(objectURL)
}
})

it('rejects revoked object URLs', async function () {
const objectURL = URL.createObjectURL(new Blob([
fs.readFileSync(pngClock)
], { type: 'image/png' }))
URL.revokeObjectURL(objectURL)

await assert.rejects(loadImage(objectURL), /Invalid object URL/)
})

it('detects invalid PNG', function (done) {
if (process.platform === 'win32') this.skip() // TODO
const img = new Image()
Expand Down
Loading