dompurify 2.2.8 → 2.3.2

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/README.md CHANGED
@@ -6,11 +6,11 @@
6
6
 
7
7
  DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
8
8
 
9
- It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version 2.2.8.
9
+ It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version 2.3.2.
10
10
 
11
11
  DOMPurify is written in JavaScript and works in all modern browsers (Safari (10+), Opera (15+), Internet Explorer (10+), Edge, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on MSIE6 or other legacy browsers. It either uses [a fall-back](#what-about-older-browsers-like-msie8) or simply does nothing.
12
12
 
13
- Our automated tests cover [15 different browsers](https://github.com/cure53/DOMPurify/blob/main/test/karma.custom-launchers.config.js#L5) right now, more to come. We also cover Node.js v14.15.1, v15.4.0, running DOMPurify on [jsdom](https://github.com/tmpvar/jsdom). Older Node.js versions are known to work as well.
13
+ Our automated tests cover [17 different browsers](https://github.com/cure53/DOMPurify/blob/main/test/karma.custom-launchers.config.js#L5) right now, more to come. We also cover Node.js v14.15.1, v15.4.0, running DOMPurify on [jsdom](https://github.com/tmpvar/jsdom). Older Node.js versions are known to work as well.
14
14
 
15
15
  DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model). Please, read it. Like, really.
16
16
 
@@ -337,7 +337,7 @@ Feature releases will not be announced to this list.
337
337
 
338
338
  Many people helped and help DOMPurify become what it is and need to be acknowledged here!
339
339
 
340
- [granlem 💸](https://twitter.com/MaximeVeit), [oreoshake 💸](https://github.com/oreoshake), [dcramer 💸](https://github.com/dcramer),[tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [neilj](https://github.com/neilj), [fhemberger](https://github.com/fhemberger), [Joris-van-der-Wel](https://github.com/Joris-van-der-Wel), [ydaniv](https://github.com/ydaniv), [terjanq](https://twitter.com/terjanq), [filedescriptor](https://github.com/filedescriptor), [ConradIrwin](https://github.com/ConradIrwin), [gibson042](https://github.com/gibson042), [choumx](https://github.com/choumx), [0xSobky](https://github.com/0xSobky), [styfle](https://github.com/styfle), [koto](https://github.com/koto), [tlau88](https://github.com/tlau88), [strugee](https://github.com/strugee), [oparoz](https://github.com/oparoz), [mathiasbynens](https://github.com/mathiasbynens), [edg2s](https://github.com/edg2s), [dnkolegov](https://github.com/dnkolegov), [dhardtke](https://github.com/dhardtke), [wirehead](https://github.com/wirehead), [thorn0](https://github.com/thorn0), [styu](https://github.com/styu), [mozfreddyb](https://github.com/mozfreddyb), [mikesamuel](https://github.com/mikesamuel), [jorangreef](https://github.com/jorangreef), [jimmyhchan](https://github.com/jimmyhchan), [jameydeorio](https://github.com/jameydeorio), [jameskraus](https://github.com/jameskraus), [hyderali](https://github.com/hyderali), [hansottowirtz](https://github.com/hansottowirtz), [hackvertor](https://github.com/hackvertor), [freddyb](https://github.com/freddyb), [flavorjones](https://github.com/flavorjones), [djfarrelly](https://github.com/djfarrelly), [devd](https://github.com/devd), [camerondunford](https://github.com/camerondunford), [buu700](https://github.com/buu700), [buildog](https://github.com/buildog), [alabiaga](https://github.com/alabiaga), [Vector919](https://github.com/Vector919), [Robbert](https://github.com/Robbert), [GreLI](https://github.com/GreLI), [FuzzySockets](https://github.com/FuzzySockets), [ArtemBernatskyy](https://github.com/ArtemBernatskyy), [@garethheyes](https://twitter.com/garethheyes), [@shafigullin](https://twitter.com/shafigullin), [@mmrupp](https://twitter.com/mmrupp), [@irsdl](https://twitter.com/irsdl),[ShikariSenpai](https://github.com/ShikariSenpai), [ansjdnakjdnajkd](https://github.com/ansjdnakjdnajkd), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro), [@CmdEngineer_](https://twitter.com/CmdEngineer_), [@avr4mit](https://twitter.com/avr4mit) and especially [@securitymb ❤️](https://twitter.com/securitymb) & [@masatokinugawa ❤️](https://twitter.com/masatokinugawa)
340
+ [lowdefy 💸](https://twitter.com/lowdefy), [granlem 💸](https://twitter.com/MaximeVeit), [oreoshake 💸](https://github.com/oreoshake), [dcramer 💸](https://github.com/dcramer),[tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [NateScarlet](https://github.com/NateScarlet), [neilj](https://github.com/neilj), [fhemberger](https://github.com/fhemberger), [Joris-van-der-Wel](https://github.com/Joris-van-der-Wel), [ydaniv](https://github.com/ydaniv), [terjanq](https://twitter.com/terjanq), [filedescriptor](https://github.com/filedescriptor), [ConradIrwin](https://github.com/ConradIrwin), [gibson042](https://github.com/gibson042), [choumx](https://github.com/choumx), [0xSobky](https://github.com/0xSobky), [styfle](https://github.com/styfle), [koto](https://github.com/koto), [tlau88](https://github.com/tlau88), [strugee](https://github.com/strugee), [oparoz](https://github.com/oparoz), [mathiasbynens](https://github.com/mathiasbynens), [edg2s](https://github.com/edg2s), [dnkolegov](https://github.com/dnkolegov), [dhardtke](https://github.com/dhardtke), [wirehead](https://github.com/wirehead), [thorn0](https://github.com/thorn0), [styu](https://github.com/styu), [mozfreddyb](https://github.com/mozfreddyb), [mikesamuel](https://github.com/mikesamuel), [jorangreef](https://github.com/jorangreef), [jimmyhchan](https://github.com/jimmyhchan), [jameydeorio](https://github.com/jameydeorio), [jameskraus](https://github.com/jameskraus), [hyderali](https://github.com/hyderali), [hansottowirtz](https://github.com/hansottowirtz), [hackvertor](https://github.com/hackvertor), [freddyb](https://github.com/freddyb), [flavorjones](https://github.com/flavorjones), [djfarrelly](https://github.com/djfarrelly), [devd](https://github.com/devd), [camerondunford](https://github.com/camerondunford), [buu700](https://github.com/buu700), [buildog](https://github.com/buildog), [alabiaga](https://github.com/alabiaga), [Vector919](https://github.com/Vector919), [Robbert](https://github.com/Robbert), [GreLI](https://github.com/GreLI), [FuzzySockets](https://github.com/FuzzySockets), [ArtemBernatskyy](https://github.com/ArtemBernatskyy), [@garethheyes](https://twitter.com/garethheyes), [@shafigullin](https://twitter.com/shafigullin), [@mmrupp](https://twitter.com/mmrupp), [@irsdl](https://twitter.com/irsdl),[ShikariSenpai](https://github.com/ShikariSenpai), [ansjdnakjdnajkd](https://github.com/ansjdnakjdnajkd), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro), [@CmdEngineer_](https://twitter.com/CmdEngineer_), [@avr4mit](https://twitter.com/avr4mit) and especially [@securitymb ❤️](https://twitter.com/securitymb) & [@masatokinugawa ❤️](https://twitter.com/masatokinugawa)
341
341
 
342
342
  ## Testing powered by
343
343
  <a target="_blank" href="https://www.browserstack.com/"><img width="200" src="https://www.browserstack.com/images/layout/browserstack-logo-600x315.png"></a><br>
@@ -1,4 +1,4 @@
1
- /*! @license DOMPurify | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.2.2/LICENSE */
1
+ /*! @license DOMPurify 2.3.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.2/LICENSE */
2
2
 
3
3
  'use strict';
4
4
 
@@ -245,7 +245,7 @@ function createDOMPurify() {
245
245
  * Version label, exposed for easier checks
246
246
  * if DOMPurify is up to date or not
247
247
  */
248
- DOMPurify.version = '2.2.8';
248
+ DOMPurify.version = '2.3.2';
249
249
 
250
250
  /**
251
251
  * Array of elements that DOMPurify removed during sanitation.
@@ -303,7 +303,8 @@ function createDOMPurify() {
303
303
  var _document = document,
304
304
  implementation = _document.implementation,
305
305
  createNodeIterator = _document.createNodeIterator,
306
- createDocumentFragment = _document.createDocumentFragment;
306
+ createDocumentFragment = _document.createDocumentFragment,
307
+ getElementsByTagName = _document.getElementsByTagName;
307
308
  var importNode = originalDocument.importNode;
308
309
 
309
310
 
@@ -410,7 +411,8 @@ function createDOMPurify() {
410
411
  var USE_PROFILES = {};
411
412
 
412
413
  /* Tags to ignore content of when KEEP_CONTENT is true */
413
- var FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
414
+ var FORBID_CONTENTS = null;
415
+ var DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
414
416
 
415
417
  /* Tags that are safe for data: URIs */
416
418
  var DATA_URI_TAGS = null;
@@ -418,13 +420,20 @@ function createDOMPurify() {
418
420
 
419
421
  /* Attributes safe for values like "javascript:" */
420
422
  var URI_SAFE_ATTRIBUTES = null;
421
- var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
423
+ var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
422
424
 
423
425
  var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
424
426
  var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
425
427
  var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
426
428
  /* Document namespace */
427
429
  var NAMESPACE = HTML_NAMESPACE;
430
+ var IS_EMPTY_INPUT = false;
431
+
432
+ /* Parsing of strict XHTML documents */
433
+ var PARSER_MEDIA_TYPE = void 0;
434
+ var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
435
+ var DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
436
+ var transformCaseFunc = void 0;
428
437
 
429
438
  /* Keep a reference to config to pass to hooks */
430
439
  var CONFIG = null;
@@ -458,6 +467,7 @@ function createDOMPurify() {
458
467
  ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
459
468
  URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES;
460
469
  DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS;
470
+ FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS) : DEFAULT_FORBID_CONTENTS;
461
471
  FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
462
472
  FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
463
473
  USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
@@ -475,7 +485,13 @@ function createDOMPurify() {
475
485
  KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
476
486
  IN_PLACE = cfg.IN_PLACE || false; // Default false
477
487
  IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
478
- NAMESPACE = cfg.NAMESPACE || NAMESPACE;
488
+ NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
489
+ PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE in SUPPORTED_PARSER_MEDIA_TYPES ? cfg.PARSER_MEDIA_TYPE : DEFAULT_PARSER_MEDIA_TYPE;
490
+ // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
491
+ transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? function (x) {
492
+ return x;
493
+ } : stringToLowerCase;
494
+
479
495
  if (SAFE_FOR_TEMPLATES) {
480
496
  ALLOW_DATA_ATTR = false;
481
497
  }
@@ -533,6 +549,14 @@ function createDOMPurify() {
533
549
  addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
534
550
  }
535
551
 
552
+ if (cfg.FORBID_CONTENTS) {
553
+ if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
554
+ FORBID_CONTENTS = clone(FORBID_CONTENTS);
555
+ }
556
+
557
+ addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS);
558
+ }
559
+
536
560
  /* Add #text in case KEEP_CONTENT is set to true */
537
561
  if (KEEP_CONTENT) {
538
562
  ALLOWED_TAGS['#text'] = true;
@@ -671,6 +695,7 @@ function createDOMPurify() {
671
695
  var _forceRemove = function _forceRemove(node) {
672
696
  arrayPush(DOMPurify.removed, { element: node });
673
697
  try {
698
+ // eslint-disable-next-line unicorn/prefer-dom-node-remove
674
699
  node.parentNode.removeChild(node);
675
700
  } catch (_) {
676
701
  try {
@@ -735,6 +760,11 @@ function createDOMPurify() {
735
760
  leadingWhitespace = matches && matches[0];
736
761
  }
737
762
 
763
+ if (PARSER_MEDIA_TYPE === 'application/xhtml+xml') {
764
+ // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
765
+ dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
766
+ }
767
+
738
768
  var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
739
769
  /*
740
770
  * Use the DOMParser API by default, fallback later if needs be
@@ -742,14 +772,18 @@ function createDOMPurify() {
742
772
  */
743
773
  if (NAMESPACE === HTML_NAMESPACE) {
744
774
  try {
745
- doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
775
+ doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
746
776
  } catch (_) {}
747
777
  }
748
778
 
749
779
  /* Use createHTMLDocument in case DOMParser is not available */
750
780
  if (!doc || !doc.documentElement) {
751
781
  doc = implementation.createDocument(NAMESPACE, 'template', null);
752
- doc.documentElement.innerHTML = dirtyPayload;
782
+ try {
783
+ doc.documentElement.innerHTML = IS_EMPTY_INPUT ? '' : dirtyPayload;
784
+ } catch (_) {
785
+ // Syntax error if dirtyPayload is invalid xml
786
+ }
753
787
  }
754
788
 
755
789
  var body = doc.body || doc.documentElement;
@@ -759,6 +793,10 @@ function createDOMPurify() {
759
793
  }
760
794
 
761
795
  /* Work on whole document or just its body */
796
+ if (NAMESPACE === HTML_NAMESPACE) {
797
+ return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
798
+ }
799
+
762
800
  return WHOLE_DOCUMENT ? doc.documentElement : body;
763
801
  };
764
802
 
@@ -769,9 +807,7 @@ function createDOMPurify() {
769
807
  * @return {Iterator} iterator instance
770
808
  */
771
809
  var _createIterator = function _createIterator(root) {
772
- return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, function () {
773
- return NodeFilter.FILTER_ACCEPT;
774
- }, false);
810
+ return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
775
811
  };
776
812
 
777
813
  /**
@@ -849,7 +885,7 @@ function createDOMPurify() {
849
885
  }
850
886
 
851
887
  /* Now let's check the element's type and name */
852
- var tagName = stringToLowerCase(currentNode.nodeName);
888
+ var tagName = transformCaseFunc(currentNode.nodeName);
853
889
 
854
890
  /* Execute a hook if present */
855
891
  _executeHook('uponSanitizeElement', currentNode, {
@@ -863,6 +899,12 @@ function createDOMPurify() {
863
899
  return true;
864
900
  }
865
901
 
902
+ /* Mitigate a problem with templates inside select */
903
+ if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) {
904
+ _forceRemove(currentNode);
905
+ return true;
906
+ }
907
+
866
908
  /* Remove element if anything forbids its presence */
867
909
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
868
910
  /* Keep content except for bad-listed elements */
@@ -931,7 +973,7 @@ function createDOMPurify() {
931
973
  (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
932
974
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
933
975
  We don't need to check the value; it's always URI safe. */
934
- if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
976
+ if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
935
977
  return false;
936
978
 
937
979
  /* Check value is safe. First, is attr inert? If so, is safe */
@@ -984,7 +1026,7 @@ function createDOMPurify() {
984
1026
  namespaceURI = _attr.namespaceURI;
985
1027
 
986
1028
  value = stringTrim(attr.value);
987
- lcName = stringToLowerCase(name);
1029
+ lcName = transformCaseFunc(name);
988
1030
 
989
1031
  /* Execute a hook if present */
990
1032
  hookEvent.attrName = lcName;
@@ -1019,7 +1061,7 @@ function createDOMPurify() {
1019
1061
  }
1020
1062
 
1021
1063
  /* Is `value` valid for this attribute? */
1022
- var lcTag = currentNode.nodeName.toLowerCase();
1064
+ var lcTag = transformCaseFunc(currentNode.nodeName);
1023
1065
  if (!_isValidAttribute(lcTag, lcName, value)) {
1024
1066
  continue;
1025
1067
  }
@@ -1092,7 +1134,8 @@ function createDOMPurify() {
1092
1134
  /* Make sure we have a string to sanitize.
1093
1135
  DO NOT return early, as this will return the wrong type if
1094
1136
  the user has requested a DOM object rather than a string */
1095
- if (!dirty) {
1137
+ IS_EMPTY_INPUT = !dirty;
1138
+ if (IS_EMPTY_INPUT) {
1096
1139
  dirty = '<!-->';
1097
1140
  }
1098
1141
 
@@ -1148,7 +1191,7 @@ function createDOMPurify() {
1148
1191
  } else if (importedNode.nodeName === 'HTML') {
1149
1192
  body = importedNode;
1150
1193
  } else {
1151
- // eslint-disable-next-line unicorn/prefer-node-append
1194
+ // eslint-disable-next-line unicorn/prefer-dom-node-append
1152
1195
  body.appendChild(importedNode);
1153
1196
  }
1154
1197
  } else {
@@ -1212,7 +1255,7 @@ function createDOMPurify() {
1212
1255
  returnNode = createDocumentFragment.call(body.ownerDocument);
1213
1256
 
1214
1257
  while (body.firstChild) {
1215
- // eslint-disable-next-line unicorn/prefer-node-append
1258
+ // eslint-disable-next-line unicorn/prefer-dom-node-append
1216
1259
  returnNode.appendChild(body.firstChild);
1217
1260
  }
1218
1261
  } else {
@@ -1281,8 +1324,8 @@ function createDOMPurify() {
1281
1324
  _parseConfig({});
1282
1325
  }
1283
1326
 
1284
- var lcTag = stringToLowerCase(tag);
1285
- var lcName = stringToLowerCase(attr);
1327
+ var lcTag = transformCaseFunc(tag);
1328
+ var lcName = transformCaseFunc(attr);
1286
1329
  return _isValidAttribute(lcTag, lcName, value);
1287
1330
  };
1288
1331