--- test/javax/servlet/TestSchemaValidation.java (revision 0) +++ test/javax/servlet/TestSchemaValidation.java (revision 0) @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package javax.servlet; + +import java.io.File; +import java.net.URL; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.apache.catalina.deploy.WebXml; +import org.apache.catalina.startup.DigesterFactory; +import org.apache.catalina.startup.WebRuleSet; +import org.apache.tomcat.util.digester.Digester; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.ls.LSInput; +import org.w3c.dom.ls.LSResourceResolver; + +public class TestSchemaValidation { + + /** + * Test for https://issues.apache.org/bugzilla/show_bug.cgi?id=55166 + */ + @Test + @Ignore + public void testValidation() throws Exception { + LSResourceResolver resolver = new LSResourceResolver() { + @Override + public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + }; + SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + schemaFactory.setResourceResolver(resolver); + URL url = ServletContext.class.getResource("/javax/servlet/resources/web-app_3_1.xsd"); + System.out.println("url = " + url); + Schema schema = schemaFactory.newSchema(url); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setSchema(schema); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(new File("test/webapp/WEB-INF/web.xml")); + Assert.assertEquals("web-app", document.getDocumentElement().getLocalName()); + } + + @Test + public void testWebapp() throws Exception { + Digester digester = DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + digester.push(new WebXml()); + WebXml desc = (WebXml) digester.parse(new File("test/webapp/WEB-INF/web.xml")); + Assert.assertEquals("3.1", desc.getVersion()); + } + + @Test + public void testWebapp_2_3() throws Exception { + Digester digester = DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + digester.push(new WebXml()); + WebXml desc = (WebXml) digester.parse(new File("test/webapp-2.3/WEB-INF/web.xml")); + Assert.assertEquals("2.3", desc.getVersion()); + } + + @Test + public void testWebapp_2_4() throws Exception { + Digester digester = DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + digester.push(new WebXml()); + WebXml desc = (WebXml) digester.parse(new File("test/webapp-2.4/WEB-INF/web.xml")); + Assert.assertEquals("2.4", desc.getVersion()); + } + + @Test + public void testWebapp_2_5() throws Exception { + Digester digester = DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + digester.push(new WebXml()); + WebXml desc = (WebXml) digester.parse(new File("test/webapp-2.5/WEB-INF/web.xml")); + Assert.assertEquals("2.5", desc.getVersion()); + } + + @Test + public void testWebapp_3_0() throws Exception { + Digester digester = DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + digester.push(new WebXml()); + WebXml desc = (WebXml) digester.parse(new File("test/webapp-3.0/WEB-INF/web.xml")); + Assert.assertEquals("3.0", desc.getVersion()); + } + + @Test + public void testWebapp_3_1() throws Exception { + Digester digester = DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + digester.push(new WebXml()); + WebXml desc = (WebXml) digester.parse(new File("test/webapp-3.1/WEB-INF/web.xml")); + Assert.assertEquals("3.1", desc.getVersion()); + } +} --- java/org/apache/tomcat/util/digester/Digester.java (revision 1500306) +++ java/org/apache/tomcat/util/digester/Digester.java (working copy) @@ -45,6 +45,7 @@ import org.xml.sax.SAXNotSupportedException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.DefaultHandler; @@ -791,6 +792,37 @@ reader.setEntityResolver(entityResolver); } + reader.setProperty("http://xml.org/sax/properties/lexical-handler", new LexicalHandler() { + @Override + public void startDTD(String name, String publicId, String systemId) throws SAXException { + setPublicId(publicId); + } + + @Override + public void endDTD() throws SAXException { + } + + @Override + public void startEntity(String name) throws SAXException { + } + + @Override + public void endEntity(String name) throws SAXException { + } + + @Override + public void startCDATA() throws SAXException { + } + + @Override + public void endCDATA() throws SAXException { + } + + @Override + public void comment(char[] ch, int start, int length) throws SAXException { + } + }); + reader.setErrorHandler(this); return reader; } @@ -1292,9 +1324,6 @@ saxLog.debug("resolveEntity('" + publicId + "', '" + systemId + "')"); } - if (publicId != null) - this.publicId = publicId; - // Has this system identifier been registered? String entityURL = null; if (publicId != null) { --- java/org/apache/catalina/startup/DigesterFactory.java (revision 1500306) +++ java/org/apache/catalina/startup/DigesterFactory.java (working copy) @@ -18,11 +18,7 @@ package org.apache.catalina.startup; -import java.net.URL; - import org.apache.catalina.util.SchemaResolver; -import org.apache.juli.logging.Log; -import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.digester.Digester; import org.apache.tomcat.util.digester.RuleSet; @@ -33,12 +29,6 @@ */ public class DigesterFactory { /** - * The log. - */ - private static final Log log = LogFactory.getLog(DigesterFactory.class); - - - /** * Create a Digester parser. * @param xmlValidation turn on/off xml validation * @param xmlNamespaceAware turn on/off namespace validation @@ -51,175 +41,11 @@ digester.setNamespaceAware(xmlNamespaceAware); digester.setValidating(xmlValidation); digester.setUseContextClassLoader(true); - - SchemaResolver schemaResolver = new SchemaResolver(digester); - registerLocalSchema(schemaResolver); - - digester.setEntityResolver(schemaResolver); + digester.setEntityResolver(SchemaResolver.getEntityResolver()); if ( rule != null ) { digester.addRuleSet(rule); } return (digester); } - - - /** - * Utilities used to force the parser to use local schema, when available, - * instead of the schemaLocation XML element. - */ - protected static void registerLocalSchema(SchemaResolver schemaResolver){ - // J2EE - register(Constants.J2eeSchemaResourcePath_14, - Constants.J2eeSchemaPublicId_14, - schemaResolver); - - register(Constants.JavaeeSchemaResourcePath_5, - Constants.JavaeeSchemaPublicId_5, - schemaResolver); - - register(Constants.JavaeeSchemaResourcePath_6, - Constants.JavaeeSchemaPublicId_6, - schemaResolver); - - register(Constants.JavaeeSchemaResourcePath_7, - Constants.JavaeeSchemaPublicId_7, - schemaResolver); - - // W3C - register(Constants.W3cSchemaResourcePath_10, - Constants.W3cSchemaPublicId_10, - schemaResolver); - - register(Constants.W3cSchemaDTDResourcePath_10, - Constants.W3cSchemaDTDPublicId_10, - schemaResolver); - - register(Constants.W3cDatatypesDTDResourcePath_10, - Constants.W3cDatatypesDTDPublicId_10, - schemaResolver); - - // JSP - register(Constants.JspSchemaResourcePath_20, - Constants.JspSchemaPublicId_20, - schemaResolver); - - register(Constants.JspSchemaResourcePath_21, - Constants.JspSchemaPublicId_21, - schemaResolver); - - register(Constants.JspSchemaResourcePath_22, - Constants.JspSchemaPublicId_22, - schemaResolver); - - register(Constants.JspSchemaResourcePath_23, - Constants.JspSchemaPublicId_23, - schemaResolver); - - // TLD - register(Constants.TldDtdResourcePath_11, - Constants.TldDtdPublicId_11, - schemaResolver); - - register(Constants.TldDtdResourcePath_12, - Constants.TldDtdPublicId_12, - schemaResolver); - - register(Constants.TldSchemaResourcePath_20, - Constants.TldSchemaPublicId_20, - schemaResolver); - - register(Constants.TldSchemaResourcePath_21, - Constants.TldSchemaPublicId_21, - schemaResolver); - - // web.xml - register(Constants.WebDtdResourcePath_22, - Constants.WebDtdPublicId_22, - schemaResolver); - - register(Constants.WebDtdResourcePath_23, - Constants.WebDtdPublicId_23, - schemaResolver); - - register(Constants.WebSchemaResourcePath_24, - Constants.WebSchemaPublicId_24, - schemaResolver); - - register(Constants.WebSchemaResourcePath_25, - Constants.WebSchemaPublicId_25, - schemaResolver); - - register(Constants.WebSchemaResourcePath_30, - Constants.WebSchemaPublicId_30, - schemaResolver); - - register(Constants.WebCommonSchemaResourcePath_30, - Constants.WebCommonSchemaPublicId_30, - schemaResolver); - - register(Constants.WebFragmentSchemaResourcePath_30, - Constants.WebFragmentSchemaPublicId_30, - schemaResolver); - - register(Constants.WebSchemaResourcePath_31, - Constants.WebSchemaPublicId_31, - schemaResolver); - - register(Constants.WebCommonSchemaResourcePath_31, - Constants.WebCommonSchemaPublicId_31, - schemaResolver); - - register(Constants.WebFragmentSchemaResourcePath_31, - Constants.WebFragmentSchemaPublicId_31, - schemaResolver); - - // Web Service - register(Constants.J2eeWebServiceSchemaResourcePath_11, - Constants.J2eeWebServiceSchemaPublicId_11, - schemaResolver); - - register(Constants.J2eeWebServiceClientSchemaResourcePath_11, - Constants.J2eeWebServiceClientSchemaPublicId_11, - schemaResolver); - - register(Constants.JavaeeWebServiceSchemaResourcePath_12, - Constants.JavaeeWebServiceSchemaPublicId_12, - schemaResolver); - - register(Constants.JavaeeWebServiceClientSchemaResourcePath_12, - Constants.JavaeeWebServiceClientSchemaPublicId_12, - schemaResolver); - - register(Constants.JavaeeWebServiceSchemaResourcePath_13, - Constants.JavaeeWebServiceSchemaPublicId_13, - schemaResolver); - - register(Constants.JavaeeWebServiceClientSchemaResourcePath_13, - Constants.JavaeeWebServiceClientSchemaPublicId_13, - schemaResolver); - - register(Constants.JavaeeWebServiceSchemaResourcePath_14, - Constants.JavaeeWebServiceSchemaPublicId_14, - schemaResolver); - - register(Constants.JavaeeWebServiceClientSchemaResourcePath_14, - Constants.JavaeeWebServiceClientSchemaPublicId_14, - schemaResolver); - } - - - /** - * Load the resource and add it to the resolver. - */ - protected static void register(String resourceURL, String resourcePublicId, - SchemaResolver schemaResolver){ - URL url = DigesterFactory.class.getResource(resourceURL); - - if(url == null) { - log.warn("Could not get url for " + resourceURL); - } else { - schemaResolver.register(resourcePublicId , url.toString() ); - } - } } --- java/org/apache/catalina/util/SchemaResolver.java (revision 1500306) +++ java/org/apache/catalina/util/SchemaResolver.java (working copy) @@ -17,14 +17,55 @@ package org.apache.catalina.util; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.util.HashMap; import java.util.Map; +import javax.servlet.ServletContext; +import javax.servlet.jsp.JspFactory; -import org.apache.tomcat.util.digester.Digester; -import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.ext.EntityResolver2; +import static org.apache.catalina.startup.Constants.J2eeSchemaPublicId_14; +import static org.apache.catalina.startup.Constants.J2eeWebServiceClientSchemaPublicId_11; +import static org.apache.catalina.startup.Constants.J2eeWebServiceSchemaPublicId_11; +import static org.apache.catalina.startup.Constants.JavaeeSchemaPublicId_5; +import static org.apache.catalina.startup.Constants.JavaeeSchemaPublicId_6; +import static org.apache.catalina.startup.Constants.JavaeeSchemaPublicId_7; +import static org.apache.catalina.startup.Constants.JavaeeWebServiceClientSchemaPublicId_12; +import static org.apache.catalina.startup.Constants.JavaeeWebServiceClientSchemaPublicId_13; +import static org.apache.catalina.startup.Constants.JavaeeWebServiceClientSchemaPublicId_14; +import static org.apache.catalina.startup.Constants.JavaeeWebServiceSchemaPublicId_12; +import static org.apache.catalina.startup.Constants.JavaeeWebServiceSchemaPublicId_13; +import static org.apache.catalina.startup.Constants.JavaeeWebServiceSchemaPublicId_14; +import static org.apache.catalina.startup.Constants.JspSchemaPublicId_20; +import static org.apache.catalina.startup.Constants.JspSchemaPublicId_21; +import static org.apache.catalina.startup.Constants.JspSchemaPublicId_22; +import static org.apache.catalina.startup.Constants.JspSchemaPublicId_23; +import static org.apache.catalina.startup.Constants.TldDtdPublicId_11; +import static org.apache.catalina.startup.Constants.TldDtdPublicId_12; +import static org.apache.catalina.startup.Constants.TldDtdResourcePath_11; +import static org.apache.catalina.startup.Constants.TldDtdResourcePath_12; +import static org.apache.catalina.startup.Constants.W3cDatatypesDTDResourcePath_10; +import static org.apache.catalina.startup.Constants.W3cSchemaDTDResourcePath_10; +import static org.apache.catalina.startup.Constants.W3cSchemaResourcePath_10; +import static org.apache.catalina.startup.Constants.WebCommonSchemaPublicId_30; +import static org.apache.catalina.startup.Constants.WebCommonSchemaPublicId_31; +import static org.apache.catalina.startup.Constants.WebDtdPublicId_22; +import static org.apache.catalina.startup.Constants.WebDtdPublicId_23; +import static org.apache.catalina.startup.Constants.WebDtdResourcePath_22; +import static org.apache.catalina.startup.Constants.WebDtdResourcePath_23; +import static org.apache.catalina.startup.Constants.WebFragmentSchemaPublicId_30; +import static org.apache.catalina.startup.Constants.WebFragmentSchemaPublicId_31; +import static org.apache.catalina.startup.Constants.WebSchemaPublicId_24; +import static org.apache.catalina.startup.Constants.WebSchemaPublicId_25; +import static org.apache.catalina.startup.Constants.WebSchemaPublicId_30; +import static org.apache.catalina.startup.Constants.WebSchemaPublicId_31; + /** * This class implements a local SAX's EntityResolver. All * DTDs and schemas used to validate the web.xml file will re-directed @@ -32,97 +73,124 @@ * * @author Jean-Francois Arcand */ -public class SchemaResolver implements EntityResolver { +public class SchemaResolver implements EntityResolver2 { - /** - * The digester instance for which this class is the entity resolver. - */ - protected final Digester digester; + private static final String SUN_1_4 = "http://java.sun.com/xml/ns/j2ee/"; + private static final String SUN = "http://java.sun.com/xml/ns/javaee/"; + private static final String JCP = "http://xmlns.jcp.org/xml/ns/javaee/"; + private static final String IBM = "http://www.ibm.com/webservices/xsd/"; + private static final SchemaResolver INSTANCE = new SchemaResolver(); + private static final Map PUBLIC = new HashMap<>(); + private static final Map SYSTEM = new HashMap<>(); + static { + Class base = ServletContext.class; - /** - * The URLs of dtds and schemas that have been registered, keyed by the - * public identifier that corresponds. - */ - protected final Map entityValidator = new HashMap<>(); + // W3C + PUBLIC.put("-//W3C//DTD XMLSCHEMA 200102//EN", base.getResource(W3cSchemaDTDResourcePath_10)); + PUBLIC.put("datatypes", base.getResource(W3cDatatypesDTDResourcePath_10)); + SYSTEM.put("http://www.w3.org/2001/xml.xsd", base.getResource(W3cSchemaResourcePath_10)); + // from J2EE 1.2 + PUBLIC.put(WebDtdPublicId_22, base.getResource(WebDtdResourcePath_22)); + PUBLIC.put(TldDtdPublicId_11, base.getResource(TldDtdResourcePath_11)); - /** - * Extension to make the difference between DTD and Schema. - */ - protected final String schemaExtension = "xsd"; + // from J2EE 1.3 + PUBLIC.put(WebDtdPublicId_23, base.getResource(WebDtdResourcePath_23)); + PUBLIC.put(TldDtdPublicId_12, base.getResource(TldDtdResourcePath_12)); + // from J2EE 1.4 + register(IBM, J2eeWebServiceSchemaPublicId_11, base); + register(IBM, J2eeWebServiceClientSchemaPublicId_11, base); + register(SUN_1_4, J2eeSchemaPublicId_14, base); + register(SUN_1_4, JspSchemaPublicId_20, JspFactory.class); + register(SUN_1_4, WebSchemaPublicId_24, base); - /** - * Create a new EntityResolver that will redirect - * all remote dtds and schema to a local destination. - * @param digester The digester instance. - */ - public SchemaResolver(Digester digester) { - this.digester = digester; + // from JavaEE 5 + register(SUN, JavaeeWebServiceSchemaPublicId_12, base); + register(SUN, JavaeeWebServiceClientSchemaPublicId_12, base); + register(SUN, JavaeeSchemaPublicId_5, base); + register(SUN, JspSchemaPublicId_21, JspFactory.class); + register(SUN, WebSchemaPublicId_25, base); + + // from JavaEE 6 + register(SUN, JavaeeWebServiceSchemaPublicId_13, base); + register(SUN, JavaeeWebServiceClientSchemaPublicId_13, base); + register(SUN, JavaeeSchemaPublicId_6, base); + register(SUN, JspSchemaPublicId_22, JspFactory.class); + register(SUN, WebSchemaPublicId_30, base); + register(SUN, WebFragmentSchemaPublicId_30, base); + register(SUN, WebCommonSchemaPublicId_30, base); + + // from JavaEE 7 + register(JCP, JavaeeWebServiceSchemaPublicId_14, base); + register(JCP, JavaeeWebServiceClientSchemaPublicId_14, base); + register(JCP, JavaeeSchemaPublicId_7, base); + register(JCP, JspSchemaPublicId_23, JspFactory.class); + register(JCP, WebSchemaPublicId_31, base); + register(JCP, WebFragmentSchemaPublicId_31, base); + register(JCP, WebCommonSchemaPublicId_31, base); } + private static void register(String origin, String file, Class base) { + SYSTEM.put(origin + file, base.getResource("resources/" + file)); + } /** - * Register the specified DTD/Schema URL for the specified public - * identifier. This must be called before the first call to - * parse(). + * Returns an EntityResolver that resolves resources from local jars. * - * When adding a schema file (*.xsd), only the name of the file - * will get added. If two schemas with the same name are added, - * only the last one will be stored. - * - * @param publicId Public identifier of the DTD to be resolved - * @param entityURL The URL to use for reading this DTD + * @return an EntityResolver that resolves resources from local jars */ - public void register(String publicId, String entityURL) { - String key = publicId; - if (publicId.indexOf(schemaExtension) != -1) - key = publicId.substring(publicId.lastIndexOf('/')+1); - entityValidator.put(key, entityURL); - } + public static SchemaResolver getEntityResolver() { + return INSTANCE; + } - /** - * Resolve the requested external entity. - * - * @param publicId The public identifier of the entity being referenced - * @param systemId The system identifier of the entity being referenced - * - * @exception SAXException if a parsing exception occurs - * + * Constructor allowing sub-classing to handle additional mappings. */ + protected SchemaResolver() { + } + @Override - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException { + public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { + return resolveEntity(null, publicId, null, systemId); + } - if (publicId != null) { - digester.setPublicId(publicId); - } + @Override + public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) + throws SAXException, IOException { - // Has this system identifier been registered? - String entityURL = null; - if (publicId != null) { - entityURL = entityValidator.get(publicId); + // resolve the systemId against the baseURI + try { + if (systemId != null && baseURI != null) { + URI systemUri = new URI(systemId); + if (!systemUri.isAbsolute()) { + systemId = new URI(baseURI).resolve(systemUri).toString(); + } + } + } catch (URISyntaxException e) { + throw new SAXException(e); } - // Redirect the schema location to a local destination - String key = null; - if (entityURL == null && systemId != null) { - key = systemId.substring(systemId.lastIndexOf('/')+1); - entityURL = entityValidator.get(key); - } + // try resolving using the publicId + URL url = PUBLIC.get(publicId); - if (entityURL == null) { - return (null); + // if not found, try resolving using the systemId + if (url == null) { + url = SYSTEM.get(systemId); } - - try { - return (new InputSource(entityURL)); - } catch (Exception e) { - throw new SAXException(e); + if (url == null) { + return null; + } else { + InputSource is = new InputSource(url.openStream()); + is.setPublicId(publicId); + is.setSystemId(systemId); + return is; } + } + @Override + public InputSource getExternalSubset(String name, String baseURI) throws SAXException, IOException { + return null; } - }