prosemirror-model
Advanced tools
Comparing version 1.22.0 to 1.22.1
@@ -0,1 +1,7 @@ | ||
## 1.22.1 (2024-07-14) | ||
### Bug fixes | ||
Add code to `DOMSerializer` that rejects DOM output specs when they originate from attribute values, to protect against XSS attacks that use corrupt attribute input. | ||
## 1.22.0 (2024-07-14) | ||
@@ -2,0 +8,0 @@ |
{ | ||
"name": "prosemirror-model", | ||
"version": "1.22.0", | ||
"version": "1.22.1", | ||
"description": "ProseMirror's document model", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -79,3 +79,3 @@ import {Fragment} from "./fragment" | ||
let {dom, contentDOM} = | ||
DOMSerializer.renderSpec(doc(options), this.nodes[node.type.name](node)) | ||
renderSpec(doc(options), this.nodes[node.type.name](node), null, node.attrs) | ||
if (contentDOM) { | ||
@@ -109,3 +109,3 @@ if (node.isLeaf) | ||
let toDOM = this.marks[mark.type.name] | ||
return toDOM && DOMSerializer.renderSpec(doc(options), toDOM(mark, inline)) | ||
return toDOM && renderSpec(doc(options), toDOM(mark, inline), null, mark.attrs) | ||
} | ||
@@ -120,40 +120,3 @@ | ||
} { | ||
if (typeof structure == "string") | ||
return {dom: doc.createTextNode(structure)} | ||
if ((structure as DOMNode).nodeType != null) | ||
return {dom: structure as DOMNode} | ||
if ((structure as any).dom && (structure as any).dom.nodeType != null) | ||
return structure as {dom: DOMNode, contentDOM?: HTMLElement} | ||
let tagName = (structure as [string])[0], space = tagName.indexOf(" ") | ||
if (space > 0) { | ||
xmlNS = tagName.slice(0, space) | ||
tagName = tagName.slice(space + 1) | ||
} | ||
let contentDOM: HTMLElement | undefined | ||
let dom = (xmlNS ? doc.createElementNS(xmlNS, tagName) : doc.createElement(tagName)) as HTMLElement | ||
let attrs = (structure as any)[1], start = 1 | ||
if (attrs && typeof attrs == "object" && attrs.nodeType == null && !Array.isArray(attrs)) { | ||
start = 2 | ||
for (let name in attrs) if (attrs[name] != null) { | ||
let space = name.indexOf(" ") | ||
if (space > 0) dom.setAttributeNS(name.slice(0, space), name.slice(space + 1), attrs[name]) | ||
else dom.setAttribute(name, attrs[name]) | ||
} | ||
} | ||
for (let i = start; i < (structure as readonly any[]).length; i++) { | ||
let child = (structure as any)[i] as DOMOutputSpec | 0 | ||
if (child === 0) { | ||
if (i < (structure as readonly any[]).length - 1 || i > start) | ||
throw new RangeError("Content hole must be the only child of its parent node") | ||
return {dom, contentDOM: dom} | ||
} else { | ||
let {dom: inner, contentDOM: innerContent} = DOMSerializer.renderSpec(doc, child, xmlNS) | ||
dom.appendChild(inner) | ||
if (innerContent) { | ||
if (contentDOM) throw new RangeError("Multiple content holes") | ||
contentDOM = innerContent as HTMLElement | ||
} | ||
} | ||
} | ||
return {dom, contentDOM} | ||
return renderSpec(doc, structure, xmlNS) | ||
} | ||
@@ -194,1 +157,80 @@ | ||
} | ||
const suspiciousAttributeCache = new WeakMap<any, readonly any[] | null>() | ||
function suspiciousAttributes(attrs: {[name: string]: any}): readonly any[] | null { | ||
let value = suspiciousAttributeCache.get(attrs) | ||
if (value === undefined) | ||
suspiciousAttributeCache.set(attrs, value = suspiciousAttributesInner(attrs)) | ||
return value | ||
} | ||
function suspiciousAttributesInner(attrs: {[name: string]: any}): readonly any[] | null { | ||
let result: any[] | null = null | ||
function scan(value: any) { | ||
if (value && typeof value == "object") { | ||
if (Array.isArray(value)) { | ||
if (typeof value[0] == "string") { | ||
if (!result) result = [] | ||
result.push(value) | ||
} else { | ||
for (let i = 0; i < value.length; i++) scan(value[i]) | ||
} | ||
} else { | ||
for (let prop in value) scan(value[prop]) | ||
} | ||
} | ||
} | ||
scan(attrs) | ||
return result | ||
} | ||
function renderSpec(doc: Document, structure: DOMOutputSpec, xmlNS: string | null, | ||
blockArraysIn: {[name: string]: any}): { | ||
dom: DOMNode, | ||
contentDOM?: HTMLElement | ||
} { | ||
if (typeof structure == "string") | ||
return {dom: doc.createTextNode(structure)} | ||
if ((structure as DOMNode).nodeType != null) | ||
return {dom: structure as DOMNode} | ||
if ((structure as any).dom && (structure as any).dom.nodeType != null) | ||
return structure as {dom: DOMNode, contentDOM?: HTMLElement} | ||
let tagName = (structure as [string])[0], suspicious | ||
if (typeof tagName != "string") throw new RangeError("Invalid array passed to renderSpec") | ||
if (blockArraysIn && (suspicious = suspiciousAttributes(blockArraysIn)) && | ||
suspicious.indexOf(structure) > -1) | ||
throw new RangeError("Using an array from an attribute object as a DOM spec. This may be an attempted cross site scripting attack.") | ||
let space = tagName.indexOf(" ") | ||
if (space > 0) { | ||
xmlNS = tagName.slice(0, space) | ||
tagName = tagName.slice(space + 1) | ||
} | ||
let contentDOM: HTMLElement | undefined | ||
let dom = (xmlNS ? doc.createElementNS(xmlNS, tagName) : doc.createElement(tagName)) as HTMLElement | ||
let attrs = (structure as any)[1], start = 1 | ||
if (attrs && typeof attrs == "object" && attrs.nodeType == null && !Array.isArray(attrs)) { | ||
start = 2 | ||
for (let name in attrs) if (attrs[name] != null) { | ||
let space = name.indexOf(" ") | ||
if (space > 0) dom.setAttributeNS(name.slice(0, space), name.slice(space + 1), attrs[name]) | ||
else dom.setAttribute(name, attrs[name]) | ||
} | ||
} | ||
for (let i = start; i < (structure as readonly any[]).length; i++) { | ||
let child = (structure as any)[i] as DOMOutputSpec | 0 | ||
if (child === 0) { | ||
if (i < (structure as readonly any[]).length - 1 || i > start) | ||
throw new RangeError("Content hole must be the only child of its parent node") | ||
return {dom, contentDOM: dom} | ||
} else { | ||
let {dom: inner, contentDOM: innerContent} = renderSpec(doc, child, xmlNS, blockArraysIn) | ||
dom.appendChild(inner) | ||
if (innerContent) { | ||
if (contentDOM) throw new RangeError("Multiple content holes") | ||
contentDOM = innerContent as HTMLElement | ||
} | ||
} | ||
} | ||
return {dom, contentDOM} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
524800
11252