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

Mutations

28

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

43

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

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()]
replaced return value with null for pro/verron/officestamper/utils/svg/SvgUtils::parseDocument → KILLED

55

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

58

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

60

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

62

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

68

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

72

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

74

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

80

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

84

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

87

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.25.5 support