@scratch/scratch-svg-renderer 12.7.0-spork.5 → 12.7.1

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.
@@ -723,38 +723,93 @@ const {
723
723
  parse,
724
724
  walk
725
725
  } = __webpack_require__(/*! css-tree */ "css-tree");
726
+ const {
727
+ ident
728
+ } = __webpack_require__(/*! css-tree/utils */ "css-tree/utils");
726
729
  const DOMPurify = __webpack_require__(/*! isomorphic-dompurify */ "isomorphic-dompurify");
727
730
  const sanitizeSvg = {};
728
- const isInternalRef = ref => ref.startsWith('#') || ref.startsWith('data:');
729
- DOMPurify.addHook('beforeSanitizeAttributes', currentNode => {
730
- if (currentNode && currentNode.href && currentNode.href.baseVal) {
731
- const href = currentNode.href.baseVal.replace(/\s/g, '');
732
- // "data:" and "#" are valid hrefs
733
- if (!isInternalRef(href)) {
734
- // TODO: Those can be in different namespaces than `xlink:`
735
- if (currentNode.attributes.getNamedItem('xlink:href')) {
736
- currentNode.attributes.removeNamedItem('xlink:href');
737
- delete currentNode['xlink:href'];
738
- }
739
- if (currentNode.attributes.getNamedItem('href')) {
740
- currentNode.attributes.removeNamedItem('href');
741
- delete currentNode.href;
731
+ const isInternalRef = ref => ref.startsWith('#') || ref.toLowerCase().startsWith('data:');
732
+
733
+ /**
734
+ * Check if raw CSS text contains an external url() reference via regex.
735
+ * Used for Raw nodes (e.g. custom property values) that css-tree doesn't fully parse.
736
+ * @param {string} text - raw CSS text to check
737
+ * @returns {boolean} true if an external url() reference was found
738
+ */
739
+ const rawTextHasExternalUrls = text => {
740
+ const normalized = text.toLowerCase().replace(/\s/g, '');
741
+ const urlPattern = /url\((.+?)\)/g;
742
+ let match;
743
+ while ((match = urlPattern.exec(normalized)) !== null) {
744
+ const ref = match[1].replace(/['"]/g, '');
745
+ if (!isInternalRef(ref)) return true;
746
+ }
747
+ return false;
748
+ };
749
+
750
+ /**
751
+ * Walk a css-tree AST and return true if any Url node references an external resource.
752
+ * Also checks Raw nodes, which css-tree produces for custom property values and other
753
+ * unparsed content that could still contain url() references.
754
+ * @param {import('css-tree').CssNode} ast - The CSS tree or subtree to walk
755
+ * @returns {boolean} True if an external url() reference was found
756
+ */
757
+ const astHasExternalUrls = ast => {
758
+ let found = false;
759
+ walk(ast, node => {
760
+ if (node.type === 'Url') {
761
+ const urlValue = node.value.trim().replace(/['"]/g, '');
762
+ if (!isInternalRef(urlValue)) {
763
+ found = true;
742
764
  }
743
765
  }
766
+ if (node.type === 'Raw' && rawTextHasExternalUrls(node.value)) {
767
+ found = true;
768
+ }
769
+ });
770
+ return found;
771
+ };
772
+
773
+ /**
774
+ * Canonicalize a CSS string and check it for external url() references.
775
+ * Canonicalization: decode CSS escapes, then parse through css-tree so that all syntax
776
+ * variations (quoting, whitespace, comments, escapes) are normalized into AST nodes.
777
+ * @param {string} cssText - raw CSS text
778
+ * @param {string} parseContext - css-tree parse context: 'value' for a single CSS value
779
+ * (presentation attributes like fill, stroke), or 'declarationList' for style attributes.
780
+ * @returns {boolean} true if an external url() reference was found
781
+ */
782
+ const cssHasExternalUrls = (cssText, parseContext) => {
783
+ const decoded = ident.decode(cssText);
784
+ try {
785
+ return astHasExternalUrls(parse(decoded, {
786
+ context: parseContext
787
+ }));
788
+ } catch (_unused) {
789
+ // If css-tree can't parse it, conservatively check the decoded text.
790
+ // This handles edge cases where creative syntax breaks the parser but
791
+ // a browser might still interpret a url() call.
792
+ return rawTextHasExternalUrls(decoded);
744
793
  }
794
+ };
745
795
 
746
- // Remove url(...) usages with external references
747
- if (currentNode && currentNode.attributes) {
748
- for (let i = currentNode.attributes.length - 1; i >= 0; i--) {
749
- const attr = currentNode.attributes[i];
750
- const rawValue = attr.value || '';
751
- const value = rawValue.toLowerCase().replace(/\s/g, '');
752
- const urlMatch = value.match(/url\((.+?)\)/);
753
- if (urlMatch) {
754
- const ref = urlMatch[1].replace(/['"]/g, '');
755
- if (!isInternalRef(ref)) {
756
- currentNode.removeAttribute(attr.name);
757
- }
796
+ // Attributes that directly reference a URI (not via CSS url())
797
+ const URI_ATTRIBUTES = new Set(['href', 'xlink:href']);
798
+ DOMPurify.addHook('beforeSanitizeAttributes', currentNode => {
799
+ if (!currentNode || !currentNode.attributes) return currentNode;
800
+ for (let i = currentNode.attributes.length - 1; i >= 0; i--) {
801
+ const attr = currentNode.attributes[i];
802
+ if (!attr.value) continue;
803
+ if (URI_ATTRIBUTES.has(attr.name)) {
804
+ // Direct URI: strip whitespace and check
805
+ if (!isInternalRef(attr.value.replace(/\s/g, ''))) {
806
+ currentNode.removeAttribute(attr.name);
807
+ }
808
+ } else {
809
+ // CSS value that might contain url()
810
+ const context = attr.name === 'style' ? 'declarationList' : 'value';
811
+ if (cssHasExternalUrls(attr.value, context)) {
812
+ currentNode.removeAttribute(attr.name);
758
813
  }
759
814
  }
760
815
  }
@@ -762,34 +817,32 @@ DOMPurify.addHook('beforeSanitizeAttributes', currentNode => {
762
817
  });
763
818
  DOMPurify.addHook('uponSanitizeElement', (node, data) => {
764
819
  if (data.tagName === 'style') {
765
- const ast = parse(node.textContent);
766
- let isModified = false;
767
- walk(ast, (astNode, item, list) => {
768
- // @import rules
769
- if (astNode.type === 'Atrule' && astNode.name.toLowerCase() === 'import') {
770
- list.remove(item);
771
- isModified = true;
772
- }
820
+ try {
821
+ // Canonicalize: decode CSS escapes then parse, so css-tree sees
822
+ // normalized tokens (e.g. \75\72\6c becomes url).
823
+ const decodedCss = ident.decode(node.textContent);
824
+ const ast = parse(decodedCss);
825
+ let isModified = decodedCss !== node.textContent;
826
+ walk(ast, (astNode, item, list) => {
827
+ // @import rules
828
+ if (astNode.type === 'Atrule' && astNode.name.toLowerCase() === 'import') {
829
+ list.remove(item);
830
+ isModified = true;
831
+ }
773
832
 
774
- // Elements using url(...) for external resources
775
- if (astNode.type === 'Declaration' && astNode.value) {
776
- let shouldRemove = false;
777
- walk(astNode.value, valueNode => {
778
- if (valueNode.type === 'Url') {
779
- const urlValue = (valueNode.value.value || '').trim().replace(/['"]/g, '');
780
- if (!isInternalRef(urlValue)) {
781
- shouldRemove = true;
782
- }
783
- }
784
- });
785
- if (shouldRemove) {
833
+ // Declarations using url(...) for external resources
834
+ if (astNode.type === 'Declaration' && astNode.value && astHasExternalUrls(astNode.value)) {
786
835
  list.remove(item);
787
836
  isModified = true;
788
837
  }
838
+ });
839
+ if (isModified) {
840
+ node.textContent = generate(ast);
789
841
  }
790
- });
791
- if (isModified) {
792
- node.textContent = generate(ast);
842
+ } catch (_unused2) {
843
+ // If CSS parsing fails, remove the style content entirely
844
+ // rather than risk passing through unsanitized CSS.
845
+ node.textContent = '';
793
846
  }
794
847
  }
795
848
  });
@@ -1811,6 +1864,17 @@ module.exports = require("css-tree");
1811
1864
 
1812
1865
  /***/ },
1813
1866
 
1867
+ /***/ "css-tree/utils"
1868
+ /*!*********************************!*\
1869
+ !*** external "css-tree/utils" ***!
1870
+ \*********************************/
1871
+ (module) {
1872
+
1873
+ "use strict";
1874
+ module.exports = require("css-tree/utils");
1875
+
1876
+ /***/ },
1877
+
1814
1878
  /***/ "fastestsmallesttextencoderdecoder"
1815
1879
  /*!****************************************************!*\
1816
1880
  !*** external "fastestsmallesttextencoderdecoder" ***!