diff --git a/exist-core/src/main/java/org/exist/resolver/ResolverFactory.java b/exist-core/src/main/java/org/exist/resolver/ResolverFactory.java new file mode 100644 index 00000000000..50d16c0ac9e --- /dev/null +++ b/exist-core/src/main/java/org/exist/resolver/ResolverFactory.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to eXist-db by + * Evolved Binary, for the benefit of the eXist-db Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to eXist-db, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in eXist-db. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2014, Evolved Binary Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.resolver; + +import com.evolvedbinary.j8fu.tuple.Tuple2; +import org.exist.xmldb.XmldbURI; +import org.xml.sax.InputSource; +import org.xmlresolver.Resolver; +import org.xmlresolver.ResolverFeature; +import org.xmlresolver.XMLResolverConfiguration; + +import java.io.StringReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; + +/** + * Factory for creating Resolvers. + * + * @author > catalog : catalogs) { + if (catalog._2.isPresent()) { + resolverConfiguration.addCatalog(new URI(catalog._1), catalog._2.get()); + } else { + resolverConfiguration.addCatalog(catalog._1); + } + } + + return new Resolver(resolverConfiguration); + } + + /** + * Catalog URI if stored in database must start with + * URI Scheme xmldb:// (and NOT xmldb:exist://) so that + * the {@link Resolver} can use {@link org.exist.protocolhandler.protocols.xmldb.Handler} + * to resolve any relative URI resources from the database. + * + * @param catalogs the catalog URIs + * + * @return the catalog URIs suitable for use with the {@link Resolver}. + */ + static List>> fixupExistCatalogUris(final List>> catalogs) { + return catalogs.stream().map(catalog -> Tuple(fixupExistCatalogUri(catalog._1), catalog._2)).collect(Collectors.toList()); + } + + /** + * Catalog URI if stored in database must start with + * URI Scheme xmldb:// (and NOT xmldb:exist://) so that + * the {@link Resolver} can use {@link org.exist.protocolhandler.protocols.xmldb.Handler} + * to resolve any relative URI resources from the database. + * + * @param catalogUri the catalog URI + * + * @return the catalog URI suitable for use with the {@link Resolver}. + */ + static String fixupExistCatalogUri(String catalogUri) { + if (catalogUri.startsWith("xmldb:exist://")) { + catalogUri = catalogUri.replace("xmldb:exist://", "xmldb://"); + } else if (catalogUri.startsWith("/db")) { + catalogUri = "xmldb://" + catalogUri; + } + return catalogUri; + } + +} diff --git a/exist-core/src/main/java/org/exist/resolver/XercesXmlResolverAdapter.java b/exist-core/src/main/java/org/exist/resolver/XercesXmlResolverAdapter.java new file mode 100644 index 00000000000..def4e0d88ca --- /dev/null +++ b/exist-core/src/main/java/org/exist/resolver/XercesXmlResolverAdapter.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to eXist-db by + * Evolved Binary, for the benefit of the eXist-db Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to eXist-db, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in eXist-db. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2014, Evolved Binary Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.resolver; + +import org.apache.xerces.impl.dtd.XMLDTDDescription; +import org.apache.xerces.impl.xs.XSDDescription; +import org.apache.xerces.util.SAXInputSource; +import org.apache.xerces.util.XMLEntityDescriptionImpl; +import org.apache.xerces.xni.QName; +import org.apache.xerces.xni.XMLResourceIdentifier; +import org.apache.xerces.xni.XNIException; +import org.apache.xerces.xni.grammars.XMLSchemaDescription; +import org.apache.xerces.xni.parser.XMLEntityResolver; +import org.apache.xerces.xni.parser.XMLInputSource; +import org.exist.util.XMLReaderObjectFactory; +import org.xml.sax.*; +import org.xmlresolver.Resolver; + +import java.io.IOException; + +/** + * Adapts an {@link org.xmlresolver.Resolver} for use + * with Xerces SAX Parser by implementing {@link org.apache.xerces.xni.parser.XMLEntityResolver}. + * + * @author Adam Retter + */ +public class XercesXmlResolverAdapter implements XMLEntityResolver { + private final Resolver resolver; + + public XercesXmlResolverAdapter(final Resolver resolver) { + this.resolver = resolver; + } + + @Override + public XMLInputSource resolveEntity(final XMLResourceIdentifier xmlResourceIdentifier) throws XNIException, IOException { + + try { + // get the name + final String name; + if (xmlResourceIdentifier instanceof XSDDescription) { + final QName triggeringComponent = ((XSDDescription) xmlResourceIdentifier).getTriggeringComponent(); + name = triggeringComponent != null ? triggeringComponent.localpart : null; + } else if (xmlResourceIdentifier instanceof XMLSchemaDescription) { + final QName triggeringComponent = ((XMLSchemaDescription) xmlResourceIdentifier).getTriggeringComponent(); + name = triggeringComponent != null ? triggeringComponent.localpart : null; + } else if (xmlResourceIdentifier instanceof XMLEntityDescriptionImpl) { + name = ((XMLEntityDescriptionImpl)xmlResourceIdentifier).getEntityName(); + } else if (xmlResourceIdentifier instanceof XMLDTDDescription) { + name = ((XMLDTDDescription)xmlResourceIdentifier).getRootName(); + } else { + name = null; + } + + // get the systemId + final String systemId; + if (xmlResourceIdentifier.getExpandedSystemId() != null) { + systemId = xmlResourceIdentifier.getExpandedSystemId(); + } else { + systemId = xmlResourceIdentifier.getNamespace(); + } + +// System.out.println(String.format("xri=(name=%s publicId=%s baseSystemId=%s systemId=%s)", name, xmlResourceIdentifier.getPublicId(), xmlResourceIdentifier.getBaseSystemId(), systemId)); + + // resolve the entity via an org.xmlresolver.Resolver + final InputSource src = resolver.resolveEntity(name, xmlResourceIdentifier.getPublicId(), xmlResourceIdentifier.getBaseSystemId(), systemId); + if (src == null) { + return null; + } + + return new SAXInputSource(src); + + } catch (final SAXException e) { + throw new XNIException(e); + } + } + + /** + * Wraps the {@code resolver} in a XercesXMLResolverAdapter + * and then sets it as the property {@code http://apache.org/xml/properties/internal/entity-resolver} + * on the {@code xmlReader}. + * + * @param xmlReader the Xerces XML Reader + * @param resolver the resolver + * + * @throws SAXNotSupportedException if the property is not supported by the XMLReader + * @throws SAXNotRecognizedException if the property is not recognised by the XMLReader + */ + public static void setXmlReaderEntityResolver(final XMLReader xmlReader, final Resolver resolver) throws SAXNotSupportedException, SAXNotRecognizedException { + final XMLEntityResolver xmlEntityResolver = new XercesXmlResolverAdapter(resolver); + setXmlReaderEntityResolver(xmlReader, xmlEntityResolver); + } + + /** + * Sets the {@code xmlEntityResolver} as the property {@code http://apache.org/xml/properties/internal/entity-resolver} + * on the {@code xmlReader}. + * + * @param xmlReader the Xerces XML Reader + * @param xmlEntityResolver the resolver + * + * @throws SAXNotSupportedException if the property is not supported by the XMLReader + * @throws SAXNotRecognizedException if the property is not recognised by the XMLReader + */ + public static void setXmlReaderEntityResolver(final XMLReader xmlReader, final XMLEntityResolver xmlEntityResolver) throws SAXNotSupportedException, SAXNotRecognizedException { + xmlReader.setProperty(XMLReaderObjectFactory.APACHE_PROPERTIES_ENTITYRESOLVER, xmlEntityResolver); + } +} diff --git a/exist-core/src/main/java/org/exist/util/Configuration.java b/exist-core/src/main/java/org/exist/util/Configuration.java index d460c25caa0..b196310b1cc 100644 --- a/exist-core/src/main/java/org/exist/util/Configuration.java +++ b/exist-core/src/main/java/org/exist/util/Configuration.java @@ -19,6 +19,7 @@ */ package org.exist.util; +import com.evolvedbinary.j8fu.tuple.Tuple2; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -26,6 +27,7 @@ import org.exist.collections.CollectionCache; import org.exist.repo.Deployment; +import org.exist.resolver.ResolverFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -55,7 +57,6 @@ import org.exist.storage.serializers.CustomMatchListenerFactory; import org.exist.storage.serializers.Serializer; import org.exist.validation.GrammarPool; -import org.exist.validation.resolver.eXistXMLCatalogResolver; import org.exist.xmldb.DatabaseImpl; import org.exist.xquery.FunctionFactory; import org.exist.xquery.PerformanceStats; @@ -68,11 +69,13 @@ import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.Map.Entry; +import java.util.stream.Collectors; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; @@ -81,7 +84,9 @@ import org.exist.Namespaces; import org.exist.scheduler.JobType; import org.exist.xquery.Module; +import org.xmlresolver.Resolver; +import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING; @@ -261,7 +266,7 @@ public Configuration(String configFilename, Optional existHomeDirname) thr //Validation final NodeList validations = doc.getElementsByTagName(XMLReaderObjectFactory.CONFIGURATION_ELEMENT_NAME); if(validations.getLength() > 0) { - configureValidation(existHomeDirname, doc, (Element)validations.item(0)); + configureValidation(existHomeDirname, (Element)validations.item(0)); } } @@ -1459,34 +1464,29 @@ private void configureIndexer( final Optional dbHome, Document doc, Elemen } - private void configureValidation( final Optional dbHome, Document doc, Element validation ) throws DatabaseConfigurationException - { + private void configureValidation(final Optional dbHome, final Element validation) throws DatabaseConfigurationException { // Determine validation mode - final String mode = getConfigAttributeValue( validation, XMLReaderObjectFactory.VALIDATION_MODE_ATTRIBUTE ); + final String mode = getConfigAttributeValue(validation, XMLReaderObjectFactory.VALIDATION_MODE_ATTRIBUTE); + if (mode != null) { + config.put(XMLReaderObjectFactory.PROPERTY_VALIDATION_MODE, mode); - if( mode != null ) { - config.put( XMLReaderObjectFactory.PROPERTY_VALIDATION_MODE, mode ); - LOG.debug( XMLReaderObjectFactory.PROPERTY_VALIDATION_MODE + ": " + config.get( XMLReaderObjectFactory.PROPERTY_VALIDATION_MODE ) ); + if (LOG.isDebugEnabled()) { + LOG.debug(XMLReaderObjectFactory.PROPERTY_VALIDATION_MODE + ": " + config.get(XMLReaderObjectFactory.PROPERTY_VALIDATION_MODE)); + } } + // Configure the Entity Resolver + final NodeList entityResolver = validation.getElementsByTagName(XMLReaderObjectFactory.CONFIGURATION_ENTITY_RESOLVER_ELEMENT_NAME); + if (entityResolver.getLength() > 0) { - // Extract catalogs - LOG.debug( "Creating eXist catalog resolver" ); - final eXistXMLCatalogResolver resolver = new eXistXMLCatalogResolver(); - - final NodeList entityResolver = validation.getElementsByTagName( XMLReaderObjectFactory.CONFIGURATION_ENTITY_RESOLVER_ELEMENT_NAME ); - - if( entityResolver.getLength() > 0 ) { - final Element r = (Element)entityResolver.item( 0 ); - final NodeList catalogs = r.getElementsByTagName( XMLReaderObjectFactory.CONFIGURATION_CATALOG_ELEMENT_NAME ); + LOG.info("Creating xmlresolver.org OASIS Catalog resolver"); - LOG.debug( "Found " + catalogs.getLength() + " catalog uri entries." ); - LOG.debug( "Using dbHome=" + dbHome ); + final Element elemEntityResolver = (Element) entityResolver.item(0); + final NodeList nlCatalogs = elemEntityResolver.getElementsByTagName(XMLReaderObjectFactory.CONFIGURATION_CATALOG_ELEMENT_NAME); // Determine webapps directory. SingleInstanceConfiguration cannot - // be used at this phase. Trick is to check wether dbHOME is - // pointing to a WEB-INF directory, meaning inside war file) - + // be used at this phase. Trick is to check whether dbHOME is + // pointing to a WEB-INF directory, meaning inside the war file. final Path webappHome = dbHome.map(h -> { if(FileUtils.fileName(h).endsWith("WEB-INF")) { return h.getParent().toAbsolutePath(); @@ -1495,44 +1495,48 @@ private void configureValidation( final Optional dbHome, Document doc, Ele } }).orElse(Paths.get("webapp").toAbsolutePath()); - LOG.debug("using webappHome=" + webappHome.toString()); - - // Get and store all URIs - final List allURIs = new ArrayList<>(); - - for( int i = 0; i < catalogs.getLength(); i++ ) { - String uri = ( (Element)catalogs.item( i ) ).getAttribute( "uri" ); + if (LOG.isDebugEnabled()) { + LOG.debug("Found " + nlCatalogs.getLength() + " catalog uri entries."); + LOG.debug("Using dbHome=" + dbHome); + LOG.debug("using webappHome=" + webappHome.toString()); + } - if( uri != null ) { // when uri attribute is filled in + // Get the Catalog URIs + final List catalogUris = new ArrayList<>(); + for (int i = 0; i < nlCatalogs.getLength(); i++) { + String uri = ( (Element)nlCatalogs.item(i)).getAttribute("uri"); + if (uri != null) { // Substitute string, creating an uri from a local file - if( uri.indexOf( "${WEBAPP_HOME}" ) != -1 ) { - uri = uri.replaceAll( "\\$\\{WEBAPP_HOME\\}", webappHome.toUri().toString() ); + if (uri.indexOf("${WEBAPP_HOME}") != -1) { + uri = uri.replaceAll( "\\$\\{WEBAPP_HOME\\}", webappHome.toUri().toString()); } - if( uri.indexOf( "${EXIST_HOME}" ) != -1 ) { - uri = uri.replaceAll( "\\$\\{EXIST_HOME\\}", dbHome.toString() ); + if (uri.indexOf("${EXIST_HOME}") != -1) { + uri = uri.replaceAll("\\$\\{EXIST_HOME\\}", dbHome.toString()); } - // Add uri to confiuration - LOG.info( "Add catalog uri " + uri + "" ); - allURIs.add( uri ); + // Add uri to configuration + LOG.info("Adding Catalog URI: " + uri); + catalogUris.add(uri); } - } - resolver.setCatalogs( allURIs ); // Store all configured URIs - config.put( XMLReaderObjectFactory.CATALOG_URIS, allURIs ); + config.put(XMLReaderObjectFactory.CATALOG_URIS, catalogUris); + // Create and Store the resolver + try { + final List>> catalogs = catalogUris.stream().map(catalogUri -> Tuple(catalogUri, Optional.empty())).collect(Collectors.toList()); + final Resolver resolver = ResolverFactory.newResolver(catalogs); + config.put(XMLReaderObjectFactory.CATALOG_RESOLVER, resolver); + } catch (final URISyntaxException e) { + LOG.error("Unable to parse catalog uri: " + e.getMessage(), e); + } } - // Store resolver - config.put( XMLReaderObjectFactory.CATALOG_RESOLVER, resolver ); - // cache final GrammarPool gp = new GrammarPool(); - config.put( XMLReaderObjectFactory.GRAMMER_POOL, gp ); - + config.put( XMLReaderObjectFactory.GRAMMER_POOL, gp); } /** diff --git a/exist-core/src/main/java/org/exist/util/XMLReaderObjectFactory.java b/exist-core/src/main/java/org/exist/util/XMLReaderObjectFactory.java index 76564bc314b..94650be29c2 100644 --- a/exist-core/src/main/java/org/exist/util/XMLReaderObjectFactory.java +++ b/exist-core/src/main/java/org/exist/util/XMLReaderObjectFactory.java @@ -31,15 +31,15 @@ import org.apache.logging.log4j.Logger; import org.exist.Namespaces; -import org.exist.storage.BrokerPool; +import org.exist.resolver.XercesXmlResolverAdapter; import org.exist.storage.BrokerPoolService; import org.exist.validation.GrammarPool; -import org.exist.validation.resolver.eXistXMLCatalogResolver; import org.xml.sax.SAXException; import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.SAXNotSupportedException; import org.xml.sax.XMLReader; +import org.xmlresolver.Resolver; /** * Factory to create new XMLReader objects on demand. The factory is used @@ -81,18 +81,16 @@ public enum VALIDATION_SETTING { private Configuration configuration; private GrammarPool grammarPool; - private eXistXMLCatalogResolver resolver; + private Resolver resolver; @Override public void configure(final Configuration configuration) { this.configuration = configuration; this.grammarPool = (GrammarPool) configuration.getProperty(XMLReaderObjectFactory.GRAMMER_POOL); - this.resolver = (eXistXMLCatalogResolver) configuration.getProperty(CATALOG_RESOLVER); + this.resolver = (Resolver) configuration.getProperty(CATALOG_RESOLVER); } - /** - * @see org.apache.commons.pool.BasePoolableObjectFactory#makeObject() - */ + @Override public Object makeObject() throws Exception { final String option = (String) configuration.getProperty(PROPERTY_VALIDATION_MODE); final VALIDATION_SETTING validation = convertValidationMode(option); @@ -102,15 +100,13 @@ public Object makeObject() throws Exception { } /** - * Create Xmlreader and setup validation + * Create XMLReader and setup validation */ - public static XMLReader createXmlReader(VALIDATION_SETTING validation, GrammarPool grammarPool, - eXistXMLCatalogResolver resolver) throws ParserConfigurationException, SAXException{ - - // Create a xmlreader + public static XMLReader createXmlReader(final VALIDATION_SETTING validation, final GrammarPool grammarPool, final Resolver resolver) throws ParserConfigurationException, SAXException{ + // Create an XMLReader final SAXParserFactory saxFactory = ExistSAXParserFactory.getSAXParserFactory(); - if (validation == VALIDATION_SETTING.AUTO || validation == VALIDATION_SETTING.ENABLED){ + if (validation == VALIDATION_SETTING.AUTO || validation == VALIDATION_SETTING.ENABLED) { saxFactory.setValidating(true); } else { saxFactory.setValidating(false); @@ -121,13 +117,13 @@ public static XMLReader createXmlReader(VALIDATION_SETTING validation, GrammarPo final XMLReader xmlReader = saxParser.getXMLReader(); // Setup grammar cache - if(grammarPool!=null){ - setReaderProperty(xmlReader,APACHE_PROPERTIES_INTERNAL_GRAMMARPOOL, grammarPool); + if (grammarPool != null ){ + setReaderProperty(xmlReader, APACHE_PROPERTIES_INTERNAL_GRAMMARPOOL, grammarPool); } // Setup xml catalog resolver - if(resolver!=null){ - setReaderProperty(xmlReader,APACHE_PROPERTIES_ENTITYRESOLVER, resolver); + if (resolver != null) { + XercesXmlResolverAdapter.setXmlReaderEntityResolver(xmlReader, resolver); } return xmlReader; diff --git a/exist-core/src/main/java/org/exist/validation/Validator.java b/exist-core/src/main/java/org/exist/validation/Validator.java index ad3564f2395..529a2fff7f3 100644 --- a/exist-core/src/main/java/org/exist/validation/Validator.java +++ b/exist-core/src/main/java/org/exist/validation/Validator.java @@ -28,25 +28,34 @@ import com.thaiopensource.validate.rng.CompactSchemaReader; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Optional; +import javax.annotation.Nullable; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.xerces.xni.parser.XMLEntityResolver; import org.exist.Namespaces; +import org.exist.resolver.ResolverFactory; +import org.exist.resolver.XercesXmlResolverAdapter; +import org.exist.security.Subject; import org.exist.storage.BrokerPool; import org.exist.util.Configuration; import org.exist.util.ExistSAXParserFactory; import org.exist.util.XMLReaderObjectFactory; import org.exist.validation.resolver.AnyUriResolver; -import org.exist.validation.resolver.SearchResourceResolver; -import org.exist.validation.resolver.eXistXMLCatalogResolver; +import org.exist.validation.resolver.SearchResourceResolver;; import org.xml.sax.ContentHandler; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; +import org.xmlresolver.Resolver; +import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING; /** @@ -56,40 +65,37 @@ */ public class Validator { - private final static Logger logger = LogManager.getLogger(Validator.class); - private BrokerPool brokerPool = null; - private GrammarPool grammarPool = null; - private eXistXMLCatalogResolver systemCatalogResolver = null; + private static final Logger logger = LogManager.getLogger(Validator.class); + + private final BrokerPool brokerPool; + private final Subject subject; + private final GrammarPool grammarPool; + private final Resolver systemCatalogResolver; /** - * Setup Validator object with brokerpool as db connection. + * Setup Validator object with Broker Pool as db connection. * - * @param pool Brokerpool + * @param brokerPool the broker pool/ */ - public Validator(BrokerPool pool) { + public Validator(final BrokerPool brokerPool, final Subject subject) { logger.info("Initializing Validator."); - if(brokerPool == null){ - this.brokerPool = pool; - } - - // Get configuration - Configuration config = brokerPool.getConfiguration(); + this.brokerPool = brokerPool; + this.subject = subject; // Check xerces version final StringBuilder xmlLibMessage = new StringBuilder(); - if(!XmlLibraryChecker.hasValidParser(xmlLibMessage)){ + if (!XmlLibraryChecker.hasValidParser(xmlLibMessage)) { logger.error(xmlLibMessage); } + final Configuration config = brokerPool.getConfiguration(); + // setup grammar brokerPool - grammarPool = (GrammarPool) - config.getProperty(XMLReaderObjectFactory.GRAMMER_POOL); + this.grammarPool = (GrammarPool) config.getProperty(XMLReaderObjectFactory.GRAMMER_POOL); // setup system wide catalog resolver - systemCatalogResolver = (eXistXMLCatalogResolver) - config.getProperty(XMLReaderObjectFactory.CATALOG_RESOLVER); - + this.systemCatalogResolver = (Resolver) config.getProperty(XMLReaderObjectFactory.CATALOG_RESOLVER); } /** @@ -105,18 +111,18 @@ public ValidationReport validate(InputStream stream) { /** * Validate XML data from reader using specified grammar. * - * @param grammarUrl User supplied path to grammar. * @param stream XML input. + * @param grammarUrl User supplied path to grammar, or null. * @return Validation report containing all validation info. */ - public ValidationReport validate(InputStream stream, String grammarUrl) { + public ValidationReport validate(final InputStream stream, @Nullable String grammarUrl) { // repair path to local resource - if(grammarUrl != null && grammarUrl.startsWith("/")){ - grammarUrl = "xmldb:exist://" + grammarUrl; + if (grammarUrl != null) { + grammarUrl = ResolverFactory.fixupExistCatalogUri(grammarUrl); } - if(grammarUrl != null && + if (grammarUrl != null && (grammarUrl.endsWith(".rng") || grammarUrl.endsWith(".rnc") || grammarUrl.endsWith(".nvdl") || grammarUrl.endsWith(".sch"))){ // Validate with Jing @@ -197,33 +203,34 @@ public ValidationReport validateParse(InputStream stream, String grammarUrl) { final ValidationReport report = new ValidationReport(); final ValidationContentHandler contenthandler = new ValidationContentHandler(); - try { final XMLReader xmlReader = getXMLReader(contenthandler, report); - if(grammarUrl == null){ + if (grammarUrl == null){ // Scenario 1 : no params - use system catalog logger.debug("Validation using system catalog."); - xmlReader.setProperty(XMLReaderObjectFactory.APACHE_PROPERTIES_ENTITYRESOLVER, systemCatalogResolver); + XercesXmlResolverAdapter.setXmlReaderEntityResolver(xmlReader, systemCatalogResolver); - } else if(grammarUrl.endsWith(".xml")){ + } else if (grammarUrl.endsWith(".xml")) { // Scenario 2 : path to catalog (xml) logger.debug("Validation using user specified catalog '" + grammarUrl + "'."); - final eXistXMLCatalogResolver resolver = new eXistXMLCatalogResolver(); - resolver.setCatalogList(new String[]{grammarUrl}); - xmlReader.setProperty(XMLReaderObjectFactory.APACHE_PROPERTIES_ENTITYRESOLVER, resolver); + final Resolver resolver = ResolverFactory.newResolver(Arrays.asList(Tuple(grammarUrl, Optional.empty()))); + XercesXmlResolverAdapter.setXmlReaderEntityResolver(xmlReader, resolver); - } else if(grammarUrl.endsWith("/")){ + } else if (grammarUrl.endsWith("/")) { // Scenario 3 : path to collection ("/"): search. logger.debug("Validation using searched grammar, start from '" + grammarUrl + "'."); - final SearchResourceResolver resolver = new SearchResourceResolver(grammarUrl, brokerPool); - xmlReader.setProperty(XMLReaderObjectFactory.APACHE_PROPERTIES_ENTITYRESOLVER, resolver); + final XMLEntityResolver resolver = new SearchResourceResolver(brokerPool, subject, grammarUrl); + XercesXmlResolverAdapter.setXmlReaderEntityResolver(xmlReader, resolver); } else { + if (grammarUrl.startsWith("/db")) { + grammarUrl = "xmldb://" + grammarUrl; + } // Scenario 4 : path to grammar (xsd, dtd) specified. logger.debug("Validation using specified grammar '" + grammarUrl + "'."); @@ -245,7 +252,7 @@ public ValidationReport validateParse(InputStream stream, String grammarUrl) { logger.debug("Document is not valid."); } - } catch(final ParserConfigurationException | SAXException | IOException ex) { + } catch(final ParserConfigurationException | SAXException | IOException | URISyntaxException ex) { logger.error(ex); report.setThrowable(ex); diff --git a/exist-core/src/main/java/org/exist/validation/resolver/SearchResourceResolver.java b/exist-core/src/main/java/org/exist/validation/resolver/SearchResourceResolver.java index bee5500b9a8..82c39fc4346 100644 --- a/exist-core/src/main/java/org/exist/validation/resolver/SearchResourceResolver.java +++ b/exist-core/src/main/java/org/exist/validation/resolver/SearchResourceResolver.java @@ -16,139 +16,216 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * $Id$ */ - package org.exist.validation.resolver; import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.URISyntaxException; import java.net.URL; +import java.util.Arrays; +import java.util.Optional; +import java.util.Properties; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.xerces.xni.XMLResourceIdentifier; import org.apache.xerces.xni.XNIException; import org.apache.xerces.xni.parser.XMLEntityResolver; import org.apache.xerces.xni.parser.XMLInputSource; +import org.exist.EXistException; +import org.exist.dom.persistent.DocumentImpl; +import org.exist.resolver.ResolverFactory; +import org.exist.security.PermissionDeniedException; import org.exist.security.Subject; import org.exist.storage.BrokerPool; +import org.exist.storage.DBBroker; +import org.exist.storage.lock.Lock; +import org.exist.storage.serializers.Serializer; +import org.exist.util.serializer.SAXSerializer; +import org.exist.util.serializer.SerializerPool; import org.exist.validation.internal.DatabaseResources; +import org.exist.xmldb.XmldbURI; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xmlresolver.Resolver; + +import javax.xml.transform.OutputKeys; + +import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; +import static java.nio.charset.StandardCharsets.UTF_8; /** - * Resolve a resource by searching in database. Schema's are queried + * Resolve a resource by searching in database. Schema's are queried * directly, DTD are searched in catalog files. * * @author Dannes Wessels (dizzzz@exist-db.org) */ public class SearchResourceResolver implements XMLEntityResolver { - private final static Logger LOG = LogManager.getLogger(SearchResourceResolver.class); - - private String collection=null; - private BrokerPool brokerPool = null; - - /** Creates a new instance of StoredResourceResolver */ - public SearchResourceResolver(String collectionPath, BrokerPool pool) { - LOG.debug("Specified collectionPath="+collectionPath); - collection=collectionPath; - brokerPool=pool; + private static final Logger LOG = LogManager.getLogger(SearchResourceResolver.class); + + private final String collectionPath; + private final Subject subject; + private final BrokerPool brokerPool; + + public SearchResourceResolver(final BrokerPool brokerPool, final Subject subject, final String collectionPath) { + this.brokerPool = brokerPool; + this.subject = subject; + this.collectionPath = collectionPath; + + if (LOG.isDebugEnabled()) { + LOG.debug("Specified collectionPath=" + collectionPath); + } } - @Override - public XMLInputSource resolveEntity(XMLResourceIdentifier xri) throws XNIException, IOException { - - if(xri.getExpandedSystemId()==null && xri.getLiteralSystemId()==null && - xri.getNamespace()==null && xri.getPublicId()==null){ - + public XMLInputSource resolveEntity(final XMLResourceIdentifier xri) throws XNIException, IOException { + if (xri.getExpandedSystemId() == null && xri.getLiteralSystemId() == null && xri.getNamespace() == null && xri.getPublicId() == null) { // quick fail return null; } - - if(LOG.isDebugEnabled()) { - LOG.debug("Resolving XMLResourceIdentifier: "+getXriDetails(xri)); - } + if (LOG.isDebugEnabled()) { + LOG.debug("Resolving XMLResourceIdentifier: " + getXriDetails(xri)); + } String resourcePath = null; - final DatabaseResources databaseResources = new DatabaseResources(brokerPool); - - //UNDERSTAND: why using guest account, it can be disabled - final Subject user = brokerPool.getSecurityManager().getGuestSubject(); - - if( xri.getNamespace() !=null ){ - + if (xri.getNamespace() != null) { // XML Schema search - LOG.debug("Searching namespace '"+xri.getNamespace()+"' in database from "+collection+"..."); - - resourcePath = databaseResources.findXSD(collection, xri.getNamespace(), user); - - // DIZZZ: set systemid? - - } else if ( xri.getPublicId() !=null ){ - + if (LOG.isDebugEnabled()) { + LOG.debug("Searching namespace '" + xri.getNamespace() + "' in database from " + collectionPath + "..."); + } + resourcePath = databaseResources.findXSD(collectionPath, xri.getNamespace(), subject); + + } else if (xri.getPublicId() != null) { // Catalog search - LOG.debug("Searching publicId '"+xri.getPublicId()+"' in catalogs in database from "+collection+"..."); - - String catalogPath = databaseResources.findCatalogWithDTD(collection, xri.getPublicId(), user); - LOG.debug("Found publicId in catalog '"+catalogPath+"'"); - if(catalogPath!=null && catalogPath.startsWith("/")){ - catalogPath="xmldb:exist://"+catalogPath; + if (LOG.isDebugEnabled()) { + LOG.debug("Searching publicId '" + xri.getPublicId() + "' in catalogs in database from " + collectionPath + "..."); } - - final eXistXMLCatalogResolver resolver = new eXistXMLCatalogResolver(); - resolver.setCatalogList(new String[]{catalogPath}); - try { - final InputSource source = resolver.resolveEntity(xri.getPublicId(), ""); - if(source!=null){ - resourcePath=source.getSystemId(); + + String catalogPath = databaseResources.findCatalogWithDTD(collectionPath, xri.getPublicId(), subject); + if (LOG.isDebugEnabled()) { + LOG.debug("Found publicId in catalog '" + catalogPath + "'"); + } + + if (catalogPath != null) { + /* NOTE(AR): Catalog URL if stored in database must start with + URI Scheme xmldb:// so that the XML Resolver can use + org.exist.protocolhandler.protocols.xmldb.Handler + to resolve any relative URI resources from the database. + */ + try { + final Optional maybeInputSource; + if (catalogPath.startsWith("xmldb:exist://")) { + catalogPath = ResolverFactory.fixupExistCatalogUri(catalogPath); + maybeInputSource = Optional.of(new InputSource(new StringReader(serializeDocument(XmldbURI.create(catalogPath))))); + } else if (catalogPath.startsWith("/db")) { + catalogPath = ResolverFactory.fixupExistCatalogUri(catalogPath); + maybeInputSource = Optional.of(new InputSource(new StringReader(serializeDocument(XmldbURI.create(catalogPath))))); + } else { + maybeInputSource = Optional.empty(); + } + + if (maybeInputSource.isPresent()) { + maybeInputSource.get().setSystemId(catalogPath); + } + + final Resolver resolver = ResolverFactory.newResolver(Arrays.asList(Tuple(catalogPath, maybeInputSource))); + final InputSource source = resolver.resolveEntity(xri.getPublicId(), ""); + if (source != null) { + resourcePath = source.getSystemId(); + } else { + resourcePath = null; + } + } catch (final SAXException | URISyntaxException e) { + throw new XNIException(e.getMessage(), e); } - } catch (final SAXException | IOException ex) { - LOG.debug(ex); } - - // set systemid? - } else { - // Fast escape; no logging, otherwise validation is slow! - return null; + resourcePath = null; } - + // Another escape route - if(resourcePath==null){ - LOG.debug("resourcePath=null"); + if (resourcePath == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("resourcePath=null"); + } return null; } - - if(resourcePath.startsWith("/")){ - resourcePath="xmldb:exist://"+resourcePath; + + resourcePath = ResolverFactory.fixupExistCatalogUri(resourcePath); + + if (LOG.isDebugEnabled()) { + LOG.debug("resourcePath='" + resourcePath + "'"); } - - LOG.debug("resourcePath='"+resourcePath+"'"); - + final InputStream is = new URL(resourcePath).openStream(); - - final XMLInputSource xis = new XMLInputSource(xri.getPublicId(), - xri.getExpandedSystemId(), xri.getBaseSystemId(), is, "UTF-8"); - if(LOG.isDebugEnabled()) { - LOG.debug( "XMLInputSource: "+getXisDetails(xis) ); + final XMLInputSource xis = new XMLInputSource(xri.getPublicId(), xri.getExpandedSystemId(), xri.getBaseSystemId(), is, UTF_8.name()); + + if (LOG.isDebugEnabled()) { + LOG.debug("XMLInputSource: " + getXisDetails(xis)); } - + return xis; } - - private String getXriDetails(XMLResourceIdentifier xrid) { - return String.format("PublicId='%s' BaseSystemId='%s' ExpandedSystemId='%s' LiteralSystemId='%s' Namespace='%s' ", + + private String getXriDetails(final XMLResourceIdentifier xrid) { + return String.format("PublicId='%s' BaseSystemId='%s' ExpandedSystemId='%s' LiteralSystemId='%s' Namespace='%s' ", xrid.getPublicId(), xrid.getBaseSystemId(), xrid.getExpandedSystemId(), xrid.getLiteralSystemId(), xrid.getNamespace()); } - private String getXisDetails(XMLInputSource xis) { - return String.format("PublicId='%s' SystemId='%s' BaseSystemId='%s' Encoding='%s' ", + private String getXisDetails(final XMLInputSource xis) { + return String.format("PublicId='%s' SystemId='%s' BaseSystemId='%s' Encoding='%s' ", xis.getPublicId(), xis.getSystemId(), xis.getBaseSystemId(), xis.getEncoding()); } + // TODO(AR) remove this when PR https://github.com/xmlresolver/xmlresolver/pull/98 is merged + private String serializeDocument(final XmldbURI documentUri) throws SAXException, IOException { + DocumentImpl doc = null; + try (final DBBroker broker = brokerPool.get(Optional.of(subject))) { + doc = broker.getXMLResource(documentUri, Lock.LockMode.READ_LOCK); + if (doc == null) { + throw new IOException("No such document: " + documentUri); + } + + try (final StringWriter stringWriter = new StringWriter()) { + final Properties outputProperties = new Properties(); + outputProperties.setProperty(OutputKeys.METHOD, "XML"); + outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + outputProperties.setProperty(OutputKeys.INDENT, "no"); + outputProperties.setProperty(OutputKeys.ENCODING, "UTF-8"); + + final Serializer serializer = broker.getSerializer(); + serializer.reset(); + SAXSerializer sax = null; + try { + sax = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class); + sax.setOutput(stringWriter, outputProperties); + serializer.setProperties(outputProperties); + serializer.setSAXHandlers(sax, sax); + serializer.toSAX(doc); + } catch (final SAXNotSupportedException | SAXNotRecognizedException e) { + throw new SAXException(e.getMessage(), e); + } finally { + if (sax != null) { + SerializerPool.getInstance().returnObject(sax); + } + } + + return stringWriter.toString(); + } + } catch (final EXistException | PermissionDeniedException e) { + throw new IOException(e.getMessage(), e); + } finally { + if (doc != null) { + doc.getUpdateLock().release(Lock.LockMode.READ_LOCK); + } + } + } } diff --git a/exist-core/src/main/java/org/exist/validation/resolver/eXistXMLCatalogResolver.java b/exist-core/src/main/java/org/exist/validation/resolver/eXistXMLCatalogResolver.java deleted file mode 100644 index 44a572349f5..00000000000 --- a/exist-core/src/main/java/org/exist/validation/resolver/eXistXMLCatalogResolver.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * eXist Open Source Native XML Database - * Copyright (C) 2001-2010 The eXist Project - * http://exist-db.org - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * $Id$ - */ - -package org.exist.validation.resolver; - -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.xerces.util.XMLCatalogResolver; -import org.apache.xerces.xni.XMLResourceIdentifier; -import org.apache.xerces.xni.XNIException; -import org.apache.xerces.xni.parser.XMLInputSource; -import org.exist.util.FileUtils; -import org.w3c.dom.ls.LSInput; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -/** - * Wrapper around xerces2's - * XMLCatalogresolver - * - * @author Dannes Wessels (dizzzz@exist-db.org) - */ -public class eXistXMLCatalogResolver extends XMLCatalogResolver { - - public eXistXMLCatalogResolver() { - super(); - LOG.debug("Initializing"); - } - - public eXistXMLCatalogResolver(java.lang.String[] catalogs) { - super(catalogs); - LOG.debug("Initializing using catalogs"); - } - - eXistXMLCatalogResolver(java.lang.String[] catalogs, boolean preferPublic) { - super(catalogs, preferPublic); - LOG.debug("Initializing using catalogs, preferPublic=" + preferPublic); - } - - private final static Logger LOG = LogManager.getLogger(eXistXMLCatalogResolver.class); - - /** - * Constructs a catalog resolver with the given list of entry files. - * - * @param catalogs List of Strings - *

- * TODO: check for non-String and NULL values. - */ - public void setCatalogs(List catalogs) { - - if (catalogs != null && catalogs.size() > 0) { - final String[] allCatalogs = new String[catalogs.size()]; - int counter = 0; - for (String element : catalogs) { - allCatalogs[counter] = element; - counter++; - } - super.setCatalogList(allCatalogs); - } - } - - /** - * @see org.apache.xerces.util.XMLCatalogResolver#resolveEntity(String, String) - */ - public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { - LOG.debug("Resolving publicId='" + publicId + "', systemId='" + systemId + "'"); - InputSource retValue = super.resolveEntity(publicId, systemId); - - if (retValue == null) { - retValue = resolveEntityFallback(publicId, systemId); - } - - LOG.debug("Resolved " + (retValue != null)); - if (retValue != null) { - LOG.debug("PublicId='" + retValue.getPublicId() + "' SystemId=" + retValue.getSystemId()); - } - return retValue; - } - - /** - * moved from Collection.resolveEntity() revision 6144 - */ - private InputSource resolveEntityFallback(String publicId, String systemId) throws SAXException, IOException { - //if resolution failed and publicId == null, - // try to make absolute file names relative and retry - LOG.debug("Resolve failed, fallback scenario"); - if (publicId != null) { - return null; - } - - final URL url = new URL(systemId); - if ("file".equals(url.getProtocol())) { - final String path = url.getPath(); - final Path f = Paths.get(path).normalize(); - if (!Files.isReadable(f)) { - return resolveEntity(null, FileUtils.fileName(f)); - } else { - return new InputSource(f.toAbsolutePath().toString()); - } - } else { - return new InputSource(url.openStream()); //TODO(AR) stream is never closed! - } - } - - /** - * @see org.apache.xerces.util.XMLCatalogResolver#resolveResource(String, String, String, String, String) - */ - public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) { - LOG.debug("Resolving type='" + type + "', namespaceURI='" + namespaceURI + "', publicId='" + publicId + "', systemId='" + systemId + "', baseURI='" + baseURI + "'"); - final LSInput retValue = super.resolveResource(type, namespaceURI, publicId, systemId, baseURI); - - LOG.debug("Resolved " + (retValue != null)); - if (retValue != null) { - LOG.debug("PublicId='" + retValue.getPublicId() + "' SystemId='" - + retValue.getSystemId() + "' BaseURI='" + retValue.getBaseURI() + "'"); - } - - return retValue; - } - - /** - * @see org.apache.xerces.util.XMLCatalogResolver#resolveEntity(String, String, String, String) - */ - public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) throws SAXException, IOException { - LOG.debug("Resolving name='" + name + "', publicId='" + publicId + "', baseURI='" + baseURI + "', systemId='" + systemId + "'"); - final InputSource retValue = super.resolveEntity(name, publicId, baseURI, systemId); - - LOG.debug("Resolved " + (retValue != null)); - if (retValue != null) { - LOG.debug("PublicId='" + retValue.getPublicId() + "' SystemId='" - + retValue.getSystemId() + "'"); - } - - return retValue; - } - - /** - * @see org.apache.xerces.util.XMLCatalogResolver#resolveIdentifier(XMLResourceIdentifier) - */ - public String resolveIdentifier(XMLResourceIdentifier xri) throws IOException, XNIException { - - if (xri.getExpandedSystemId() == null && xri.getLiteralSystemId() == null && - xri.getNamespace() == null && xri.getPublicId() == null) { - - // quick fail - return null; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Resolving XMLResourceIdentifier: " + getXriDetails(xri)); - } - - final String retValue = super.resolveIdentifier(xri); - LOG.debug("Resolved " + (retValue != null)); - if (retValue != null) { - LOG.debug("Identifier='" + retValue + "'"); - } - return retValue; - } - - /** - * @see org.apache.xerces.util.XMLCatalogResolver#resolveEntity(XMLResourceIdentifier) - */ - public XMLInputSource resolveEntity(XMLResourceIdentifier xri) throws XNIException, IOException { - if (xri.getExpandedSystemId() == null && xri.getLiteralSystemId() == null && - xri.getNamespace() == null && xri.getPublicId() == null) { - - // quick fail - return null; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Resolving XMLResourceIdentifier: " + getXriDetails(xri)); - } - final XMLInputSource retValue = super.resolveEntity(xri); - - - LOG.debug("Resolved " + (retValue != null)); - if (retValue != null) { - LOG.debug("PublicId='" + retValue.getPublicId() + "' SystemId='" - + retValue.getSystemId() + "' BaseSystemId=" + retValue.getBaseSystemId()); - } - - return retValue; - } - - /** - * @see org.apache.xerces.util.XMLCatalogResolver#getExternalSubset(String, String) - */ - public InputSource getExternalSubset(String name, String baseURI) throws SAXException, IOException { - LOG.debug("name='" + name + "' baseURI='" + baseURI + "'"); - return super.getExternalSubset(name, baseURI); - } - - private String getXriDetails(XMLResourceIdentifier xrid) { - return "PublicId='" + xrid.getPublicId() + "' " + "BaseSystemId='" + xrid.getBaseSystemId() + "' " + "ExpandedSystemId='" + xrid.getExpandedSystemId() + "' " + "LiteralSystemId='" + xrid.getLiteralSystemId() + "' " + "Namespace='" + xrid.getNamespace() + "' "; - } - -} diff --git a/exist-core/src/main/java/org/exist/xmlrpc/RpcConnection.java b/exist-core/src/main/java/org/exist/xmlrpc/RpcConnection.java index bf4871ad7c5..ca80df86665 100644 --- a/exist-core/src/main/java/org/exist/xmlrpc/RpcConnection.java +++ b/exist-core/src/main/java/org/exist/xmlrpc/RpcConnection.java @@ -3324,7 +3324,7 @@ private boolean isValid(final XmldbURI docUri) throws EXistException, Permission try { // Setup validator - final Validator validator = new Validator(factory.getBrokerPool()); + final Validator validator = new Validator(factory.getBrokerPool(), user); // Get inputstream // TODO DWES reconsider diff --git a/exist-core/src/main/java/org/exist/xquery/functions/validation/Jaxp.java b/exist-core/src/main/java/org/exist/xquery/functions/validation/Jaxp.java index 53c33b7b2e5..38222d2300d 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/validation/Jaxp.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/validation/Jaxp.java @@ -22,8 +22,14 @@ package org.exist.xquery.functions.validation; import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; import java.net.MalformedURLException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Properties; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; @@ -37,22 +43,31 @@ import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; -import org.apache.xerces.xni.parser.XMLEntityResolver; +import com.evolvedbinary.j8fu.tuple.Tuple2; +import org.apache.xerces.xni.parser.XMLEntityResolver; import org.exist.Namespaces; import org.exist.dom.QName; import org.exist.dom.memtree.DocumentBuilderReceiver; import org.exist.dom.memtree.MemTreeBuilder; +import org.exist.dom.persistent.DocumentImpl; +import org.exist.resolver.ResolverFactory; +import org.exist.resolver.XercesXmlResolverAdapter; +import org.exist.security.PermissionDeniedException; import org.exist.storage.BrokerPool; +import org.exist.storage.lock.Lock; +import org.exist.storage.serializers.Serializer; import org.exist.util.Configuration; import org.exist.util.ExistSAXParserFactory; import org.exist.util.XMLReaderObjectFactory; import org.exist.util.io.TemporaryFileManager; +import org.exist.util.serializer.SAXSerializer; +import org.exist.util.serializer.SerializerPool; import org.exist.validation.GrammarPool; import org.exist.validation.ValidationContentHandler; import org.exist.validation.ValidationReport; import org.exist.validation.resolver.SearchResourceResolver; -import org.exist.validation.resolver.eXistXMLCatalogResolver; +import org.exist.xmldb.XmldbURI; import org.exist.xquery.BasicFunction; import org.exist.xquery.Cardinality; import org.exist.xquery.FunctionSignature; @@ -72,7 +87,10 @@ import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.SAXNotSupportedException; import org.xml.sax.XMLReader; +import org.xmlresolver.Resolver; +import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; +import static java.nio.charset.StandardCharsets.UTF_8; import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING; /** @@ -173,29 +191,21 @@ public Jaxp(XQueryContext context, FunctionSignature signature) { brokerPool = context.getBroker().getBrokerPool(); } - /** - * @throws org.exist.xquery.XPathException - * @see BasicFunction#eval(Sequence[], Sequence) - */ - public Sequence eval(Sequence[] args, Sequence contextSequence) - throws XPathException { - - XMLEntityResolver entityResolver = null; - GrammarPool grammarPool = null; - + @Override + public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException { final ValidationReport report = new ValidationReport(); - ContentHandler contenthandler = null; - MemTreeBuilder instanceBuilder = null; - InputSource instance = null; + final MemTreeBuilder instanceBuilder; + final ContentHandler contenthandler; if (isCalledAs("jaxp-parse")) { instanceBuilder = context.getDocumentBuilder(); contenthandler = new DocumentBuilderReceiver(instanceBuilder, true); // (namespace?) - } else { + instanceBuilder = null; contenthandler = new ValidationContentHandler(); } + InputSource instance = null; try { report.start(); @@ -217,8 +227,8 @@ public Sequence eval(Sequence[] args, Sequence contextSequence) // Use system catalog LOG.debug("Using system catalog."); final Configuration config = brokerPool.getConfiguration(); - entityResolver = (eXistXMLCatalogResolver) config.getProperty(XMLReaderObjectFactory.CATALOG_RESOLVER); - setXmlReaderEnitityResolver(xmlReader, entityResolver); + final Resolver resolver = (Resolver) config.getProperty(XMLReaderObjectFactory.CATALOG_RESOLVER); + XercesXmlResolverAdapter.setXmlReaderEntityResolver(xmlReader, resolver); } else { // Get URL for catalog @@ -228,14 +238,38 @@ public Sequence eval(Sequence[] args, Sequence contextSequence) if (singleUrl.endsWith("/")) { // Search grammar in collection specified by URL. Just one collection is used. LOG.debug("Search for grammar in " + singleUrl); - entityResolver = new SearchResourceResolver(catalogUrls[0], brokerPool); - setXmlReaderEnitityResolver(xmlReader, entityResolver); + final XMLEntityResolver resolver = new SearchResourceResolver(brokerPool, context.getSubject(), catalogUrls[0]); + XercesXmlResolverAdapter.setXmlReaderEntityResolver(xmlReader, resolver); } else if (singleUrl.endsWith(".xml")) { LOG.debug("Using catalogs " + getStrings(catalogUrls)); - entityResolver = new eXistXMLCatalogResolver(); - ((eXistXMLCatalogResolver) entityResolver).setCatalogList(catalogUrls); - setXmlReaderEnitityResolver(xmlReader, entityResolver); + + final List>> catalogs = new ArrayList<>(); + for (String catalogUrl : catalogUrls) { + + /* NOTE(AR): Catalog URL if stored in database must start with + URI Scheme xmldb:// so that the XML Resolver can use + org.exist.protocolhandler.protocols.xmldb.Handler + to resolve any relative URI resources from the database. + */ + final Optional maybeInputSource; + if (catalogUrl.startsWith("xmldb:exist://")) { + catalogUrl = ResolverFactory.fixupExistCatalogUri(catalogUrl); + maybeInputSource = Optional.of(new InputSource(new StringReader(serializeDocument(XmldbURI.create(catalogUrl))))); + } else if (catalogUrl.startsWith("/db")) { + catalogUrl = ResolverFactory.fixupExistCatalogUri(catalogUrl); + maybeInputSource = Optional.of(new InputSource(new StringReader(serializeDocument(XmldbURI.create(catalogUrl))))); + } else { + maybeInputSource = Optional.empty(); + } + + if (maybeInputSource.isPresent()) { + maybeInputSource.get().setSystemId(catalogUrl); + } + catalogs.add(Tuple(catalogUrl, maybeInputSource)); + } + final Resolver resolver = ResolverFactory.newResolver(catalogs); + XercesXmlResolverAdapter.setXmlReaderEntityResolver(xmlReader, resolver); } else { LOG.error("Catalog URLs should end on / or .xml"); @@ -248,7 +282,7 @@ public Sequence eval(Sequence[] args, Sequence contextSequence) if (useCache) { LOG.debug("Grammar caching enabled."); final Configuration config = brokerPool.getConfiguration(); - grammarPool = (GrammarPool) config.getProperty(XMLReaderObjectFactory.GRAMMER_POOL); + final GrammarPool grammarPool = (GrammarPool) config.getProperty(XMLReaderObjectFactory.GRAMMER_POOL); xmlReader.setProperty(XMLReaderObjectFactory.APACHE_PROPERTIES_INTERNAL_GRAMMARPOOL, grammarPool); } @@ -278,7 +312,6 @@ public Sequence eval(Sequence[] args, Sequence contextSequence) } finally { report.stop(); - Shared.closeInputSource(instance); } @@ -318,6 +351,7 @@ private XMLReader getXMLReader() throws ParserConfigurationException, SAXExcepti // Enable validation stuff saxFactory.setValidating(true); saxFactory.setNamespaceAware(true); + saxFactory.setFeature(XMLReaderObjectFactory.APACHE_FEATURES_VALIDATION_SCHEMA, true); // Create xml reader final SAXParser saxParser = saxFactory.newSAXParser(); @@ -345,14 +379,47 @@ private void setXmlReaderFeature(XMLReader xmlReader, String featureName, boolea } } - private void setXmlReaderEnitityResolver(XMLReader xmlReader, XMLEntityResolver entityResolver ){ - + // TODO(AR) remove this when PR https://github.com/xmlresolver/xmlresolver/pull/98 is merged + private String serializeDocument(final XmldbURI documentUri) throws SAXException, IOException { + DocumentImpl doc = null; try { - xmlReader.setProperty(XMLReaderObjectFactory.APACHE_PROPERTIES_ENTITYRESOLVER, entityResolver); + doc = context.getBroker().getXMLResource(documentUri, Lock.LockMode.READ_LOCK); + if (doc == null) { + throw new IOException("No such document: " + documentUri); + } - } catch (final SAXNotRecognizedException | SAXNotSupportedException ex) { - LOG.error(ex.getMessage()); + try (final StringWriter stringWriter = new StringWriter()) { + final Properties outputProperties = new Properties(); + outputProperties.setProperty(OutputKeys.METHOD, "XML"); + outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + outputProperties.setProperty(OutputKeys.INDENT, "no"); + outputProperties.setProperty(OutputKeys.ENCODING, UTF_8.name()); + + final Serializer serializer = context.getBroker().getSerializer(); + serializer.reset(); + SAXSerializer sax = null; + try { + sax = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class); + sax.setOutput(stringWriter, outputProperties); + serializer.setProperties(outputProperties); + serializer.setSAXHandlers(sax, sax); + serializer.toSAX(doc); + } catch (final SAXNotSupportedException | SAXNotRecognizedException e) { + throw new SAXException(e.getMessage(), e); + } finally { + if (sax != null) { + SerializerPool.getInstance().returnObject(sax); + } + } + return stringWriter.toString(); + } + } catch (final PermissionDeniedException e) { + throw new IOException(e.getMessage(), e); + } finally { + if (doc != null) { + doc.getUpdateLock().release(Lock.LockMode.READ_LOCK); + } } } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/validation/Validation.java b/exist-core/src/main/java/org/exist/xquery/functions/validation/Validation.java index 289ebff8c52..1c9b4b4d6f1 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/validation/Validation.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/validation/Validation.java @@ -21,7 +21,6 @@ */ package org.exist.xquery.functions.validation; -import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; @@ -45,7 +44,7 @@ import org.exist.xquery.value.ValueSequence; /** - * XQuery function for validation of XML instance documents + * XQuery function for validation of XML instance documents * using grammars like XSDs and DTDs. * * @author Dannes Wessels (dizzzz@exist-db.org) @@ -138,7 +137,7 @@ public class Validation extends BasicFunction { public Validation(XQueryContext context, FunctionSignature signature) { super(context, signature); brokerPool = context.getBroker().getBrokerPool(); - validator = new Validator(brokerPool); + validator = new Validator(brokerPool, context.getSubject()); } /** diff --git a/lib/endorsed/xmlresolver-4.2.0-data.jar b/lib/endorsed/xmlresolver-4.2.0-data.jar new file mode 100644 index 00000000000..65da8793b7e Binary files /dev/null and b/lib/endorsed/xmlresolver-4.2.0-data.jar differ diff --git a/lib/endorsed/xmlresolver-4.2.0.jar b/lib/endorsed/xmlresolver-4.2.0.jar new file mode 100644 index 00000000000..3dca89fb177 Binary files /dev/null and b/lib/endorsed/xmlresolver-4.2.0.jar differ