Keep artwork on disk and serve every image from iPX (#111)
The page loaded artwork from each publisher's server, or through /api/art, fetched every time, for http-only hosts. Nothing was kept, every visit asked every publisher, and artwork went when a publisher's server did. - The page draws every image a feed or item names from /api/art. The first time, iPX fetches it (only an address a feed or item names, only an image, up to 5 MB, within the feed timeout) and keeps it in art/ beside the database, under a hash of its address with its type beside it (src/art.rs). Later it comes from disk, which marks it as used. - art_cache_mb, a server setting on the admin page, 500 by default, caps what is kept: the sweep before each scan drops the least recently shown until it fits. 0 keeps nothing, and artwork is fetched through iPX each time. Settings saved before it get the default. - Served from iPX's own address, someone else's image must stay an image: nosniff, and a CSP with sandbox, so an SVG opened on its own runs no script as iPX. - The fixture server sends .jpg as image/jpeg, which /api/art requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -956,6 +956,22 @@ test('reading an item updates its feed\'s count without reloading the list', asy
|
||||
expect(lists, 'the row came back with the read, not by reloading the list').toEqual([]);
|
||||
});
|
||||
|
||||
test('artwork comes from iPX, kept, and only as an image', async ({ page }) => {
|
||||
// Every image a feed names is drawn from iPX's address.
|
||||
expect(await page.evaluate(() => artHTML('https://example.com/a.jpg', 'A'))).toContain('src="/api/art?u=https%3A%2F%2Fexample.com%2Fa.jpg"');
|
||||
// Multi Show's items name art.jpg.
|
||||
await expect(page.locator('.feed', { hasText: 'Multi Show' })).toBeVisible({ timeout: 20_000 });
|
||||
const src = '/api/art?u=' + encodeURIComponent('http://127.0.0.1:8792/art.jpg');
|
||||
for (const _ of [1, 2]) { // fetched, then kept
|
||||
const r = await page.request.get(src);
|
||||
expect(r.status()).toBe(200);
|
||||
expect(r.headers()['content-type']).toMatch(/^image\//);
|
||||
expect(r.headers()['content-security-policy']).toContain('sandbox');
|
||||
}
|
||||
// An address no feed names is not fetched.
|
||||
expect((await page.request.get('/api/art?u=' + encodeURIComponent('http://127.0.0.1:8792/show.xml'))).status()).toBe(404);
|
||||
});
|
||||
|
||||
test('a deleted file looks as if it was never downloaded', async ({ page }) => {
|
||||
// Other people subscribe to Picture Blog by now, so both prompts come; take them.
|
||||
page.on('dialog', d => d.accept());
|
||||
|
||||
Reference in New Issue
Block a user