@xmldom/xmldom 0.7.6 → 0.7.8

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 CHANGED
@@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.7.8](https://github.com/xmldom/xmldom/compare/0.7.7...0.7.8)
8
+
9
+ ### Fixed
10
+
11
+ - fix: Restore ES5 compatibility [`#452`](https://github.com/xmldom/xmldom/pull/452) / [`#453`](https://github.com/xmldom/xmldom/issues/453)
12
+
13
+ Thank you, [@fengxinming](https://github.com/fengxinming), for your contributions
14
+
15
+
16
+ ## [0.7.7](https://github.com/xmldom/xmldom/compare/0.7.6...0.7.7)
17
+
18
+ ### Fixed
19
+
20
+ - Security: Prevent inserting DOM nodes when they are not well-formed [`CVE-2022-39353`](https://github.com/xmldom/xmldom/security/advisories/GHSA-crh6-fp67-6883)
21
+ In case such a DOM would be created, the part that is not well-formed will be transformed into text nodes, in which xml specific characters like `<` and `>` are encoded accordingly.
22
+ In the upcoming version 0.9.0 those text nodes will no longer be added and an error will be thrown instead.
23
+ This change can break your code, if you relied on this behavior, e.g. multiple root elements in the past. We consider it more important to align with the specs that we want to be aligned with, considering the potential security issues that might derive from people not being aware of the difference in behavior.
24
+ Related Spec: <https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity>
25
+
26
+ Thank you, [@frumioj](https://github.com/frumioj), [@cjbarth](https://github.com/cjbarth), [@markgollnick](https://github.com/markgollnick) for your contributions
27
+
7
28
  ## [0.7.6](https://github.com/xmldom/xmldom/compare/0.7.5...0.7.6)
8
29
 
9
30
  ### Fixed
@@ -1,5 +1,37 @@
1
1
  'use strict'
2
2
 
3
+ /**
4
+ * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
5
+ *
6
+ * Works with anything that has a `length` property and index access properties, including NodeList.
7
+ *
8
+ * @template {unknown} T
9
+ * @param {Array<T> | ({length:number, [number]: T})} list
10
+ * @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
11
+ * @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default,
12
+ * allows injecting a custom implementation in tests
13
+ * @returns {T | undefined}
14
+ *
15
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
16
+ * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find
17
+ */
18
+ function find(list, predicate, ac) {
19
+ if (ac === undefined) {
20
+ ac = Array.prototype;
21
+ }
22
+ if (list && typeof ac.find === 'function') {
23
+ return ac.find.call(list, predicate);
24
+ }
25
+ for (var i = 0; i < list.length; i++) {
26
+ if (Object.prototype.hasOwnProperty.call(list, i)) {
27
+ var item = list[i];
28
+ if (predicate.call(undefined, item, i, list)) {
29
+ return item;
30
+ }
31
+ }
32
+ }
33
+ }
34
+
3
35
  /**
4
36
  * "Shallow freezes" an object to render it immutable.
5
37
  * Uses `Object.freeze` if available,
@@ -139,6 +171,7 @@ var NAMESPACE = freeze({
139
171
  XMLNS: 'http://www.w3.org/2000/xmlns/',
140
172
  })
141
173
 
174
+ exports.find = find;
142
175
  exports.freeze = freeze;
143
176
  exports.MIME_TYPE = MIME_TYPE;
144
177
  exports.NAMESPACE = NAMESPACE;
package/lib/dom.js CHANGED
@@ -1,5 +1,6 @@
1
1
  var conventions = require("./conventions");
2
2
 
3
+ var find = conventions.find;
3
4
  var NAMESPACE = conventions.NAMESPACE;
4
5
 
5
6
  /**
@@ -158,14 +159,14 @@ NodeList.prototype = {
158
159
  * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive.
159
160
  * @standard level1
160
161
  */
161
- length:0,
162
+ length:0,
162
163
  /**
163
164
  * Returns the indexth item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null.
164
165
  * @standard level1
165
- * @param index unsigned long
166
+ * @param index unsigned long
166
167
  * Index into the collection.
167
168
  * @return Node
168
- * The node at the indexth position in the NodeList, or null if that is not a valid index.
169
+ * The node at the indexth position in the NodeList, or null if that is not a valid index.
169
170
  */
170
171
  item: function(index) {
171
172
  return this[index] || null;
@@ -175,7 +176,23 @@ NodeList.prototype = {
175
176
  serializeToString(this[i],buf,isHTML,nodeFilter);
176
177
  }
177
178
  return buf.join('');
178
- }
179
+ },
180
+ /**
181
+ * @private
182
+ * @param {function (Node):boolean} predicate
183
+ * @returns {Node[]}
184
+ */
185
+ filter: function (predicate) {
186
+ return Array.prototype.filter.call(this, predicate);
187
+ },
188
+ /**
189
+ * @private
190
+ * @param {Node} item
191
+ * @returns {number}
192
+ */
193
+ indexOf: function (item) {
194
+ return Array.prototype.indexOf.call(this, item);
195
+ },
179
196
  };
180
197
 
181
198
  function LiveNodeList(node,refresh){
@@ -209,7 +226,7 @@ _extends(LiveNodeList,NodeList);
209
226
  * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap,
210
227
  * and does not imply that the DOM specifies an order to these Nodes.
211
228
  * NamedNodeMap objects in the DOM are live.
212
- * used for attributes or DocumentType entities
229
+ * used for attributes or DocumentType entities
213
230
  */
214
231
  function NamedNodeMap() {
215
232
  };
@@ -253,7 +270,7 @@ function _removeNamedNode(el,list,attr){
253
270
  }
254
271
  }
255
272
  }else{
256
- throw DOMException(NOT_FOUND_ERR,new Error(el.tagName+'@'+attr))
273
+ throw new DOMException(NOT_FOUND_ERR,new Error(el.tagName+'@'+attr))
257
274
  }
258
275
  }
259
276
  NamedNodeMap.prototype = {
@@ -298,10 +315,10 @@ NamedNodeMap.prototype = {
298
315
  var attr = this.getNamedItem(key);
299
316
  _removeNamedNode(this._ownerElement,this,attr);
300
317
  return attr;
301
-
302
-
318
+
319
+
303
320
  },// raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR
304
-
321
+
305
322
  //for level2
306
323
  removeNamedItemNS:function(namespaceURI,localName){
307
324
  var attr = this.getNamedItemNS(namespaceURI,localName);
@@ -447,10 +464,10 @@ Node.prototype = {
447
464
  prefix : null,
448
465
  localName : null,
449
466
  // Modified in DOM Level 2:
450
- insertBefore:function(newChild, refChild){//raises
467
+ insertBefore:function(newChild, refChild){//raises
451
468
  return _insertBefore(this,newChild,refChild);
452
469
  },
453
- replaceChild:function(newChild, oldChild){//raises
470
+ replaceChild:function(newChild, oldChild){//raises
454
471
  this.insertBefore(newChild,oldChild);
455
472
  if(oldChild){
456
473
  this.removeChild(oldChild);
@@ -618,7 +635,7 @@ function _onUpdateChild(doc,el,newChild){
618
635
  /**
619
636
  * attributes;
620
637
  * children;
621
- *
638
+ *
622
639
  * writeable properties:
623
640
  * nodeValue,Attr:value,CharacterData:data
624
641
  * prefix
@@ -639,48 +656,177 @@ function _removeChild(parentNode,child){
639
656
  _onUpdateChild(parentNode.ownerDocument,parentNode);
640
657
  return child;
641
658
  }
659
+
642
660
  /**
643
- * preformance key(refChild == null)
661
+ * Returns `true` if `node` can be a parent for insertion.
662
+ * @param {Node} node
663
+ * @returns {boolean}
644
664
  */
645
- function _insertBefore(parentNode,newChild,nextChild){
646
- var cp = newChild.parentNode;
665
+ function hasValidParentNodeType(node) {
666
+ return (
667
+ node &&
668
+ (node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE)
669
+ );
670
+ }
671
+
672
+ /**
673
+ * Returns `true` if `node` can be inserted according to it's `nodeType`.
674
+ * @param {Node} node
675
+ * @returns {boolean}
676
+ */
677
+ function hasInsertableNodeType(node) {
678
+ return (
679
+ node &&
680
+ (isElementNode(node) ||
681
+ isTextNode(node) ||
682
+ isDocTypeNode(node) ||
683
+ node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
684
+ node.nodeType === Node.COMMENT_NODE ||
685
+ node.nodeType === Node.PROCESSING_INSTRUCTION_NODE)
686
+ );
687
+ }
688
+
689
+ /**
690
+ * Returns true if `node` is a DOCTYPE node
691
+ * @param {Node} node
692
+ * @returns {boolean}
693
+ */
694
+ function isDocTypeNode(node) {
695
+ return node && node.nodeType === Node.DOCUMENT_TYPE_NODE;
696
+ }
697
+
698
+ /**
699
+ * Returns true if the node is an element
700
+ * @param {Node} node
701
+ * @returns {boolean}
702
+ */
703
+ function isElementNode(node) {
704
+ return node && node.nodeType === Node.ELEMENT_NODE;
705
+ }
706
+ /**
707
+ * Returns true if `node` is a text node
708
+ * @param {Node} node
709
+ * @returns {boolean}
710
+ */
711
+ function isTextNode(node) {
712
+ return node && node.nodeType === Node.TEXT_NODE;
713
+ }
714
+
715
+ /**
716
+ * Check if en element node can be inserted before `child`, or at the end if child is falsy,
717
+ * according to the presence and position of a doctype node on the same level.
718
+ *
719
+ * @param {Document} doc The document node
720
+ * @param {Node} child the node that would become the nextSibling if the element would be inserted
721
+ * @returns {boolean} `true` if an element can be inserted before child
722
+ * @private
723
+ * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
724
+ */
725
+ function isElementInsertionPossible(doc, child) {
726
+ var parentChildNodes = doc.childNodes || [];
727
+ if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) {
728
+ return false;
729
+ }
730
+ var docTypeNode = find(parentChildNodes, isDocTypeNode);
731
+ return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
732
+ }
733
+ /**
734
+ * @private
735
+ * @param {Node} parent the parent node to insert `node` into
736
+ * @param {Node} node the node to insert
737
+ * @param {Node=} child the node that should become the `nextSibling` of `node`
738
+ * @returns {Node}
739
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
740
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
741
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
742
+ */
743
+ function _insertBefore(parent, node, child) {
744
+ if (!hasValidParentNodeType(parent)) {
745
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
746
+ }
747
+ if (child && child.parentNode !== parent) {
748
+ throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
749
+ }
750
+ if (
751
+ !hasInsertableNodeType(node) ||
752
+ // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
753
+ // || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE)
754
+ (isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE)
755
+ ) {
756
+ throw new DOMException(
757
+ HIERARCHY_REQUEST_ERR,
758
+ 'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType
759
+ );
760
+ }
761
+ var parentChildNodes = parent.childNodes || [];
762
+ var nodeChildNodes = node.childNodes || [];
763
+ if (parent.nodeType === Node.DOCUMENT_NODE) {
764
+ if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
765
+ var nodeChildElements = nodeChildNodes.filter(isElementNode);
766
+ if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
767
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
768
+ }
769
+ if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
770
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
771
+ }
772
+ }
773
+ if (isElementNode(node)) {
774
+ if (find(parentChildNodes, isElementNode) || !isElementInsertionPossible(parent, child)) {
775
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
776
+ }
777
+ }
778
+ if (isDocTypeNode(node)) {
779
+ if (find(parentChildNodes, isDocTypeNode)) {
780
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
781
+ }
782
+ var parentElementChild = find(parentChildNodes, isElementNode);
783
+ if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
784
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
785
+ }
786
+ if (!child && parentElementChild) {
787
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
788
+ }
789
+ }
790
+ }
791
+
792
+ var cp = node.parentNode;
647
793
  if(cp){
648
- cp.removeChild(newChild);//remove and update
794
+ cp.removeChild(node);//remove and update
649
795
  }
650
- if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){
651
- var newFirst = newChild.firstChild;
796
+ if(node.nodeType === DOCUMENT_FRAGMENT_NODE){
797
+ var newFirst = node.firstChild;
652
798
  if (newFirst == null) {
653
- return newChild;
799
+ return node;
654
800
  }
655
- var newLast = newChild.lastChild;
801
+ var newLast = node.lastChild;
656
802
  }else{
657
- newFirst = newLast = newChild;
803
+ newFirst = newLast = node;
658
804
  }
659
- var pre = nextChild ? nextChild.previousSibling : parentNode.lastChild;
805
+ var pre = child ? child.previousSibling : parent.lastChild;
660
806
 
661
807
  newFirst.previousSibling = pre;
662
- newLast.nextSibling = nextChild;
663
-
664
-
808
+ newLast.nextSibling = child;
809
+
810
+
665
811
  if(pre){
666
812
  pre.nextSibling = newFirst;
667
813
  }else{
668
- parentNode.firstChild = newFirst;
814
+ parent.firstChild = newFirst;
669
815
  }
670
- if(nextChild == null){
671
- parentNode.lastChild = newLast;
816
+ if(child == null){
817
+ parent.lastChild = newLast;
672
818
  }else{
673
- nextChild.previousSibling = newLast;
819
+ child.previousSibling = newLast;
674
820
  }
675
821
  do{
676
- newFirst.parentNode = parentNode;
822
+ newFirst.parentNode = parent;
677
823
  }while(newFirst !== newLast && (newFirst= newFirst.nextSibling))
678
- _onUpdateChild(parentNode.ownerDocument||parentNode,parentNode);
679
- //console.log(parentNode.lastChild.nextSibling == null)
680
- if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) {
681
- newChild.firstChild = newChild.lastChild = null;
824
+ _onUpdateChild(parent.ownerDocument||parent, parent);
825
+ //console.log(parent.lastChild.nextSibling == null)
826
+ if (node.nodeType == DOCUMENT_FRAGMENT_NODE) {
827
+ node.firstChild = node.lastChild = null;
682
828
  }
683
- return newChild;
829
+ return node;
684
830
  }
685
831
  function _appendSingleChild(parentNode,newChild){
686
832
  var cp = newChild.parentNode;
@@ -703,6 +849,7 @@ function _appendSingleChild(parentNode,newChild){
703
849
  return newChild;
704
850
  //console.log("__aa",parentNode.lastChild.nextSibling == null)
705
851
  }
852
+
706
853
  Document.prototype = {
707
854
  //implementation : null,
708
855
  nodeName : '#document',
@@ -727,11 +874,13 @@ Document.prototype = {
727
874
  }
728
875
  return newChild;
729
876
  }
730
- if(this.documentElement == null && newChild.nodeType == ELEMENT_NODE){
877
+ _insertBefore(this, newChild, refChild);
878
+ newChild.ownerDocument = this;
879
+ if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) {
731
880
  this.documentElement = newChild;
732
881
  }
733
882
 
734
- return _insertBefore(this,newChild,refChild),(newChild.ownerDocument = this),newChild;
883
+ return newChild;
735
884
  },
736
885
  removeChild : function(oldChild){
737
886
  if(this.documentElement == oldChild){
@@ -925,7 +1074,7 @@ Element.prototype = {
925
1074
  var attr = this.getAttributeNode(name)
926
1075
  attr && this.removeAttributeNode(attr);
927
1076
  },
928
-
1077
+
929
1078
  //four real opeartion method
930
1079
  appendChild:function(newChild){
931
1080
  if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){
@@ -949,7 +1098,7 @@ Element.prototype = {
949
1098
  var old = this.getAttributeNodeNS(namespaceURI, localName);
950
1099
  old && this.removeAttributeNode(old);
951
1100
  },
952
-
1101
+
953
1102
  hasAttributeNS : function(namespaceURI, localName){
954
1103
  return this.getAttributeNodeNS(namespaceURI, localName)!=null;
955
1104
  },
@@ -965,7 +1114,7 @@ Element.prototype = {
965
1114
  getAttributeNodeNS : function(namespaceURI, localName){
966
1115
  return this.attributes.getNamedItemNS(namespaceURI, localName);
967
1116
  },
968
-
1117
+
969
1118
  getElementsByTagName : function(tagName){
970
1119
  return new LiveNodeList(this,function(base){
971
1120
  var ls = [];
@@ -986,7 +1135,7 @@ Element.prototype = {
986
1135
  }
987
1136
  });
988
1137
  return ls;
989
-
1138
+
990
1139
  });
991
1140
  }
992
1141
  };
@@ -1015,7 +1164,7 @@ CharacterData.prototype = {
1015
1164
  },
1016
1165
  insertData: function(offset,text) {
1017
1166
  this.replaceData(offset,0,text);
1018
-
1167
+
1019
1168
  },
1020
1169
  appendChild:function(newChild){
1021
1170
  throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR])
@@ -1109,7 +1258,7 @@ function nodeSerializeToString(isHtml,nodeFilter){
1109
1258
  var refNode = this.nodeType == 9 && this.documentElement || this;
1110
1259
  var prefix = refNode.prefix;
1111
1260
  var uri = refNode.namespaceURI;
1112
-
1261
+
1113
1262
  if(uri && prefix == null){
1114
1263
  //console.log(prefix)
1115
1264
  var prefix = refNode.lookupPrefix(uri);
@@ -1142,8 +1291,8 @@ function needNamespaceDefine(node, isHTML, visibleNamespaces) {
1142
1291
  if (prefix === "xml" && uri === NAMESPACE.XML || uri === NAMESPACE.XMLNS) {
1143
1292
  return false;
1144
1293
  }
1145
-
1146
- var i = visibleNamespaces.length
1294
+
1295
+ var i = visibleNamespaces.length
1147
1296
  while (i--) {
1148
1297
  var ns = visibleNamespaces[i];
1149
1298
  // get namespace prefix
@@ -1187,7 +1336,7 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
1187
1336
  var len = attrs.length;
1188
1337
  var child = node.firstChild;
1189
1338
  var nodeName = node.tagName;
1190
-
1339
+
1191
1340
  isHTML = NAMESPACE.isHTML(node.namespaceURI) || isHTML
1192
1341
 
1193
1342
  var prefixedNodeName = nodeName
@@ -1246,14 +1395,14 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
1246
1395
  serializeToString(attr,buf,isHTML,nodeFilter,visibleNamespaces);
1247
1396
  }
1248
1397
 
1249
- // add namespace for current node
1398
+ // add namespace for current node
1250
1399
  if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) {
1251
1400
  var prefix = node.prefix||'';
1252
1401
  var uri = node.namespaceURI;
1253
1402
  addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
1254
1403
  visibleNamespaces.push({ prefix: prefix, namespace:uri });
1255
1404
  }
1256
-
1405
+
1257
1406
  if(child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)){
1258
1407
  buf.push('>');
1259
1408
  //if is cdata child node
@@ -1468,7 +1617,7 @@ try{
1468
1617
  }
1469
1618
  }
1470
1619
  })
1471
-
1620
+
1472
1621
  function getTextContent(node){
1473
1622
  switch(node.nodeType){
1474
1623
  case ELEMENT_NODE:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmldom/xmldom",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "description": "A pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module.",
5
5
  "keywords": [
6
6
  "w3c",
@@ -31,7 +31,9 @@
31
31
  "start": "nodemon --watch package.json --watch lib --watch test --exec 'npm --silent run test && npm --silent run lint'",
32
32
  "stryker": "stryker run",
33
33
  "stryker:dry-run": "stryker run -m '' --reporters progress",
34
- "test": "jest"
34
+ "test": "jest",
35
+ "testrelease": "npm test && eslint lib",
36
+ "release": "np --no-yarn --test-script testrelease --branch release-0.7.x --tag lts"
35
37
  },
36
38
  "engines": {
37
39
  "node": ">=10.0.0"
@@ -46,6 +48,7 @@
46
48
  "get-stream": "^6.0.1",
47
49
  "jest": "^27.0.6",
48
50
  "nodemon": "^2.0.12",
51
+ "np": "7.6.2",
49
52
  "prettier": "^2.3.2",
50
53
  "xmltest": "^1.5.0",
51
54
  "yauzl": "^2.10.0"