Files
site-builder/craft/src/components/business/BookingEmbed.toHtml.test.ts
T

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&amp;y=&quot;&gt;&lt;script&gt;"');
expect(html).not.toContain('<script');
expect(html).toContain('&lt;b&gt;Book&lt;/b&gt;');
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>/);
});
});