mirror of
https://github.com/ancsemi/Haven.git
synced 2026-10-27 09:05:01 +00:00
The web client HTML-escaped CDN query strings before the media proxy fetched them, so signed Discord URLs 404ed and the bubble was blank. Decode first, align the /uploads/ image regex with mobile, and stop 403ing Discord with a bot User-Agent. Co-authored-by: Cursor <cursoragent@cursor.com>
52 lines
2.4 KiB
JavaScript
52 lines
2.4 KiB
JavaScript
'use strict';
|
|
|
|
// Desktop used to HTML-escape message content before turning image URLs into
|
|
// <img src>, so Ferry's Discord CDN query strings (?ex=&is=&hm=) became
|
|
// & and the media proxy requested a URL Discord has never signed. Mobile
|
|
// never escapes the URL, which is why the same photo showed there and not on
|
|
// desktop. These tests lock the decode + filename regexes the client now uses.
|
|
|
|
const assert = require('node:assert/strict');
|
|
const test = require('node:test');
|
|
const { sniffImageType } = require('../src/mediaProxy');
|
|
|
|
function decodeHtmlEntities(str) {
|
|
return str
|
|
.replace(/'/g, "'")
|
|
.replace(/"/g, '"')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/&/g, '&');
|
|
}
|
|
|
|
const IMAGE_RE = /\.(jpg|jpeg|png|gif|webp)(\?[^"'<>]*)?$/i;
|
|
const LOCAL_UPLOAD_RE = /^\/uploads\/(?:[\w\-]+\/)?[\w\-.]+\.(jpg|jpeg|png|gif|webp|svg)$/i;
|
|
const OLD_LOCAL_RE = /^\/uploads\/[\w\-]+\.(jpg|jpeg|png|gif|webp|svg)$/i;
|
|
|
|
test('HTML-escaped Discord CDN query strings decode back to a fetchable URL', () => {
|
|
const raw = 'https://cdn.discordapp.com/attachments/1178/8830/photo.png?ex=68b0&is=68af&hm=abcd&';
|
|
const escaped = raw.replace(/&/g, '&');
|
|
assert.notEqual(escaped, raw);
|
|
assert.equal(decodeHtmlEntities(escaped), raw);
|
|
assert.match(decodeHtmlEntities(escaped), IMAGE_RE);
|
|
// encodeURIComponent of the escaped form is what used to 404 upstream.
|
|
assert.match(encodeURIComponent(escaped), /amp%3B/);
|
|
assert.doesNotMatch(encodeURIComponent(raw), /amp%3B/);
|
|
});
|
|
|
|
test('local upload regex allows dots in the basename and one extra path segment', () => {
|
|
assert.match('/uploads/1725-aabbccdd.jpg', LOCAL_UPLOAD_RE);
|
|
assert.match('/uploads/photo.edit.jpg', LOCAL_UPLOAD_RE);
|
|
assert.match('/uploads/images/cat.webp', LOCAL_UPLOAD_RE);
|
|
assert.match('/uploads/stickers/wave.png', LOCAL_UPLOAD_RE);
|
|
assert.doesNotMatch('/uploads/photo.edit.jpg', OLD_LOCAL_RE);
|
|
});
|
|
|
|
test('sniffImageType recognises raster magic bytes when the CDN lies about Content-Type', () => {
|
|
assert.equal(sniffImageType(Buffer.from([0xFF, 0xD8, 0xFF, 0xE0, 0, 0, 0, 0, 0, 0, 0, 0])), 'image/jpeg');
|
|
assert.equal(sniffImageType(Buffer.from([0x89, 0x50, 0x4E, 0x47, 0, 0, 0, 0, 0, 0, 0, 0])), 'image/png');
|
|
assert.equal(sniffImageType(Buffer.from('GIF89a......')), 'image/gif');
|
|
assert.equal(sniffImageType(Buffer.from('RIFF....WEBP')), 'image/webp');
|
|
assert.equal(sniffImageType(Buffer.from('not an image!!')), null);
|
|
});
|