diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dce505..a83e4df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.2.30] - 2026-01-05 + +### Added +- Add `escapeAttrName()` + +### Changed +- Refactor HTML escaping + +### Deprecated +- Mark `escapeAttrValue()` as deprecated with warning to use `escape()` + ## [v0.2.29] - 2025-12-23 ### Changed diff --git a/dom.js b/dom.js index 17070cf..fa00154 100644 --- a/dom.js +++ b/dom.js @@ -1,11 +1,30 @@ import { attachListeners } from '@aegisjsproject/callback-registry/events.js'; - -const ESCAPED_PATTERN = /&(?![a-zA-Z\d]{2,5};|#\d{1,3};)/g; - -export const escapeAttrVal = str => str.toString() - // Do not double-escape - .replaceAll(ESCAPED_PATTERN, '&') - .replaceAll('"', '"'); +export const HTML_UNSAFE_PATTERN = /[<>"']|&(?![a-zA-Z\d]{2,5};|#\d{1,3};)/g; +/*eslint no-control-regex: "off"*/ +export const ATTR_NAME_UNSAFE_PATTERN = /[\u0000-\u001f\u007f-\u009f\s"'\\/=><&]/g; +export const HTML_REPLACEMENTS = Object.freeze({ + '&': '&', + '"': '"', + '\'': ''', + '<': '<', + '>': '>', +}); + +export const escape = str => (str?.toString?.() ?? '') + .replaceAll(HTML_UNSAFE_PATTERN, char => HTML_REPLACEMENTS[char]); + +/** + * + * @param {@deprecated} str + * @returns {string} + */ +export const escapeAttrVal = str => { + console.warn('`escapeAttrVal()` is deprecated. Please use `escape()` instead.'); + return escape(str); +}; + +export const escapeAttrName = str => (str?.toString?.() ?? '') + .replace(ATTR_NAME_UNSAFE_PATTERN, char => '_' + char.charCodeAt(0).toString(16).padStart(4, '0') + '_'); export function createAttribute(name, value = '', namespace) { const attr = typeof namespace === 'string' @@ -17,13 +36,7 @@ export function createAttribute(name, value = '', namespace) { return attr; } -export const stringifyAttr = attr => `${attr.name}="${escapeAttrVal(attr.value)}"`; - -export const escape = str => str.toString() - .replaceAll(ESCAPED_PATTERN, '&') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('"', '"'); +export const stringifyAttr = attr => `${escapeAttrName(attr?.name)}="${escape(attr?.value)}"`; export const getUniqueSelector = (prefix = '_aegis-scope') => `${prefix}-${crypto.randomUUID()}`; diff --git a/http.config.js b/http.config.js index 25ae412..cf85a6f 100644 --- a/http.config.js +++ b/http.config.js @@ -1,7 +1,7 @@ -import { useDefaultCSP, addConnectSrc, addTrustedTypePolicy, lockCSP } from '@aegisjsproject/http-utils/csp.js'; - +import { useDefaultCSP, addScriptSrc, addConnectSrc, addTrustedTypePolicy, lockCSP } from '@aegisjsproject/http-utils/csp.js'; +addScriptSrc('https://unpkg.com/@shgysk8zer0/', 'https://unpkg.com/@aegisjsproject/'); addConnectSrc('https://icanhazdadjoke.com/'); -addTrustedTypePolicy('aegis-router#html', 'default'); +addTrustedTypePolicy('aegis-router#html', 'aegis-sanitizer#html', 'default'); lockCSP(); export default { diff --git a/index.html b/index.html index abb65ee..7b5f65e 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ diff --git a/package-lock.json b/package-lock.json index 36dadeb..9bd423a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aegisjsproject/core", - "version": "0.2.29", + "version": "0.2.30", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aegisjsproject/core", - "version": "0.2.29", + "version": "0.2.30", "funding": [ { "type": "librepay", @@ -1206,6 +1206,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1403,6 +1404,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2053,6 +2055,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -2871,7 +2874,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -3007,6 +3011,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3465,6 +3470,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "dev": true, + "peer": true, "requires": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", diff --git a/package.json b/package.json index 5d67663..2c2981f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aegisjsproject/core", - "version": "0.2.29", + "version": "0.2.30", "description": "A fast, secure, modern, light-weight, and simple JS library for creating web components and more!", "keywords": [ "aegis",