@nerimity/html-embed 1.1.14 → 1.2.0
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +104 -5
- package/package.json +24 -23
- package/src/index.ts +113 -40
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiLA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM;;;;;;;;IAGtC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -6,10 +39,53 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
39
|
exports.htmlToJson = htmlToJson;
|
|
7
40
|
const htm_1 = __importDefault(require("htm"));
|
|
8
41
|
const css_1 = __importDefault(require("css"));
|
|
42
|
+
const htmlparser2 = __importStar(require("htmlparser2"));
|
|
9
43
|
const { validate } = require("csstree-validator");
|
|
10
|
-
const allowedTags = [
|
|
44
|
+
const allowedTags = [
|
|
45
|
+
"h1",
|
|
46
|
+
"h2",
|
|
47
|
+
"h3",
|
|
48
|
+
"h4",
|
|
49
|
+
"h5",
|
|
50
|
+
"h6",
|
|
51
|
+
"div",
|
|
52
|
+
"img",
|
|
53
|
+
"span",
|
|
54
|
+
"strong",
|
|
55
|
+
"a",
|
|
56
|
+
"style",
|
|
57
|
+
"p",
|
|
58
|
+
"ul",
|
|
59
|
+
"li",
|
|
60
|
+
"ol",
|
|
61
|
+
"table",
|
|
62
|
+
"thead",
|
|
63
|
+
"tbody",
|
|
64
|
+
"tr",
|
|
65
|
+
"td",
|
|
66
|
+
"th",
|
|
67
|
+
"blockquote",
|
|
68
|
+
"pre",
|
|
69
|
+
"br",
|
|
70
|
+
"b",
|
|
71
|
+
"i",
|
|
72
|
+
"u",
|
|
73
|
+
"em",
|
|
74
|
+
"small",
|
|
75
|
+
"mark",
|
|
76
|
+
"sub",
|
|
77
|
+
"sup",
|
|
78
|
+
"code",
|
|
79
|
+
"hr",
|
|
80
|
+
"section",
|
|
81
|
+
"article",
|
|
82
|
+
"header",
|
|
83
|
+
"footer",
|
|
84
|
+
"nav",
|
|
85
|
+
];
|
|
11
86
|
const allowedAttributes = ["href", "src", "color", "style", "class"];
|
|
12
87
|
const allowedCssProperties = [
|
|
88
|
+
"opacity",
|
|
13
89
|
"background-clip",
|
|
14
90
|
"-webkitBackgroundClip",
|
|
15
91
|
"-webkitTextFillColor",
|
|
@@ -66,7 +142,9 @@ const allowedCssProperties = [
|
|
|
66
142
|
"alignContent",
|
|
67
143
|
"alignSelf",
|
|
68
144
|
"whiteSpace",
|
|
145
|
+
"wordBreak",
|
|
69
146
|
"fontFamily",
|
|
147
|
+
"fontStyle",
|
|
70
148
|
"fontSize",
|
|
71
149
|
"fontWeight",
|
|
72
150
|
"zIndex",
|
|
@@ -91,7 +169,7 @@ function h(tag, props, ...children) {
|
|
|
91
169
|
}
|
|
92
170
|
// check if attributes are safe
|
|
93
171
|
if (props) {
|
|
94
|
-
const unsafeAttribute = Object.keys(props).find(prop => !allowedAttributes.includes(prop));
|
|
172
|
+
const unsafeAttribute = Object.keys(props).find((prop) => !allowedAttributes.includes(prop));
|
|
95
173
|
if (unsafeAttribute) {
|
|
96
174
|
throw new Error(unsafeAttribute + " attribute is not allowed!");
|
|
97
175
|
}
|
|
@@ -99,7 +177,7 @@ function h(tag, props, ...children) {
|
|
|
99
177
|
// check if styles are safe
|
|
100
178
|
if (props === null || props === void 0 ? void 0 : props.style) {
|
|
101
179
|
const styles = props.style.split(";");
|
|
102
|
-
const unsafeCssProperty = styles.find(style => {
|
|
180
|
+
const unsafeCssProperty = styles.find((style) => {
|
|
103
181
|
if (style === "")
|
|
104
182
|
return false;
|
|
105
183
|
const keyVal = style.split(":");
|
|
@@ -115,7 +193,8 @@ function h(tag, props, ...children) {
|
|
|
115
193
|
}
|
|
116
194
|
}
|
|
117
195
|
if (props === null || props === void 0 ? void 0 : props.href) {
|
|
118
|
-
if (!props.href.startsWith("http://") &&
|
|
196
|
+
if (!props.href.startsWith("http://") &&
|
|
197
|
+
!props.href.startsWith("https://")) {
|
|
119
198
|
throw new Error("href must start with http:// or https://");
|
|
120
199
|
}
|
|
121
200
|
}
|
|
@@ -126,8 +205,28 @@ function h(tag, props, ...children) {
|
|
|
126
205
|
}
|
|
127
206
|
const _html = htm_1.default.bind(h);
|
|
128
207
|
function htmlToJson(html) {
|
|
208
|
+
validateHtmlStructure(html);
|
|
129
209
|
return _html([html]);
|
|
130
210
|
}
|
|
211
|
+
function validateHtmlStructure(html) {
|
|
212
|
+
let isMalformed = false;
|
|
213
|
+
const parser = new htmlparser2.Parser({
|
|
214
|
+
onerror(error) {
|
|
215
|
+
isMalformed = true;
|
|
216
|
+
},
|
|
217
|
+
}, { decodeEntities: true, lowerCaseTags: true });
|
|
218
|
+
parser.write(html);
|
|
219
|
+
parser.end();
|
|
220
|
+
if (isMalformed) {
|
|
221
|
+
throw new Error("Invalid HTML structure detected.");
|
|
222
|
+
}
|
|
223
|
+
const openTags = (html.match(/<[a-zA-Z0-9]+/g) || []).length;
|
|
224
|
+
const closeTags = (html.match(/<\/[a-zA-Z0-9]+/g) || []).length;
|
|
225
|
+
const expectedCloseTags = openTags - (html.match(/<(img|br|hr)/g) || []).length;
|
|
226
|
+
if (expectedCloseTags !== closeTags) {
|
|
227
|
+
throw new Error("Mismatched or unclosed HTML tags.");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
131
230
|
function checkCSS(cssVal) {
|
|
132
231
|
var _a;
|
|
133
232
|
if (validate(cssVal).length) {
|
|
@@ -156,7 +255,7 @@ function checkCSS(cssVal) {
|
|
|
156
255
|
}
|
|
157
256
|
}
|
|
158
257
|
function cssNameToJsName(name) {
|
|
159
|
-
var split = name.split("-").filter(n => n);
|
|
258
|
+
var split = name.split("-").filter((n) => n);
|
|
160
259
|
var output = "";
|
|
161
260
|
for (var i = 0; i < split.length; i++) {
|
|
162
261
|
if (i > 0 && split[i].length > 0 && !(i == 1 && split[i] == "ms")) {
|
package/package.json
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nerimity/html-embed",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"build": "tsc",
|
|
8
|
-
"example": "tsc & node example.js"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [],
|
|
11
|
-
"author": "",
|
|
12
|
-
"license": "ISC",
|
|
13
|
-
"devDependencies": {
|
|
14
|
-
"@types/css": "^0.0.37",
|
|
15
|
-
"@types/node": "^22.10.10",
|
|
16
|
-
"typescript": "^5.5.2"
|
|
17
|
-
},
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"css": "^3.0.0",
|
|
20
|
-
"csstree-validator": "^4.0.1",
|
|
21
|
-
"htm": "^3.1.0"
|
|
22
|
-
|
|
23
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@nerimity/html-embed",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"example": "tsc & node example.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/css": "^0.0.37",
|
|
15
|
+
"@types/node": "^22.10.10",
|
|
16
|
+
"typescript": "^5.5.2"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"css": "^3.0.0",
|
|
20
|
+
"csstree-validator": "^4.0.1",
|
|
21
|
+
"htm": "^3.1.0",
|
|
22
|
+
"htmlparser2": "^10.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,53 @@
|
|
|
1
|
-
import htm from
|
|
2
|
-
import css from
|
|
3
|
-
|
|
1
|
+
import htm from "htm";
|
|
2
|
+
import css from "css";
|
|
3
|
+
import * as htmlparser2 from "htmlparser2";
|
|
4
|
+
const { validate } = require("csstree-validator");
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const allowedTags = [
|
|
7
|
+
"h1",
|
|
8
|
+
"h2",
|
|
9
|
+
"h3",
|
|
10
|
+
"h4",
|
|
11
|
+
"h5",
|
|
12
|
+
"h6",
|
|
13
|
+
"div",
|
|
14
|
+
"img",
|
|
15
|
+
"span",
|
|
16
|
+
"strong",
|
|
17
|
+
"a",
|
|
18
|
+
"style",
|
|
19
|
+
"p",
|
|
20
|
+
"ul",
|
|
21
|
+
"li",
|
|
22
|
+
"ol",
|
|
23
|
+
"table",
|
|
24
|
+
"thead",
|
|
25
|
+
"tbody",
|
|
26
|
+
"tr",
|
|
27
|
+
"td",
|
|
28
|
+
"th",
|
|
29
|
+
"blockquote",
|
|
30
|
+
"pre",
|
|
31
|
+
"br",
|
|
32
|
+
"b",
|
|
33
|
+
"i",
|
|
34
|
+
"u",
|
|
35
|
+
"em",
|
|
36
|
+
"small",
|
|
37
|
+
"mark",
|
|
38
|
+
"sub",
|
|
39
|
+
"sup",
|
|
40
|
+
"code",
|
|
41
|
+
"hr",
|
|
42
|
+
"section",
|
|
43
|
+
"article",
|
|
44
|
+
"header",
|
|
45
|
+
"footer",
|
|
46
|
+
"nav",
|
|
47
|
+
];
|
|
48
|
+
const allowedAttributes = ["href", "src", "color", "style", "class"];
|
|
8
49
|
const allowedCssProperties = [
|
|
50
|
+
"opacity",
|
|
9
51
|
"background-clip",
|
|
10
52
|
"-webkitBackgroundClip",
|
|
11
53
|
"-webkitTextFillColor",
|
|
@@ -62,7 +104,9 @@ const allowedCssProperties = [
|
|
|
62
104
|
"alignContent",
|
|
63
105
|
"alignSelf",
|
|
64
106
|
"whiteSpace",
|
|
107
|
+
"wordBreak",
|
|
65
108
|
"fontFamily",
|
|
109
|
+
"fontStyle",
|
|
66
110
|
"fontSize",
|
|
67
111
|
"fontWeight",
|
|
68
112
|
"zIndex",
|
|
@@ -79,68 +123,100 @@ const allowedCssProperties = [
|
|
|
79
123
|
"overflowY",
|
|
80
124
|
"userSelect",
|
|
81
125
|
"pointerEvents",
|
|
82
|
-
]
|
|
83
|
-
|
|
126
|
+
];
|
|
84
127
|
|
|
85
128
|
function h(tag: string, props: any, ...children: any[]) {
|
|
86
129
|
// check if tag is safe
|
|
87
130
|
if (!allowedTags.includes(tag.toLowerCase())) {
|
|
88
|
-
throw new Error(tag+" tag is not allowed!")
|
|
131
|
+
throw new Error(tag + " tag is not allowed!");
|
|
89
132
|
}
|
|
90
133
|
// check if attributes are safe
|
|
91
134
|
if (props) {
|
|
92
|
-
const unsafeAttribute = Object.keys(props).find(
|
|
135
|
+
const unsafeAttribute = Object.keys(props).find(
|
|
136
|
+
(prop) => !allowedAttributes.includes(prop)
|
|
137
|
+
);
|
|
93
138
|
if (unsafeAttribute) {
|
|
94
|
-
throw new Error(unsafeAttribute+" attribute is not allowed!")
|
|
139
|
+
throw new Error(unsafeAttribute + " attribute is not allowed!");
|
|
95
140
|
}
|
|
96
141
|
}
|
|
97
142
|
// check if styles are safe
|
|
98
143
|
if (props?.style) {
|
|
99
144
|
const styles: string[] = props.style.split(";");
|
|
100
|
-
const unsafeCssProperty = styles.find(style => {
|
|
145
|
+
const unsafeCssProperty = styles.find((style) => {
|
|
101
146
|
if (style === "") return false;
|
|
102
|
-
const keyVal = style.split(":")
|
|
103
|
-
const key = keyVal[0].trim()
|
|
104
|
-
const value = keyVal[1].trim()
|
|
147
|
+
const keyVal = style.split(":");
|
|
148
|
+
const key = keyVal[0].trim();
|
|
149
|
+
const value = keyVal[1].trim();
|
|
105
150
|
if (key === "position" && value === "fixed") {
|
|
106
|
-
throw new Error(value + " value is not allowed for "+ key + "!")
|
|
151
|
+
throw new Error(value + " value is not allowed for " + key + "!");
|
|
107
152
|
}
|
|
108
|
-
return !allowedCssProperties.includes(cssNameToJsName(key))
|
|
109
|
-
})
|
|
153
|
+
return !allowedCssProperties.includes(cssNameToJsName(key));
|
|
154
|
+
});
|
|
110
155
|
if (unsafeCssProperty) {
|
|
111
|
-
throw new Error(
|
|
156
|
+
throw new Error(
|
|
157
|
+
unsafeCssProperty.split(":")[0].trim() + " property is not allowed!"
|
|
158
|
+
);
|
|
112
159
|
}
|
|
113
160
|
}
|
|
114
161
|
if (props?.href) {
|
|
115
|
-
if (
|
|
116
|
-
|
|
162
|
+
if (
|
|
163
|
+
!props.href.startsWith("http://") &&
|
|
164
|
+
!props.href.startsWith("https://")
|
|
165
|
+
) {
|
|
166
|
+
throw new Error("href must start with http:// or https://");
|
|
117
167
|
}
|
|
118
168
|
}
|
|
119
169
|
if (tag.toLowerCase() === "style" && children[0]) {
|
|
120
170
|
checkCSS(children[0]);
|
|
121
171
|
}
|
|
122
|
-
|
|
172
|
+
|
|
123
173
|
return { tag, attributes: props, content: children };
|
|
124
174
|
}
|
|
125
175
|
|
|
126
176
|
const _html = htm.bind(h);
|
|
127
177
|
|
|
128
|
-
|
|
129
|
-
|
|
130
178
|
export function htmlToJson(html: string) {
|
|
131
|
-
|
|
179
|
+
validateHtmlStructure(html);
|
|
180
|
+
return _html([html] as any);
|
|
132
181
|
}
|
|
133
182
|
|
|
183
|
+
function validateHtmlStructure(html: string) {
|
|
184
|
+
let isMalformed = false;
|
|
185
|
+
const parser = new htmlparser2.Parser(
|
|
186
|
+
{
|
|
187
|
+
onerror(error) {
|
|
188
|
+
isMalformed = true;
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{ decodeEntities: true, lowerCaseTags: true }
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
parser.write(html);
|
|
195
|
+
parser.end();
|
|
196
|
+
|
|
197
|
+
if (isMalformed) {
|
|
198
|
+
throw new Error("Invalid HTML structure detected.");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const openTags = (html.match(/<[a-zA-Z0-9]+/g) || []).length;
|
|
202
|
+
const closeTags = (html.match(/<\/[a-zA-Z0-9]+/g) || []).length;
|
|
134
203
|
|
|
204
|
+
const expectedCloseTags =
|
|
205
|
+
openTags - (html.match(/<(img|br|hr)/g) || []).length;
|
|
206
|
+
|
|
207
|
+
if (expectedCloseTags !== closeTags) {
|
|
208
|
+
throw new Error("Mismatched or unclosed HTML tags.");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
135
211
|
|
|
136
212
|
function checkCSS(cssVal: string) {
|
|
137
213
|
if (validate(cssVal).length) {
|
|
138
|
-
throw new Error("Invalid css!")
|
|
214
|
+
throw new Error("Invalid css!");
|
|
139
215
|
}
|
|
140
|
-
const openCurlyBracketCount = cssVal.match(/{/gi)
|
|
141
|
-
const closeCurlyBracketCount = cssVal.match(/}/gi)
|
|
216
|
+
const openCurlyBracketCount = cssVal.match(/{/gi);
|
|
217
|
+
const closeCurlyBracketCount = cssVal.match(/}/gi);
|
|
142
218
|
if (openCurlyBracketCount?.length !== closeCurlyBracketCount?.length) {
|
|
143
|
-
throw new Error("Extra curly or missing curly brackets found in css!")
|
|
219
|
+
throw new Error("Extra curly or missing curly brackets found in css!");
|
|
144
220
|
}
|
|
145
221
|
const rules = css.parse(cssVal).stylesheet?.rules;
|
|
146
222
|
if (!rules) return;
|
|
@@ -148,21 +224,19 @@ function checkCSS(cssVal: string) {
|
|
|
148
224
|
const rule = rules[i];
|
|
149
225
|
const declarations = (rule as any).declarations;
|
|
150
226
|
for (let x = 0; x < declarations.length; x++) {
|
|
151
|
-
const {property, value} = declarations[x];
|
|
227
|
+
const { property, value } = declarations[x];
|
|
152
228
|
if (!allowedCssProperties.includes(cssNameToJsName(property))) {
|
|
153
|
-
throw new Error(property + " style is not allowed!")
|
|
229
|
+
throw new Error(property + " style is not allowed!");
|
|
154
230
|
}
|
|
155
231
|
if (property === "position" && value === "fixed") {
|
|
156
|
-
throw new Error(value + " value is not allowed for "+ property + "!")
|
|
232
|
+
throw new Error(value + " value is not allowed for " + property + "!");
|
|
157
233
|
}
|
|
158
234
|
}
|
|
159
|
-
|
|
160
235
|
}
|
|
161
236
|
}
|
|
162
237
|
|
|
163
|
-
|
|
164
238
|
function cssNameToJsName(name: string) {
|
|
165
|
-
var split = name.split("-").filter(n => n);
|
|
239
|
+
var split = name.split("-").filter((n) => n);
|
|
166
240
|
var output = "";
|
|
167
241
|
for (var i = 0; i < split.length; i++) {
|
|
168
242
|
if (i > 0 && split[i].length > 0 && !(i == 1 && split[i] == "ms")) {
|
|
@@ -171,12 +245,11 @@ function cssNameToJsName(name: string) {
|
|
|
171
245
|
output += split[i];
|
|
172
246
|
}
|
|
173
247
|
if (name.startsWith("-")) {
|
|
174
|
-
output = "-" + output
|
|
248
|
+
output = "-" + output;
|
|
175
249
|
}
|
|
176
250
|
return output;
|
|
177
251
|
}
|
|
178
252
|
|
|
179
|
-
function jsNameToCssName(name: string)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
253
|
+
function jsNameToCssName(name: string) {
|
|
254
|
+
return name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
255
|
+
}
|