@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.
- package/dist/node/scratch-svg-renderer.js +113 -49
- package/dist/node/scratch-svg-renderer.js.map +1 -1
- package/dist/web/scratch-svg-renderer.js +16637 -12758
- package/dist/web/scratch-svg-renderer.js.map +1 -1
- package/package.json +5 -5
- package/src/sanitize-svg.js +105 -58
|
@@ -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
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
-
|
|
775
|
-
|
|
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
|
-
|
|
792
|
-
|
|
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" ***!
|