mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 01:15:49 +10:00
Merge branch 'main' into fix/name-input-invalid-characters
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { isHttpUrl, toSafeHref } from './is-http-url';
|
||||
|
||||
describe('isHttpUrl', () => {
|
||||
it('accepts http and https URLs', () => {
|
||||
expect(isHttpUrl('http://example.com')).toBe(true);
|
||||
expect(isHttpUrl('https://example.com/path?q=1#hash')).toBe(true);
|
||||
expect(isHttpUrl('HTTPS://EXAMPLE.COM')).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects non-http(s) schemes', () => {
|
||||
expect(isHttpUrl('javascript:alert(1)')).toBe(false);
|
||||
expect(isHttpUrl('JavaScript:alert(1)')).toBe(false);
|
||||
expect(isHttpUrl('data:text/html,<script>alert(1)</script>')).toBe(false);
|
||||
expect(isHttpUrl('vbscript:msgbox(1)')).toBe(false);
|
||||
expect(isHttpUrl('file:///etc/passwd')).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects non-absolute or unparseable values', () => {
|
||||
expect(isHttpUrl('not a url')).toBe(false);
|
||||
expect(isHttpUrl('/relative/path')).toBe(false);
|
||||
expect(isHttpUrl('')).toBe(false);
|
||||
});
|
||||
|
||||
it('does not treat leading whitespace tricks as safe', () => {
|
||||
// `new URL` trims leading control chars; ensure a smuggled scheme is rejected.
|
||||
expect(isHttpUrl(' javascript:alert(1)')).toBe(false);
|
||||
expect(isHttpUrl('java\tscript:alert(1)')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toSafeHref', () => {
|
||||
it('returns the URL when it is http(s)', () => {
|
||||
expect(toSafeHref('https://example.com')).toBe('https://example.com');
|
||||
});
|
||||
|
||||
it('returns undefined for dangerous or empty values', () => {
|
||||
expect(toSafeHref('javascript:alert(1)')).toBeUndefined();
|
||||
expect(toSafeHref('data:text/html,x')).toBeUndefined();
|
||||
expect(toSafeHref('')).toBeUndefined();
|
||||
expect(toSafeHref(null)).toBeUndefined();
|
||||
expect(toSafeHref(undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
const ALLOWED_PROTOCOLS = ['http', 'https'];
|
||||
|
||||
/**
|
||||
* Returns true only when `value` parses as an absolute URL using the http or
|
||||
* https protocol.
|
||||
*
|
||||
* Zod's `.url()` accepts any parseable URL, including non-web schemes. Use this
|
||||
* to restrict user-supplied URLs to http(s) before they are stored or rendered
|
||||
* as a link.
|
||||
*/
|
||||
export const isHttpUrl = (value: string) => {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
|
||||
return ALLOWED_PROTOCOLS.includes(url.protocol.slice(0, -1).toLowerCase());
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the value to use for a link `href` only when it is an http(s) URL,
|
||||
* otherwise `undefined`. Use this when rendering user-supplied URLs as anchors,
|
||||
* including for older rows stored before URL validation was in place.
|
||||
*/
|
||||
export const toSafeHref = (value: string | null | undefined): string | undefined => {
|
||||
if (!value || !isHttpUrl(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
Reference in New Issue
Block a user