66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
import { describe, test, expect } from 'vitest';
|
|
import { BookingEmbed, bookingEmbedUrl } from './BookingEmbed';
|
|
|
|
const toHtml = (BookingEmbed as any).toHtml;
|
|
const defaults = () => ({ ...(BookingEmbed as any).craft.props });
|
|
const run = (url: string, extra: any = {}) => toHtml({ ...defaults(), url, ...extra }, '').html as string;
|
|
|
|
describe('BookingEmbed.toHtml host allowlist', () => {
|
|
test.each([
|
|
'https://calendly.com/acme/30min',
|
|
'https://app.calendly.com/x',
|
|
'https://squareup.com/appointments/book/abc',
|
|
'https://app.squareup.com/appointments/book/abc/L1/start',
|
|
'https://acme.square.site/appointments',
|
|
])('allows %s', (u) => {
|
|
const html = run(u);
|
|
expect(html).toContain('<iframe src="');
|
|
expect(html).toContain('sandbox="allow-scripts allow-forms allow-same-origin allow-popups"');
|
|
expect(html).toContain('referrerpolicy="strict-origin-when-cross-origin"');
|
|
expect(html).toContain('loading="lazy"');
|
|
expect(html).toContain('height:700px');
|
|
});
|
|
|
|
test.each([
|
|
'https://evil.com',
|
|
'javascript:alert(1)',
|
|
' JaVaScRiPt:alert(1)',
|
|
'data:text/html,<script>alert(1)</script>',
|
|
'http://calendly.com/acme',
|
|
'https://calendly.com.evil.com/x',
|
|
'https://evilcalendly.com/x',
|
|
'https://evil.com/calendly.com',
|
|
'https://evil.com?u=https://calendly.com',
|
|
'https://calendly.com@evil.com/x',
|
|
'https://user:pw@calendly.com/x',
|
|
'https://square.site/x',
|
|
'//evil.com',
|
|
'',
|
|
])('never iframes %s', (u) => {
|
|
expect(bookingEmbedUrl(u)).toBe('');
|
|
const html = run(u);
|
|
expect(html).not.toContain('<iframe');
|
|
expect(html).not.toMatch(/javascript:/i);
|
|
expect(html).not.toContain('<script');
|
|
});
|
|
|
|
test("provider 'custom' is not an escape hatch", () => {
|
|
expect(run('https://evil.com', { provider: 'custom' })).not.toContain('<iframe');
|
|
});
|
|
|
|
test('refused https URL degrades to an escaped link, never an iframe', () => {
|
|
const html = run('https://evil.com/a?x=1&y="><script>', { title: '<b>Book</b>' });
|
|
expect(html).toContain('<a href="https://evil.com/a?x=1&y="><script>"');
|
|
expect(html).not.toContain('<script');
|
|
expect(html).toContain('<b>Book</b>');
|
|
expect(html).not.toContain('<iframe');
|
|
});
|
|
|
|
test('title and height are escaped / sanitised', () => {
|
|
const html = run('https://calendly.com/a', { title: '"><script>x</script>', height: '700px;position:fixed" onload="x' });
|
|
expect(html).not.toContain('<script>');
|
|
// quotes/semicolons are stripped from height: the payload stays inside style="".
|
|
expect(html).toMatch(/style="width:100%;height:[^";]*;border:none;display:block"><\/iframe>/);
|
|
});
|
|
});
|