SvgUtils.java

1
package pro.verron.officestamper.utils.svg;
2
3
import org.w3c.dom.Document;
4
import org.xml.sax.SAXException;
5
import pro.verron.officestamper.utils.UtilsException;
6
7
import javax.xml.XMLConstants;
8
import javax.xml.parsers.DocumentBuilder;
9
import javax.xml.parsers.DocumentBuilderFactory;
10
import javax.xml.parsers.ParserConfigurationException;
11
import java.io.ByteArrayInputStream;
12
import java.io.IOException;
13
14
/// Utility class for working with SVG (Scalable Vector Graphics) documents.
15
/// Provides methods to parse SVG data securely, mitigating common security risks
16
/// such as XML External Entity (XXE) attacks.
17
public class SvgUtils {
18
19
    private static volatile boolean restrictedMode = true;
20
21
    private SvgUtils() {
22
        /* This utility class should not be instantiated */
23
    }
24
25
    /// Returns whether SVG parsing safe mode is enabled.
26
    ///
27
    /// When enabled (default), the parser is hardened against XXE/DTD and related attacks.
28
    public static boolean isRestrictedMode() {
29 2 1. isRestrictedMode : replaced boolean return with true for pro/verron/officestamper/utils/svg/SvgUtils::isRestrictedMode → SURVIVED
2. isRestrictedMode : replaced boolean return with false for pro/verron/officestamper/utils/svg/SvgUtils::isRestrictedMode → SURVIVED
        return restrictedMode;
30
    }
31
32
    /// Parse an SVG XML document from bytes with hardened XML parser settings.
33
    ///
34
    /// - Disables DTDs and external entity resolution to prevent XXE attacks
35
    /// - Enables secure processing
36
    /// - Disables XInclude and entity expansion
37
    ///
38
    /// @param bytes the SVG content as a UTF-8 encoded byte array
39
    /// @return the parsed DOM Document
40
    /// @throws UtilsException if parsing fails or the parser cannot be securely configured
41
    public static Document parseDocument(byte[] bytes) {
42
        var inputStream = new ByteArrayInputStream(bytes);
43
        try {
44 1 1. parseDocument : negated conditional → KILLED
            var documentBuilder = restrictedMode ? newRestrictedDocumentBuilder() : newPermissiveDocumentBuilder();
45 1 1. parseDocument : replaced return value with null for pro/verron/officestamper/utils/svg/SvgUtils::parseDocument → KILLED
            return documentBuilder.parse(inputStream);
46
        } catch (SAXException | IOException | ParserConfigurationException e) {
47
            throw new UtilsException("Failed to parse SVG document securely", e);
48
        }
49
    }
50
51
    private static DocumentBuilder newRestrictedDocumentBuilder()
52
            throws ParserConfigurationException {
53
        var factory = DocumentBuilderFactory.newInstance();
54
55
        // Namespace-aware parsing is generally recommended for SVG
56 1 1. newRestrictedDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setNamespaceAware → KILLED
        factory.setNamespaceAware(true);
57
58
        // Harden against XXE/DTD
59 1 1. newRestrictedDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
60
        // Disallow any DOCTYPE
61 1 1. newRestrictedDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
62
        // Prevent external entity resolution
63 1 1. newRestrictedDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
64 1 1. newRestrictedDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
65 1 1. newRestrictedDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
66
67
        // XInclude and entity expansion
68
        try {
69 1 1. newRestrictedDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setXIncludeAware → SURVIVED
            factory.setXIncludeAware(false);
70
        } catch (UnsupportedOperationException ignored) {
71
            // Some implementations may not support XInclude; safe to ignore
72
        }
73 1 1. newRestrictedDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setExpandEntityReferences → SURVIVED
        factory.setExpandEntityReferences(false);
74
75 1 1. newRestrictedDocumentBuilder : replaced return value with null for pro/verron/officestamper/utils/svg/SvgUtils::newRestrictedDocumentBuilder → KILLED
        return factory.newDocumentBuilder();
76
    }
77
78
    private static DocumentBuilder newPermissiveDocumentBuilder()
79
            throws ParserConfigurationException {
80
        var factory = DocumentBuilderFactory.newInstance();
81 1 1. newPermissiveDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setNamespaceAware → KILLED
        factory.setNamespaceAware(true);
82
        // Intentionally avoid setting hardened features; caller opted out of safe mode.
83
        // Keep entity expansion off to prevent excessive memory usage by default.
84
        try {
85 1 1. newPermissiveDocumentBuilder : removed call to javax/xml/parsers/DocumentBuilderFactory::setXIncludeAware → SURVIVED
            factory.setXIncludeAware(false);
86
        } catch (UnsupportedOperationException ignored) {
87
        }
88 1 1. newPermissiveDocumentBuilder : replaced return value with null for pro/verron/officestamper/utils/svg/SvgUtils::newPermissiveDocumentBuilder → KILLED
        return factory.newDocumentBuilder();
89
    }
90
91
    /// Enables SVG parsing safe mode.
92
    ///
93
    /// When safe mode is enabled, the SVG parser applies stricter security configurations to mitigate
94
    /// potential vulnerabilities, such as XML External Entity (XXE) or Document Type Definition (DTD) attacks.
95
    ///
96
    /// This method sets the internal flag to indicate that safe mode is active,
97
    /// affecting the behavior of SVG parsing methods in this utility class.
98
    public static void enableSafeMode() {
99
        restrictedMode = true;
100
    }
101
102
    /// Disables the safe mode for SVG parsing.
103
    ///
104
    /// When safe mode is disabled, the SVG parser applies relaxed security configurations,
105
    /// which may reintroduce vulnerabilities, such as XML External Entity (XXE) or Document Type Definition (DTD)
106
    ///  attacks.
107
    ///
108
    /// This method sets an internal flag to indicate that the safe mode is inactive,
109
    /// affecting the behavior of SVG parsing methods in this utility class.
110
    public static void disableSafeMode() {
111
        restrictedMode = false;
112
    }
113
}

Mutations

29

1.1
Location : isRestrictedMode
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/svg/SvgUtils::isRestrictedMode → SURVIVED
Covering tests

2.2
Location : isRestrictedMode
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/svg/SvgUtils::isRestrictedMode → SURVIVED Covering tests

44

1.1
Location : parseDocument
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:parsesInternalDoctypeWhenUnsafe()]
negated conditional → KILLED

45

1.1
Location : parseDocument
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:parsesInternalDoctypeWhenUnsafe()]
replaced return value with null for pro/verron/officestamper/utils/svg/SvgUtils::parseDocument → KILLED

56

1.1
Location : newRestrictedDocumentBuilder
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:parsesMinimalSvg()]
removed call to javax/xml/parsers/DocumentBuilderFactory::setNamespaceAware → KILLED

59

1.1
Location : newRestrictedDocumentBuilder
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
Covering tests

61

1.1
Location : newRestrictedDocumentBuilder
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:rejectsExternalEntityXXE()]
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED

63

1.1
Location : newRestrictedDocumentBuilder
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:rejectsExternalEntityXXE()]
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED

64

1.1
Location : newRestrictedDocumentBuilder
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:rejectsExternalEntityXXE()]
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED

65

1.1
Location : newRestrictedDocumentBuilder
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:rejectsExternalEntityXXE()]
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED

69

1.1
Location : newRestrictedDocumentBuilder
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setXIncludeAware → SURVIVED
Covering tests

73

1.1
Location : newRestrictedDocumentBuilder
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setExpandEntityReferences → SURVIVED
Covering tests

75

1.1
Location : newRestrictedDocumentBuilder
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:rejectsExternalEntityXXE()]
replaced return value with null for pro/verron/officestamper/utils/svg/SvgUtils::newRestrictedDocumentBuilder → KILLED

81

1.1
Location : newPermissiveDocumentBuilder
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:parsesInternalDoctypeWhenUnsafe()]
removed call to javax/xml/parsers/DocumentBuilderFactory::setNamespaceAware → KILLED

85

1.1
Location : newPermissiveDocumentBuilder
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setXIncludeAware → SURVIVED
Covering tests

88

1.1
Location : newPermissiveDocumentBuilder
Killed by : pro.verron.officestamper.utils.svg.SvgUtilsTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.svg.SvgUtilsTest]/[method:parsesInternalDoctypeWhenUnsafe()]
replaced return value with null for pro/verron/officestamper/utils/svg/SvgUtils::newPermissiveDocumentBuilder → KILLED

Active mutators

Tests examined


Report generated by PIT 1.23.1 support