import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const { fetchImage, nameFor } = require('../electron/imagefetch.cjs'); const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'); const publicIp = async () => [{ address: '93.184.216.34' }]; const reply = (body, headers = {}, status = 200) => new Response(body, { status, headers }); const msg = async (p) => { try { await p; return null; } catch (e) { return e.message; } }; test('a picture is downloaded with its name and type', async () => { const seen = []; const img = await fetchImage('https://example.com/a/My%20Photo.png?size=large', { lookup: publicIp, fetch: async (url) => { seen.push(url); return reply(PNG, { 'content-type': 'image/png' }); } }); assert.equal(img.name, 'My Photo.png'); assert.equal(img.type, 'image/png'); assert.equal(img.bytes.length, PNG.length); assert.deepEqual(seen, ['https://example.com/a/My%20Photo.png?size=large']); }); test('names: from the address when it has an image extension, else from the type', () => { assert.equal(nameFor(new URL('https://x.test/pic.JPG'), 'image/jpeg'), 'pic.JPG'); assert.equal(nameFor(new URL('https://x.test/photo'), 'image/webp'), 'photo.webp'); assert.equal(nameFor(new URL('https://x.test/'), 'image/png'), 'cover.png'); assert.equal(nameFor(new URL('https://x.test/get.php'), 'image/gif'), 'get.gif'); assert.equal(nameFor(new URL('https://x.test/a%2Fb%3F.png'), 'image/png'), 'a-b-.png'); }); test('only public pictures: wrong schemes, private addresses, non-images and errors are refused', async () => { const ok = { lookup: publicIp, fetch: async () => reply(PNG, { 'content-type': 'image/png' }) }; assert.match(await msg(fetchImage('file:///etc/passwd', ok)), /public web address/); assert.match(await msg(fetchImage('ftp://example.com/a.png', ok)), /public web address/); assert.match(await msg(fetchImage('http://127.0.0.1/a.png', ok)), /public web address/); assert.match(await msg(fetchImage('http://localhost/a.png', ok)), /public web address/); assert.match(await msg(fetchImage('http://192.168.1.5/a.png', ok)), /public web address/); assert.match(await msg(fetchImage('https://intranet.test/a.png', { ...ok, lookup: async () => [{ address: '10.0.0.7' }] })), /public web address/); assert.match(await msg(fetchImage('not a url', ok)), /public web address/); assert.match(await msg(fetchImage('https://example.com/page', { lookup: publicIp, fetch: async () => reply('', { 'content-type': 'text/html' }) })), /not a picture/); assert.match(await msg(fetchImage('https://example.com/a.png', { lookup: publicIp, fetch: async () => reply('nope', {}, 404) })), /answered 404/); assert.match(await msg(fetchImage('https://example.com/a.png', { lookup: publicIp, fetch: async () => { throw new Error('boom'); } })), /Could not reach/); assert.match(await msg(fetchImage('https://example.com/a.png', { lookup: publicIp, fetch: async () => reply('', { 'content-type': 'image/png' }) })), /nothing/); assert.match(await msg(fetchImage('https://example.com/a.png', { lookup: publicIp, fetch: async () => reply(PNG, { 'content-type': 'image/png', 'content-length': String(60 * 1024 * 1024) }) })), /too big/); }); test('redirects are followed, and each hop is checked again', async () => { const good = async (url) => (url.endsWith('/start') ? reply('', { location: 'https://cdn.example.com/real.png' }, 302) : reply(PNG, { 'content-type': 'image/png' })); const img = await fetchImage('https://example.com/start', { lookup: publicIp, fetch: good }); assert.equal(img.name, 'real.png'); const toPrivate = async () => reply('', { location: 'http://127.0.0.1:8080/secret.png' }, 301); assert.match(await msg(fetchImage('https://example.com/start', { lookup: publicIp, fetch: toPrivate })), /redirects somewhere/); const loop = async () => reply('', { location: 'https://example.com/start' }, 302); assert.match(await msg(fetchImage('https://example.com/start', { lookup: publicIp, fetch: loop })), /Too many redirects/); });