77import org .slf4j .LoggerFactory ;
88
99import javax .xml .XMLConstants ;
10+ import javax .xml .parsers .DocumentBuilderFactory ;
11+ import javax .xml .parsers .ParserConfigurationException ;
1012import javax .xml .stream .FactoryConfigurationError ;
1113import javax .xml .stream .XMLInputFactory ;
1214import javax .xml .stream .XMLOutputFactory ;
1315import javax .xml .transform .TransformerConfigurationException ;
1416import javax .xml .transform .TransformerFactory ;
1517import java .lang .ref .SoftReference ;
18+ import java .util .function .Supplier ;
1619
1720public final class XmlFactories {
1821
1922 private static final Logger logger = LoggerFactory .getLogger (XmlFactories .class );
2023
2124 private static final CachedInstancePerThreadSupplier <XMLOutputFactory > cachedOutputFactory =
22- new CachedInstancePerThreadSupplier <XMLOutputFactory >(new Supplier <XMLOutputFactory >() {
23- @ Override
24- public XMLOutputFactory get () {
25- return makeNewOutputFactory ();
26- }
27- });
25+ new CachedInstancePerThreadSupplier <>(XmlFactories ::makeNewOutputFactory );
26+
27+ private static final CachedInstancePerThreadSupplier <DocumentBuilderFactory > cachedDocumentBuilderFactory =
28+ new CachedInstancePerThreadSupplier <>(XmlFactories ::makeNewDocumentBuilderFactory );
2829
2930 private XmlFactories () {} // preventing instances of utility class
3031
@@ -62,21 +63,78 @@ public static TransformerFactory makeNewTransformerFactory() {
6263 try {
6364 factory .setFeature (XMLConstants .FEATURE_SECURE_PROCESSING , true );
6465 } catch (TransformerConfigurationException e ) {
65- logger . warn ( "Unable to set {} on TransformerFactory; cause: {}" , XMLConstants .FEATURE_SECURE_PROCESSING , e .getMessage ());
66+ logTransformerWarning ( XMLConstants .FEATURE_SECURE_PROCESSING , e .getMessage ());
6667 }
6768 try {
6869 factory .setAttribute (XMLConstants .ACCESS_EXTERNAL_DTD , "" );
6970 } catch (IllegalArgumentException e ) {
70- logger . warn ( "Unable to set {} on TransformerFactory; cause: {}" , XMLConstants .ACCESS_EXTERNAL_DTD , e .getMessage ());
71+ logTransformerWarning ( XMLConstants .ACCESS_EXTERNAL_DTD , e .getMessage ());
7172 }
7273 try {
7374 factory .setAttribute (XMLConstants .ACCESS_EXTERNAL_STYLESHEET , "" );
7475 } catch (IllegalArgumentException e ) {
75- logger . warn ( "Unable to set {} on TransformerFactory; cause: {}" , XMLConstants .ACCESS_EXTERNAL_STYLESHEET , e .getMessage ());
76+ logTransformerWarning ( XMLConstants .ACCESS_EXTERNAL_STYLESHEET , e .getMessage ());
7677 }
7778 return factory ;
7879 }
7980
81+ private static void logTransformerWarning (String xmlConstant , String errorMessage ) {
82+ logger .warn ("Unable to set {} on TransformerFactory; cause: {}" , xmlConstant , errorMessage );
83+ }
84+
85+ private static DocumentBuilderFactory makeNewDocumentBuilderFactory () {
86+ DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance ();
87+ // Default to best practices for conservative security including recommendations per
88+ // https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.md
89+ try {
90+ factory .setFeature (XMLConstants .FEATURE_SECURE_PROCESSING , true );
91+ } catch (ParserConfigurationException e ) {
92+ logger .warn ("Unable to set FEATURE_SECURE_PROCESSING on DocumentBuilderFactory; cause: {}" , e .getMessage ());
93+ }
94+ try {
95+ factory .setFeature ("http://apache.org/xml/features/disallow-doctype-decl" , true );
96+ } catch (ParserConfigurationException e ) {
97+ logger .warn ("Unable to set disallow-doctype-decl on DocumentBuilderFactory; cause: {}" , e .getMessage ());
98+ }
99+ try {
100+ factory .setFeature ("http://xml.org/sax/features/external-general-entities" , false );
101+ } catch (ParserConfigurationException e ) {
102+ logger .warn ("Unable to set external-general-entities on DocumentBuilderFactory; cause: {}" , e .getMessage ());
103+ }
104+ try {
105+ factory .setFeature ("http://xml.org/sax/features/external-parameter-entities" , false );
106+ } catch (ParserConfigurationException e ) {
107+ logger .warn ("Unable to set external-parameter-entities on DocumentBuilderFactory; cause: {}" , e .getMessage ());
108+ }
109+ try {
110+ factory .setFeature ("http://apache.org/xml/features/nonvalidating/load-external-dtd" , false );
111+ } catch (ParserConfigurationException e ) {
112+ logger .warn ("Unable to set load-external-dtd on DocumentBuilderFactory; cause: {}" , e .getMessage ());
113+ }
114+ factory .setXIncludeAware (false );
115+ factory .setExpandEntityReferences (false );
116+ factory .setNamespaceAware (true );
117+ factory .setValidating (false );
118+
119+ return factory ;
120+ }
121+
122+ /**
123+ * Returns a shared {@link DocumentBuilderFactory} configured with secure defaults.
124+ * <p>
125+ * Creating XML factories is potentially a pretty expensive operation. Using a shared instance helps to amortize
126+ * this initialization cost via reuse.
127+ *
128+ * @return a securely configured {@link DocumentBuilderFactory}
129+ *
130+ * @since 8.1.0
131+ *
132+ * @see #makeNewDocumentBuilderFactory() if you really (really?) need a non-shared instance
133+ */
134+ public static DocumentBuilderFactory getDocumentBuilderFactory () {
135+ return cachedDocumentBuilderFactory .get ();
136+ }
137+
80138 /**
81139 * Returns a shared {@link XMLOutputFactory}. This factory will have its
82140 * {@link XMLOutputFactory#IS_REPAIRING_NAMESPACES} property set to {@code true}.
@@ -88,31 +146,12 @@ public static TransformerFactory makeNewTransformerFactory() {
88146 *
89147 * @throws FactoryConfigurationError see {@link XMLOutputFactory#newInstance()}
90148 *
91- * @see #makeNewOutputFactory() if you really (really?) need an non-shared instance
149+ * @see #makeNewOutputFactory() if you really (really?) need a non-shared instance
92150 */
93151 public static XMLOutputFactory getOutputFactory () {
94152 return cachedOutputFactory .get ();
95153 }
96154
97- /**
98- * Represents a supplier of results.
99- *
100- * <p>There is no requirement that a new or distinct result be returned each
101- * time the supplier is invoked.
102- *
103- * @param <T> the type of results supplied by this supplier
104- */
105- // TODO replace with java.util.function.Supplier<T> after Java 8 migration
106- interface Supplier <T > {
107-
108- /**
109- * Gets a result.
110- *
111- * @return a result
112- */
113- T get ();
114- }
115-
116155 /**
117156 * A supplier that caches results per thread.
118157 * <p>
@@ -129,7 +168,7 @@ interface Supplier<T> {
129168 */
130169 private static class CachedInstancePerThreadSupplier <T > implements Supplier <T > {
131170
132- private final ThreadLocal <SoftReference <T >> cachedInstances = new ThreadLocal <SoftReference < T > >();
171+ private final ThreadLocal <SoftReference <T >> cachedInstances = new ThreadLocal <>();
133172
134173 /**
135174 * The underlying supplier, invoked to originally retrieve the per-thread result
@@ -167,7 +206,7 @@ public T get() {
167206 }
168207
169208 // ... and retain it for later re-use
170- cachedInstances .set (new SoftReference <T >(cachedInstance ));
209+ cachedInstances .set (new SoftReference <>(cachedInstance ));
171210 }
172211
173212 return cachedInstance ;
0 commit comments