diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/XhtmlDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/XhtmlDt.java index 66594a4859f..b22008a078b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/XhtmlDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/XhtmlDt.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.model.api.BasePrimitive; import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.SimpleSetter; import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.util.XmlDetectionUtil; import ca.uhn.fhir.util.XmlUtil; import java.util.List; @@ -77,7 +78,7 @@ public class XhtmlDt extends BasePrimitive { @Override protected String parse(String theValue) { - if (XmlUtil.isStaxPresent()) { + if (XmlDetectionUtil.isStaxPresent()) { // for validation XmlUtil.parse(theValue); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlDetectionUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlDetectionUtil.java new file mode 100644 index 00000000000..4619418d7c8 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlDetectionUtil.java @@ -0,0 +1,57 @@ +package ca.uhn.fhir.util; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2017 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Reader; +import java.io.StringReader; + +public class XmlDetectionUtil { + + private static final Logger ourLog = LoggerFactory.getLogger(XmlDetectionUtil.class); + private static Boolean ourStaxPresent; + + /** + * This method will return true if a StAX XML parsing library is present + * on the classpath + */ + public static boolean isStaxPresent() { + Boolean retVal = ourStaxPresent; + if (retVal == null) { + try { + Class.forName("javax.xml.stream.events.XMLEvent"); + Class xmlUtilClazz = Class.forName("ca.uhn.fhir.util.XmlUtil"); + xmlUtilClazz.getMethod("createXmlReader", Reader.class).invoke(xmlUtilClazz, new StringReader("")); + ourStaxPresent = Boolean.TRUE; + retVal = Boolean.TRUE; + } catch (Throwable t) { + ourLog.info("StAX not detected on classpath, XML processing will be disabled"); + ourStaxPresent = Boolean.FALSE; + retVal = Boolean.FALSE; + } + } + return retVal; + } + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java index a69f27a889d..301bdfe42ba 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java @@ -46,15 +46,14 @@ import static org.apache.commons.lang3.StringUtils.isBlank; * This class contains code adapted from the Apache Axiom project. */ public class XmlUtil { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlUtil.class); + private static final Map VALID_ENTITY_NAMES; + private static final ExtendedEntityReplacingXmlResolver XML_RESOLVER = new ExtendedEntityReplacingXmlResolver(); private static XMLOutputFactory ourFragmentOutputFactory; private static volatile boolean ourHaveLoggedStaxImplementation; private static volatile XMLInputFactory ourInputFactory; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlUtil.class); private static Throwable ourNextException; private static volatile XMLOutputFactory ourOutputFactory; - private static Boolean ourStaxPresent; - private static final Map VALID_ENTITY_NAMES; - private static final ExtendedEntityReplacingXmlResolver XML_RESOLVER = new ExtendedEntityReplacingXmlResolver(); static { HashMap validEntityNames = new HashMap(1448); @@ -1510,6 +1509,207 @@ public class XmlUtil { VALID_ENTITY_NAMES = Collections.unmodifiableMap(validEntityNames); } + private static XMLOutputFactory createOutputFactory() throws FactoryConfigurationError { + try { + // Detect if we're running with the Android lib, and force repackaged Woodstox to be used + Class.forName("ca.uhn.fhir.repackage.javax.xml.stream.XMLOutputFactory"); + System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); + } catch (ClassNotFoundException e) { + // ok + } + + XMLOutputFactory outputFactory = newOutputFactory(); + + if (!ourHaveLoggedStaxImplementation) { + logStaxImplementation(outputFactory.getClass()); + } + + /* + * Note that these properties are Woodstox specific and they cause a crash in environments where SJSXP is + * being used (e.g. glassfish) so we don't set them there. + */ + try { + Class.forName("com.ctc.wstx.stax.WstxOutputFactory"); + if (outputFactory instanceof WstxOutputFactory) { + outputFactory.setProperty(XMLOutputFactory2.P_TEXT_ESCAPER, new MyEscaper()); + } + } catch (ClassNotFoundException e) { + ourLog.debug("WstxOutputFactory (Woodstox) not found on classpath"); + } + return outputFactory; + } + + public static XMLEventWriter createXmlFragmentWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException { + XMLOutputFactory outputFactory = getOrCreateFragmentOutputFactory(); + XMLEventWriter retVal = outputFactory.createXMLEventWriter(theWriter); + return retVal; + } + + public static XMLEventReader createXmlReader(Reader reader) throws FactoryConfigurationError, XMLStreamException { + throwUnitTestExceptionIfConfiguredToDoSo(); + + XMLInputFactory inputFactory = getOrCreateInputFactory(); + + // Now.. create the reader and return it + XMLEventReader er = inputFactory.createXMLEventReader(reader); + return er; + } + + public static XMLStreamWriter createXmlStreamWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException { + throwUnitTestExceptionIfConfiguredToDoSo(); + + XMLOutputFactory outputFactory = getOrCreateOutputFactory(); + XMLStreamWriter retVal = outputFactory.createXMLStreamWriter(theWriter); + return retVal; + } + + public static XMLEventWriter createXmlWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException { + XMLOutputFactory outputFactory = getOrCreateOutputFactory(); + XMLEventWriter retVal = outputFactory.createXMLEventWriter(theWriter); + return retVal; + } + + /** + * Encode a set of StAX events into a String + */ + public static String encode(List theEvents) { + try { + StringWriter w = new StringWriter(); + XMLEventWriter ew = XmlUtil.createXmlFragmentWriter(w); + + for (XMLEvent next : theEvents) { + if (next.isCharacters()) { + ew.add(next); + } else { + ew.add(next); + } + } + ew.close(); + return w.toString(); + } catch (XMLStreamException e) { + throw new DataFormatException("Problem with the contained XML events", e); + } catch (FactoryConfigurationError e) { + throw new ConfigurationException(e); + } + } + + private static XMLOutputFactory getOrCreateFragmentOutputFactory() throws FactoryConfigurationError { + XMLOutputFactory retVal = ourFragmentOutputFactory; + if (retVal == null) { + retVal = createOutputFactory(); + retVal.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE); + ourFragmentOutputFactory = retVal; + return retVal; + } + return retVal; + } + + private static XMLInputFactory getOrCreateInputFactory() throws FactoryConfigurationError { + if (ourInputFactory == null) { + + try { + // Detect if we're running with the Android lib, and force repackaged Woodstox to be used + Class.forName("ca.uhn.fhir.repackage.javax.xml.stream.XMLInputFactory"); + System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory"); + } catch (ClassNotFoundException e) { + // ok + } + + XMLInputFactory inputFactory = newInputFactory(); + + if (!ourHaveLoggedStaxImplementation) { + logStaxImplementation(inputFactory.getClass()); + } + + /* + * These two properties disable external entity processing, which can + * be a security vulnerability. + * + * See https://github.com/jamesagnew/hapi-fhir/issues/339 + * https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing + */ + inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory + inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities + + + /* + * In the following few lines, you can uncomment the first and comment the second to disable automatic + * parsing of extended entities, e.g. § + * + * Note that these properties are Woodstox specific and they cause a crash in environments where SJSXP is + * being used (e.g. glassfish) so we don't set them there. + */ + try { + Class.forName("com.ctc.wstx.stax.WstxInputFactory"); + boolean isWoodstox = inputFactory instanceof com.ctc.wstx.stax.WstxInputFactory; + if ( !isWoodstox ) + { + // Check if implementation is woodstox by property since instanceof check does not work if running in JBoss + try + { + isWoodstox = inputFactory.getProperty( "org.codehaus.stax2.implVersion" ) != null; + } + catch ( Exception e ) + { + // ignore + } + } + if (isWoodstox) { + // inputFactory.setProperty(WstxInputFactory.IS_REPLACING_ENTITY_REFERENCES, false); + inputFactory.setProperty(WstxInputProperties.P_UNDECLARED_ENTITY_RESOLVER, XML_RESOLVER); + try { + inputFactory.setProperty(WstxInputProperties.P_MAX_ATTRIBUTE_SIZE, "100000000"); + } catch (IllegalArgumentException e) { + // ignore + } + } + } catch (ClassNotFoundException e) { + ourLog.debug("WstxOutputFactory (Woodstox) not found on classpath"); + } + ourInputFactory = inputFactory; + } + return ourInputFactory; + } + + private static XMLOutputFactory getOrCreateOutputFactory() throws FactoryConfigurationError { + if (ourOutputFactory == null) { + ourOutputFactory = createOutputFactory(); + } + return ourOutputFactory; + } + + + private static void logStaxImplementation(Class theClass) { + IDependencyLog logger = DependencyLogFactory.createJarLogger(); + if (logger != null) { + logger.logStaxImplementation(theClass); + } + ourHaveLoggedStaxImplementation = true; + } + + + static XMLInputFactory newInputFactory() throws FactoryConfigurationError { + XMLInputFactory inputFactory; + try { + inputFactory = XMLInputFactory.newInstance(); + throwUnitTestExceptionIfConfiguredToDoSo(); + } catch (Throwable e) { + throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e); + } + return inputFactory; + } + + static XMLOutputFactory newOutputFactory() throws FactoryConfigurationError { + XMLOutputFactory outputFactory; + try { + outputFactory = XMLOutputFactory.newInstance(); + throwUnitTestExceptionIfConfiguredToDoSo(); + } catch (Throwable e) { + throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e); + } + return outputFactory; + } + /** * Parses an XML string into a set of StAX events */ @@ -1553,207 +1753,6 @@ public class XmlUtil { } } - /** - * Encode a set of StAX events into a String - */ - public static String encode(List theEvents) { - try { - StringWriter w = new StringWriter(); - XMLEventWriter ew = XmlUtil.createXmlFragmentWriter(w); - - for (XMLEvent next : theEvents) { - if (next.isCharacters()) { - ew.add(next); - } else { - ew.add(next); - } - } - ew.close(); - return w.toString(); - } catch (XMLStreamException e) { - throw new DataFormatException("Problem with the contained XML events", e); - } catch (FactoryConfigurationError e) { - throw new ConfigurationException(e); - } - } - - - private static XMLOutputFactory createOutputFactory() throws FactoryConfigurationError { - try { - // Detect if we're running with the Android lib, and force repackaged Woodstox to be used - Class.forName("ca.uhn.fhir.repackage.javax.xml.stream.XMLOutputFactory"); - System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); - } catch (ClassNotFoundException e) { - // ok - } - - XMLOutputFactory outputFactory = newOutputFactory(); - - if (!ourHaveLoggedStaxImplementation) { - logStaxImplementation(outputFactory.getClass()); - } - - /* - * Note that these properties are Woodstox specific and they cause a crash in environments where SJSXP is - * being used (e.g. glassfish) so we don't set them there. - */ - try { - Class.forName("com.ctc.wstx.stax.WstxOutputFactory"); - if (outputFactory instanceof WstxOutputFactory) { - outputFactory.setProperty(XMLOutputFactory2.P_TEXT_ESCAPER, new MyEscaper()); - } - } catch (ClassNotFoundException e) { - ourLog.debug("WstxOutputFactory (Woodstox) not found on classpath"); - } - return outputFactory; - } - - public static XMLEventWriter createXmlFragmentWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException { - XMLOutputFactory outputFactory = getOrCreateFragmentOutputFactory(); - XMLEventWriter retVal = outputFactory.createXMLEventWriter(theWriter); - return retVal; - } - - public static XMLEventReader createXmlReader(Reader reader) throws FactoryConfigurationError, XMLStreamException { - throwUnitTestExceptionIfConfiguredToDoSo(); - - XMLInputFactory inputFactory = getOrCreateInputFactory(); - - // Now.. create the reader and return it - XMLEventReader er = inputFactory.createXMLEventReader(reader); - return er; - } - - public static XMLStreamWriter createXmlStreamWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException { - throwUnitTestExceptionIfConfiguredToDoSo(); - - XMLOutputFactory outputFactory = getOrCreateOutputFactory(); - XMLStreamWriter retVal = outputFactory.createXMLStreamWriter(theWriter); - return retVal; - } - - public static XMLEventWriter createXmlWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException { - XMLOutputFactory outputFactory = getOrCreateOutputFactory(); - XMLEventWriter retVal = outputFactory.createXMLEventWriter(theWriter); - return retVal; - } - - private static XMLOutputFactory getOrCreateFragmentOutputFactory() throws FactoryConfigurationError { - XMLOutputFactory retVal = ourFragmentOutputFactory; - if (retVal == null) { - retVal = createOutputFactory(); - retVal.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE); - ourFragmentOutputFactory = retVal; - return retVal; - } - return retVal; - } - - private static XMLInputFactory getOrCreateInputFactory() throws FactoryConfigurationError { - if (ourInputFactory == null) { - - try { - // Detect if we're running with the Android lib, and force repackaged Woodstox to be used - Class.forName("ca.uhn.fhir.repackage.javax.xml.stream.XMLInputFactory"); - System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory"); - } catch (ClassNotFoundException e) { - // ok - } - - XMLInputFactory inputFactory = newInputFactory(); - - if (!ourHaveLoggedStaxImplementation) { - logStaxImplementation(inputFactory.getClass()); - } - - /* - * These two properties disable external entity processing, which can - * be a security vulnerability. - * - * See https://github.com/jamesagnew/hapi-fhir/issues/339 - * https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing - */ - inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory - inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities - - - /* - * In the following few lines, you can uncomment the first and comment the second to disable automatic - * parsing of extended entities, e.g. § - * - * Note that these properties are Woodstox specific and they cause a crash in environments where SJSXP is - * being used (e.g. glassfish) so we don't set them there. - */ - try { - Class.forName("com.ctc.wstx.stax.WstxInputFactory"); - boolean isWoodstox = inputFactory instanceof com.ctc.wstx.stax.WstxInputFactory; - if ( !isWoodstox ) - { - // Check if implementation is woodstox by property since instanceof check does not work if running in JBoss - try - { - isWoodstox = inputFactory.getProperty( "org.codehaus.stax2.implVersion" ) != null; - } - catch ( Exception e ) - { - // ignore - } - } - if (isWoodstox) { - // inputFactory.setProperty(WstxInputFactory.IS_REPLACING_ENTITY_REFERENCES, false); - inputFactory.setProperty(WstxInputProperties.P_UNDECLARED_ENTITY_RESOLVER, XML_RESOLVER); - try { - inputFactory.setProperty(WstxInputProperties.P_MAX_ATTRIBUTE_SIZE, "100000000"); - } catch (IllegalArgumentException e) { - // ignore - } - } - } catch (ClassNotFoundException e) { - ourLog.debug("WstxOutputFactory (Woodstox) not found on classpath"); - } - ourInputFactory = inputFactory; - } - return ourInputFactory; - } - - private static XMLOutputFactory getOrCreateOutputFactory() throws FactoryConfigurationError { - if (ourOutputFactory == null) { - ourOutputFactory = createOutputFactory(); - } - return ourOutputFactory; - } - - private static void logStaxImplementation(Class theClass) { - IDependencyLog logger = DependencyLogFactory.createJarLogger(); - if (logger != null) { - logger.logStaxImplementation(theClass); - } - ourHaveLoggedStaxImplementation = true; - } - - - static XMLInputFactory newInputFactory() throws FactoryConfigurationError { - XMLInputFactory inputFactory; - try { - inputFactory = XMLInputFactory.newInstance(); - throwUnitTestExceptionIfConfiguredToDoSo(); - } catch (Throwable e) { - throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e); - } - return inputFactory; - } - - static XMLOutputFactory newOutputFactory() throws FactoryConfigurationError { - XMLOutputFactory outputFactory; - try { - outputFactory = XMLOutputFactory.newInstance(); - throwUnitTestExceptionIfConfiguredToDoSo(); - } catch (Throwable e) { - throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e); - } - return outputFactory; - } - /** * FOR UNIT TESTS ONLY - Throw this exception for the next operation */ @@ -1782,26 +1781,6 @@ public class XmlUtil { return null; } } - - /** - * This method will return true if a StAX XML parsing library is present - * on the classpath - */ - public static boolean isStaxPresent() { - Boolean retVal = ourStaxPresent; - if (retVal == null) { - try { - newInputFactory(); - ourStaxPresent = Boolean.TRUE; - retVal = Boolean.TRUE; - } catch (ConfigurationException e) { - ourLog.info("StAX not detected on classpath, XML processing will be disabled"); - ourStaxPresent = Boolean.FALSE; - retVal = Boolean.FALSE; - } - } - return retVal; - } public static class MyEscaper implements EscapingWriterFactory { diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index 86594930586..ceafc691847 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.Set; import ca.uhn.fhir.rest.api.CacheControlDirective; +import ca.uhn.fhir.util.XmlDetectionUtil; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -110,7 +111,7 @@ public abstract class BaseClient implements IRestfulClient { setKeepResponses(true); } - if (XmlUtil.isStaxPresent() == false) { + if (XmlDetectionUtil.isStaxPresent() == false) { myEncoding = EncodingEnum.JSON; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 8f1a3341258..49950d34dd3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -576,11 +576,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } catch (InterruptedException theE) { // ignore } - } else { - ourLog.info("Proceeding, as we have {} results", mySyncedPids.size()); } } while (keepWaiting); + ourLog.info("Proceeding, as we have {} results", mySyncedPids.size()); + ArrayList retVal = new ArrayList<>(); synchronized (mySyncedPids) { verifySearchHasntFailedOrThrowInternalErrorException(mySearch); @@ -594,6 +594,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } } + ourLog.info("Done syncing results", mySyncedPids.size()); + return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java index d3e9c7657b4..7e85a05d75e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java @@ -189,6 +189,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscriptionTemp)); + ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute(); waitForQueueToDrain(); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 6d57c41a303..bb56c80141f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -26,7 +26,11 @@
]]> See the HAPI FHIR Android Documentation]]> - for more information. + for more information. As a part of this fix, all dependencies on + the StAX API have been removed in environments where StAX is not + present (such as Android). The client will now detect this case, and + explicitly request JSON payloads from servers, meaning that Android clients + no longer need to include two parser stacks A performance to the JPA server has been made which reduces the number