package org.apache.poi.ooxml.util;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
public final class SAXHelper {
private static final POILogger logger = POILogFactory.getLogger(SAXHelper.class);
private static long lastLog;
private SAXHelper() {}
public static XMLReader newXMLReader() throws SAXException, ParserConfigurationException {
XMLReader xmlReader = saxFactory.newSAXParser().getXMLReader();
xmlReader.setEntityResolver(IGNORING_ENTITY_RESOLVER);
trySetSAXFeature(xmlReader, XMLConstants.FEATURE_SECURE_PROCESSING);
trySetXercesSecurityManager(xmlReader);
return xmlReader;
}
static final EntityResolver IGNORING_ENTITY_RESOLVER = (publicId, systemId) -> new InputSource(new StringReader(""));
private static final SAXParserFactory saxFactory;
static {
try {
saxFactory = SAXParserFactory.newInstance();
saxFactory.setValidating(false);
saxFactory.setNamespaceAware(true);
trySetSAXFeature(saxFactory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
trySetSAXFeature(saxFactory, POIXMLConstants.FEATURE_LOAD_DTD_GRAMMAR, false);
trySetSAXFeature(saxFactory, POIXMLConstants.FEATURE_LOAD_EXTERNAL_DTD, false);
} catch (RuntimeException | Error re) {
logger.log(POILogger.WARN, "Failed to create SAXParserFactory", re);
throw re;
} catch (Exception e) {
logger.log(POILogger.WARN, "Failed to create SAXParserFactory", e);
throw new RuntimeException("Failed to create SAXParserFactory", e);
}
}
private static void trySetSAXFeature(@SuppressWarnings("SameParameterValue") SAXParserFactory spf,
String feature, boolean flag) {
try {
spf.setFeature(feature, flag);
} catch (Exception e) {
logger.log(POILogger.WARN, "SAX Feature unsupported", feature, e);
} catch (AbstractMethodError ame) {
logger.log(POILogger.WARN, "Cannot set SAX feature because outdated XML parser in classpath", feature, ame);
}
}
private static void trySetSAXFeature(XMLReader xmlReader, @SuppressWarnings("SameParameterValue") String feature) {
try {
xmlReader.setFeature(feature, true);
} catch (Exception e) {
logger.log(POILogger.WARN, "SAX Feature unsupported", feature, e);
} catch (AbstractMethodError ame) {
logger.log(POILogger.WARN, "Cannot set SAX feature because outdated XML parser in classpath", feature, ame);
}
}
private static void trySetXercesSecurityManager(XMLReader xmlReader) {
for (String securityManagerClassName : new String[] {
"org.apache.xerces.util.SecurityManager"
}) {
try {
Object mgr = Class.forName(securityManagerClassName).newInstance();
Method setLimit = mgr.getClass().getMethod("setEntityExpansionLimit", Integer.TYPE);
setLimit.invoke(mgr, 1);
xmlReader.setProperty(POIXMLConstants.PROPERTY_SECURITY_MANAGER, mgr);
return;
} catch (ClassNotFoundException e) {
} catch (Throwable e) {
if(System.currentTimeMillis() > lastLog + TimeUnit.MINUTES.toMillis(5)) {
logger.log(POILogger.WARN, "SAX Security Manager could not be setup [log suppressed for 5 minutes]", e);
lastLog = System.currentTimeMillis();
}
}
}
try {
xmlReader.setProperty(POIXMLConstants.PROPERTY_ENTITY_EXPANSION_LIMIT, 1);
} catch (SAXException e) {
if(System.currentTimeMillis() > lastLog + TimeUnit.MINUTES.toMillis(5)) {
logger.log(POILogger.WARN, "SAX Security Manager could not be setup [log suppressed for 5 minutes]", e);
lastLog = System.currentTimeMillis();
}
}
}
}