You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
106 lines
3.3 KiB
106 lines
3.3 KiB
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:meshcore_open/helpers/gif_helper.dart';
|
|
|
|
// #283: render pasted GIF/media URLs inline, but ONLY from an allowlist of
|
|
// curated hosts (Giphy + Tenor). Anything off-list must stay a plain link and
|
|
// must never be auto-fetched: auto-loading a stranger's URL leaks the viewer's
|
|
// IP and enables tracking-pixel / read-receipt abuse, and inline-rendering
|
|
// arbitrary hosts is a malware and inappropriate-content vector.
|
|
void main() {
|
|
const gifId = 'zaMiq1BvCdAVIiu3Mb';
|
|
const giphyRender = 'https://media.giphy.com/media/$gifId/giphy.gif';
|
|
|
|
group('Giphy forms all resolve to a renderable URL', () {
|
|
test('the app payload (giphy page URL)', () {
|
|
expect(
|
|
GifHelper.resolveGifUrl('https://giphy.com/gifs/$gifId'),
|
|
giphyRender,
|
|
);
|
|
});
|
|
|
|
test('legacy g:<id>', () {
|
|
expect(GifHelper.resolveGifUrl('g:$gifId'), giphyRender);
|
|
});
|
|
|
|
test('media.giphy.com direct asset', () {
|
|
expect(GifHelper.resolveGifUrl(giphyRender), giphyRender);
|
|
});
|
|
|
|
test('i.giphy.com webp, the form users actually paste', () {
|
|
expect(
|
|
GifHelper.resolveGifUrl('https://i.giphy.com/$gifId.webp'),
|
|
giphyRender,
|
|
);
|
|
});
|
|
|
|
test('i.giphy.com gif', () {
|
|
expect(
|
|
GifHelper.resolveGifUrl('https://i.giphy.com/$gifId.gif'),
|
|
giphyRender,
|
|
);
|
|
});
|
|
|
|
test('a reply carrying a pasted Giphy URL', () {
|
|
expect(
|
|
GifHelper.resolveGifUrl('@[Some Node] https://i.giphy.com/$gifId.webp'),
|
|
giphyRender,
|
|
);
|
|
});
|
|
});
|
|
|
|
group('Tenor direct media resolves to itself', () {
|
|
test('media.tenor.com gif', () {
|
|
const url = 'https://media.tenor.com/abc123XYZ/happy-dance.gif';
|
|
expect(GifHelper.resolveGifUrl(url), url);
|
|
});
|
|
|
|
test('c.tenor.com gif', () {
|
|
const url = 'https://c.tenor.com/abc123XYZ/tenor.gif';
|
|
expect(GifHelper.resolveGifUrl(url), url);
|
|
});
|
|
|
|
test('media.tenor.com webp', () {
|
|
const url = 'https://media.tenor.com/abc123XYZ/thing.webp';
|
|
expect(GifHelper.resolveGifUrl(url), url);
|
|
});
|
|
});
|
|
|
|
group('off-allowlist URLs must NOT render (never auto-fetch)', () {
|
|
test('an arbitrary https image', () {
|
|
expect(
|
|
GifHelper.resolveGifUrl('https://evil.example.com/tracker.gif'),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('a lookalike host is not trusted', () {
|
|
expect(
|
|
GifHelper.resolveGifUrl('https://giphy.com.evil.example/$gifId.gif'),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('a general image host (imgur) is off-list', () {
|
|
expect(GifHelper.resolveGifUrl('https://i.imgur.com/abc123.gif'), isNull);
|
|
});
|
|
|
|
test('plain text is not a GIF', () {
|
|
expect(GifHelper.resolveGifUrl('hey are you there'), isNull);
|
|
});
|
|
|
|
test('a non-media URL is not a GIF', () {
|
|
expect(GifHelper.resolveGifUrl('https://example.com/page'), isNull);
|
|
});
|
|
|
|
test('a tenor PAGE url does not render (needs API resolution)', () {
|
|
// Modern Tenor CDN paths use an opaque hash not derivable from the page
|
|
// id, so a page URL cannot be resolved without the Tenor API. Stays a
|
|
// plain link rather than silently rendering the wrong thing.
|
|
expect(
|
|
GifHelper.resolveGifUrl('https://tenor.com/view/happy-dance-12345'),
|
|
isNull,
|
|
);
|
|
});
|
|
});
|
|
}
|