@gcforms/editor 1.0.3 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/package.json +4 -4
- package/src/utils/url.test.ts +103 -0
- package/src/utils/url.ts +17 -13
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.6] - 2025-07-21
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Update Lexical to version 0.33.1
|
|
13
|
+
|
|
14
|
+
## [1.0.5] - 2025-06-23
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Update Yarn to version 4.9.2
|
|
19
|
+
- Update Lexical to version 0.32.0
|
|
20
|
+
|
|
21
|
+
## [1.0.4] - 2025-06-02
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Bumped Lexical version to 0.31.2
|
|
26
|
+
- Refactored URL validation and sanitization
|
|
27
|
+
- Allow mailto: links
|
|
28
|
+
- Wrote unit tests for URL utils
|
|
29
|
+
|
|
8
30
|
## [1.0.3] - 2025-05-13
|
|
9
31
|
|
|
10
32
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gcforms/editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"author": "Canadian Digital Service",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
|
-
"packageManager": "yarn@4.
|
|
9
|
+
"packageManager": "yarn@4.9.2",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@lexical/react": "^0.
|
|
12
|
-
"lexical": "^0.
|
|
11
|
+
"@lexical/react": "^0.33.1",
|
|
12
|
+
"lexical": "^0.33.1"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"react": "^19.1.0",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { sanitizeUrl, isValidUrl } from "./url";
|
|
2
|
+
|
|
3
|
+
describe("sanitizeUrl", () => {
|
|
4
|
+
it("returns safe http/https URLs unchanged", () => {
|
|
5
|
+
expect(sanitizeUrl("http://example.com")).toBe("http://example.com");
|
|
6
|
+
expect(sanitizeUrl("https://example.com")).toBe("https://example.com");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("returns safe mailto", () => {
|
|
10
|
+
expect(sanitizeUrl("mailto:test@example.com")).toBe("mailto:test@example.com");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns relative URLs unchanged", () => {
|
|
14
|
+
expect(sanitizeUrl("/relative/path")).toBe("/relative/path");
|
|
15
|
+
expect(sanitizeUrl("foo/bar")).toBe("foo/bar");
|
|
16
|
+
expect(sanitizeUrl("index.html")).toBe("index.html");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("trims whitespace from URLs", () => {
|
|
20
|
+
expect(sanitizeUrl(" http://example.com ")).toBe("http://example.com");
|
|
21
|
+
expect(sanitizeUrl(" /foo/bar ")).toBe("/foo/bar");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns about:blank for unsafe URLs (does not sanitize)", () => {
|
|
25
|
+
expect(sanitizeUrl("javascript:alert(1)")).toBe("about:blank");
|
|
26
|
+
expect(sanitizeUrl("data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==")).toBe(
|
|
27
|
+
"about:blank"
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns trimmed input for non-URL strings", () => {
|
|
32
|
+
expect(sanitizeUrl("not a url")).toBe("not a url");
|
|
33
|
+
expect(sanitizeUrl(" not a url ")).toBe("not a url");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns about:blank for unsupported protocols", () => {
|
|
37
|
+
expect(sanitizeUrl("ftp://example.com")).toBe("about:blank");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("handles empty string", () => {
|
|
41
|
+
expect(sanitizeUrl("")).toBe("");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("isValidUrl", () => {
|
|
46
|
+
it("returns true for valid http/https URLs", () => {
|
|
47
|
+
expect(isValidUrl("http://example.com")).toBe(true);
|
|
48
|
+
expect(isValidUrl("https://sub.domain.com/path?query=1")).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns true for valid mailto URLs", () => {
|
|
52
|
+
expect(isValidUrl("mailto:test@example.com")).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns true for valid tel URLs", () => {
|
|
56
|
+
expect(isValidUrl("tel:+1234567890")).toBe(true);
|
|
57
|
+
expect(isValidUrl("tel:123-456-7890")).toBe(true);
|
|
58
|
+
expect(isValidUrl("tel:(123)456-7890")).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns true for valid sms URLs", () => {
|
|
62
|
+
expect(isValidUrl("sms:+1234567890")).toBe(true);
|
|
63
|
+
expect(isValidUrl("sms:123-456-7890")).toBe(true);
|
|
64
|
+
expect(isValidUrl("sms:(123)456-7890")).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns false for invalid URLs", () => {
|
|
68
|
+
expect(isValidUrl("notaurl")).toBe(false);
|
|
69
|
+
expect(isValidUrl("http:/bad")).toBe(false);
|
|
70
|
+
expect(isValidUrl("ftp://example.com")).toBe(false); // ftp is not in regexp
|
|
71
|
+
expect(isValidUrl("javascript:alert(1)")).toBe(false);
|
|
72
|
+
expect(isValidUrl("data:image/png;base64,abc")).toBe(false);
|
|
73
|
+
expect(isValidUrl("tel:")).toBe(false);
|
|
74
|
+
expect(isValidUrl("sms:")).toBe(false);
|
|
75
|
+
expect(isValidUrl("tel:abc-def-ghij")).toBe(false);
|
|
76
|
+
expect(isValidUrl("sms:abc-def-ghij")).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns false for empty string", () => {
|
|
80
|
+
expect(isValidUrl("")).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("returns true for mailto with query params", () => {
|
|
84
|
+
expect(isValidUrl("mailto:user@example.com?subject=Hello")).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("returns false for malformed mailto addresses", () => {
|
|
88
|
+
expect(isValidUrl("mailto:user@")).toBe(false);
|
|
89
|
+
expect(isValidUrl("mailto:@example.com")).toBe(false);
|
|
90
|
+
expect(isValidUrl("mailto:")).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("returns false for URLs with internal whitespace", () => {
|
|
94
|
+
expect(isValidUrl("http://exa mple.com")).toBe(false);
|
|
95
|
+
expect(isValidUrl("mailto:test@ example.com")).toBe(false);
|
|
96
|
+
expect(isValidUrl("tel:123 456 7890")).toBe(false);
|
|
97
|
+
expect(isValidUrl("sms:123 456 7890")).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns false for internationalized domain names (unicode)", () => {
|
|
101
|
+
expect(isValidUrl("http://exämple.com")).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
package/src/utils/url.ts
CHANGED
|
@@ -6,25 +6,29 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
/** A pattern that matches safe URLs. */
|
|
11
|
-
const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^&:/?#]*(?:[/?#]|$))/gi;
|
|
12
|
-
|
|
13
|
-
/** A pattern that matches safe data URLs. */
|
|
14
|
-
const DATA_URL_PATTERN =
|
|
15
|
-
/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;
|
|
16
|
-
|
|
17
|
-
url = String(url).trim();
|
|
9
|
+
const SUPPORTED_URL_PROTOCOLS = new Set(["http:", "https:", "mailto:", "tel:", "sms:"]);
|
|
18
10
|
|
|
19
|
-
|
|
11
|
+
export function sanitizeUrl(url: string): string {
|
|
12
|
+
try {
|
|
13
|
+
const parsedUrl = new URL(url);
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) {
|
|
16
|
+
return "about:blank";
|
|
17
|
+
}
|
|
18
|
+
} catch {
|
|
19
|
+
return url.trim();
|
|
20
|
+
}
|
|
21
|
+
return url.trim();
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Validates if a string is a well-formed URL.
|
|
26
|
+
* Supports http, https, mailto, tel, and sms protocols.
|
|
26
27
|
*/
|
|
27
|
-
const urlRegExp = new RegExp(
|
|
28
|
+
const urlRegExp = new RegExp(
|
|
29
|
+
/^(https?:\/\/([a-zA-Z0-9-]{1,63}\.)+[a-zA-Z]{2,}(\/[^\s]*)?|mailto:[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\?[^\s]*)?|tel:\+?[0-9\-().]{3,}|sms:\+?[0-9\-().]{3,})$/
|
|
30
|
+
);
|
|
31
|
+
|
|
28
32
|
export function isValidUrl(url: string): boolean {
|
|
29
33
|
return urlRegExp.test(url);
|
|
30
34
|
}
|