@angular/platform-server 21.2.21 → 21.2.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/platform-server",
3
- "version": "21.2.21",
3
+ "version": "21.2.23",
4
4
  "description": "Angular - library for using Angular in Node.js",
5
5
  "author": "angular",
6
6
  "license": "MIT",
@@ -8,10 +8,10 @@
8
8
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
9
9
  },
10
10
  "peerDependencies": {
11
- "@angular/common": "21.2.21",
12
- "@angular/compiler": "21.2.21",
13
- "@angular/core": "21.2.21",
14
- "@angular/platform-browser": "21.2.21",
11
+ "@angular/common": "21.2.23",
12
+ "@angular/compiler": "21.2.23",
13
+ "@angular/core": "21.2.23",
14
+ "@angular/platform-browser": "21.2.23",
15
15
  "rxjs": "^6.5.3 || ^7.4.0"
16
16
  },
17
17
  "dependencies": {
@@ -848,6 +848,23 @@ function requireNodeUtils () {
848
848
  return a.name;
849
849
  }
850
850
 
851
+ function fallbackRawContentTags(node) {
852
+ const tags = [];
853
+ while (node) {
854
+ if (node.nodeType === 1 /*ELEMENT_NODE*/) {
855
+ if (node.namespaceURI === NAMESPACE.HTML && hasRawContentFallback[node.tagName]) {
856
+ tags.push(node.localName);
857
+ }
858
+ node = node.parentNode;
859
+ } else if (node.nodeType === 11 /*DOCUMENT_FRAGMENT_NODE*/ && node._host) {
860
+ node = node._host;
861
+ } else {
862
+ node = node.parentNode;
863
+ }
864
+ }
865
+ return tags;
866
+ }
867
+
851
868
  /**
852
869
  * Escapes matching closing tag in a raw text.
853
870
  *
@@ -877,12 +894,22 @@ function requireNodeUtils () {
877
894
  return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
878
895
  }
879
896
 
880
- function escapeFallbackRawText(rawText, parentTag) {
881
- var result = '';
882
- var index = 0;
897
+ function escapeMatchingClosingTags(rawText, parentTag, ancestorTags) {
898
+ let result = escapeMatchingClosingTag(rawText, parentTag);
899
+ if (ancestorTags) {
900
+ for (const ancestorTag of ancestorTags) {
901
+ result = escapeMatchingClosingTag(result, ancestorTag);
902
+ }
903
+ }
904
+ return result;
905
+ }
906
+
907
+ function escapeFallbackRawText(rawText, parentTag, ancestorTags) {
908
+ let result = '';
909
+ let index = 0;
883
910
 
884
911
  while (index < rawText.length) {
885
- var commentStart = rawText.indexOf('<!--', index);
912
+ const commentStart = rawText.indexOf('<!--', index);
886
913
  if (commentStart === -1) {
887
914
  result += escape(rawText.slice(index));
888
915
  break;
@@ -890,15 +917,15 @@ function requireNodeUtils () {
890
917
 
891
918
  result += escape(rawText.slice(index, commentStart));
892
919
 
893
- var commentEnd = findCommentEnd(rawText, commentStart + 4);
920
+ const commentEnd = findCommentEnd(rawText, commentStart + 4);
894
921
  if (commentEnd === -1) {
895
- result += escapeMatchingClosingTag(rawText.slice(commentStart), parentTag);
922
+ result += escapeMatchingClosingTags(rawText.slice(commentStart), parentTag, ancestorTags);
896
923
  break;
897
924
  }
898
925
 
899
926
  // A complete HTML comment remains inert if downstream tooling reparses
900
927
  // fallback raw text as normal HTML, so preserve its comment semantics.
901
- result += escapeMatchingClosingTag(rawText.slice(commentStart, commentEnd), parentTag);
928
+ result += escapeMatchingClosingTags(rawText.slice(commentStart, commentEnd), parentTag, ancestorTags);
902
929
  index = commentEnd;
903
930
  }
904
931
 
@@ -911,24 +938,47 @@ function requireNodeUtils () {
911
938
  if (rawText.charAt(index) === '-' && rawText.charAt(index + 1) === '>')
912
939
  return index + 2;
913
940
 
914
- var match = CLOSING_COMMENT_REGEXP.exec(rawText.slice(index));
941
+ const match = CLOSING_COMMENT_REGEXP.exec(rawText.slice(index));
915
942
  return match ? index + match.index + match[0].length : -1;
916
943
  }
917
944
 
918
945
  const CLOSING_COMMENT_REGEXP = /--!?>/;
919
946
 
947
+ /**
948
+ * Escapes a comment content that abruptly closes the comment.
949
+ *
950
+ * A comment can not carry content that starts with `>` or `->`: the parser
951
+ * closes the comment as soon as it comes across such a sequence right after
952
+ * `<!--` (the "abrupt-closing-of-empty-comment" parse error). For example,
953
+ * `#comment('><img src=x onerror=alert(1)>')` would otherwise serialize into
954
+ * `<!--><img src=x onerror=alert(1)>-->`, which de-serializes into an empty
955
+ * comment followed by a live `<img>` element. Escaping the leading `>` keeps
956
+ * the content inside the comment, where it stays inert.
957
+ */
958
+ function escapeAbruptClosingCommentTag(rawContent) {
959
+ if (rawContent.startsWith('>')) {
960
+ return '&gt;' + rawContent.slice(1);
961
+ }
962
+ if (rawContent.startsWith('->')) {
963
+ return '-&gt;' + rawContent.slice(2);
964
+ }
965
+ return rawContent; // fast path
966
+ }
967
+
920
968
  /**
921
969
  * Escapes closing comment tag in a comment content.
922
970
  *
923
971
  * For example, given `#comment('-->')`, the content of a comment would be
924
972
  * updated to `--&gt;` to avoid unexpected and unsafe behavior after
925
- * de-serialization.
973
+ * de-serialization. Content that abruptly closes an empty comment is
974
+ * escaped as well, see `escapeAbruptClosingCommentTag()`.
926
975
  */
927
976
  function escapeClosingCommentTag(rawContent) {
928
- if (!CLOSING_COMMENT_REGEXP.test(rawContent)) {
929
- return rawContent; // fast path
977
+ const content = escapeAbruptClosingCommentTag(rawContent);
978
+ if (!CLOSING_COMMENT_REGEXP.test(content)) {
979
+ return content; // fast path
930
980
  }
931
- return rawContent.replace(/(--\!?)>/g, '$1&gt;');
981
+ return content.replace(/(--\!?)>/g, '$1&gt;');
932
982
  }
933
983
 
934
984
  /**
@@ -962,8 +1012,12 @@ function requireNodeUtils () {
962
1012
  // If an element can have raw content, this content may
963
1013
  // potentially require escaping to avoid XSS.
964
1014
  var upperTag = tagname.toUpperCase();
965
- if (hasRawContent[upperTag] && !hasRawContentFallback[upperTag]) {
1015
+ if (hasRawContent[upperTag] && !hasRawContentFallback[upperTag] && ss.includes('</')) {
966
1016
  ss = escapeMatchingClosingTag(ss, tagname);
1017
+ const fallbackTags = fallbackRawContentTags(parent);
1018
+ for (const fallbackTag of fallbackTags) {
1019
+ ss = escapeMatchingClosingTag(ss, fallbackTag);
1020
+ }
967
1021
  }
968
1022
  if (html && extraNewLine[tagname] && ss.charAt(0)==='\n') s += '\n';
969
1023
  // Serialize children and add end tag for all others
@@ -983,22 +1037,33 @@ function requireNodeUtils () {
983
1037
  if (hasRawContent[parenttag]) {
984
1038
  // Preserve actual child element markup in fallback elements such as
985
1039
  // <noscript>, but do not emit text-node payloads as raw HTML.
986
- s += hasRawContentFallback[parenttag] ? escapeFallbackRawText(kid.data, parent.localName) : kid.data;
1040
+ s += hasRawContentFallback[parenttag] ? escapeFallbackRawText(kid.data, parent.localName, fallbackRawContentTags(parent.parentNode)) : kid.data;
987
1041
  } else {
988
1042
  s += escape(kid.data);
989
1043
  }
990
1044
  break;
991
1045
  case 8: //COMMENT_NODE
992
- s += '<!--' + escapeClosingCommentTag(kid.data) + '-->';
1046
+ let commentData = escapeClosingCommentTag(kid.data);
1047
+ if (commentData.includes('</')) {
1048
+ const fallbackTags = fallbackRawContentTags(parent);
1049
+ for (const fallbackTag of fallbackTags) {
1050
+ commentData = escapeMatchingClosingTag(commentData, fallbackTag);
1051
+ }
1052
+ }
1053
+ s += '<!--' + commentData + '-->';
993
1054
  break;
994
1055
  case 7: //PROCESSING_INSTRUCTION_NODE
995
- const content = escapeProcessingInstructionContent(kid.data);
1056
+ let content = escapeProcessingInstructionContent(kid.data);
1057
+ if (content.includes('</')) {
1058
+ const fallbackTags = fallbackRawContentTags(parent);
1059
+ for (const fallbackTag of fallbackTags) {
1060
+ content = escapeMatchingClosingTag(content, fallbackTag);
1061
+ }
1062
+ }
996
1063
  s += '<?' + kid.target + ' ' + content + '?>';
997
1064
  break;
998
1065
  case 10: //DOCUMENT_TYPE_NODE
999
- s += '<!DOCTYPE ' + kid.name;
1000
-
1001
- s += '>';
1066
+ s += '<!DOCTYPE ' + kid.name + '>';
1002
1067
  break;
1003
1068
  default:
1004
1069
  utils.InvalidStateError();
@@ -7588,7 +7653,7 @@ function requireHtmlelts () {
7588
7653
  required: Boolean,
7589
7654
  readOnly: Boolean,
7590
7655
  checked: Boolean,
7591
- value: String,
7656
+ value: { type: String, treatNullAsEmptyString: true },
7592
7657
  src: URL,
7593
7658
  defaultChecked: {name: 'checked', type: Boolean},
7594
7659
  size: {type: 'unsigned long', default: 20, min: 1, setmin: 1},
@@ -8111,10 +8176,21 @@ function requireHtmlelts () {
8111
8176
  ctor: function HTMLTemplateElement(doc, localName, prefix) {
8112
8177
  HTMLElement.call(this, doc, localName, prefix);
8113
8178
  this._contentFragment = doc._templateDoc.createDocumentFragment();
8179
+ this._contentFragment._host = this;
8114
8180
  },
8115
8181
  props: {
8116
8182
  content: { get: function() { return this._contentFragment; } },
8117
- serialize: { value: function() { return this.content.serialize(); } }
8183
+ serialize: { value: function() { return this.content.serialize(); } },
8184
+ cloneNode: {
8185
+ value: function(deep) {
8186
+ var clone = HTMLElement.prototype.cloneNode.call(this, deep);
8187
+ if (deep) {
8188
+ clone._contentFragment = this._contentFragment.cloneNode(true);
8189
+ clone._contentFragment._host = clone;
8190
+ }
8191
+ return clone;
8192
+ }
8193
+ }
8118
8194
  }
8119
8195
  });
8120
8196
 
@@ -14182,6 +14258,8 @@ function requireHTMLParser () {
14182
14258
  emitDoctype();
14183
14259
  break;
14184
14260
  case -1: // EOF
14261
+ // Lookahead states must consume the EOF marker explicitly.
14262
+ nextchar += 1;
14185
14263
  forcequirks();
14186
14264
  emitDoctype();
14187
14265
  emitEOF();