Description: Fix CVE-2015-0254 XXE and RCE via XSL extension in JSTL XML tags When an application uses or tags to process untrusted XML documents, a request may utilize external entity references to access resources on the host system or utilize XSLT extensions that may allow remote execution. For more information, just go to: http://www.securityfocus.com/archive/1/534772. Author: The Apache Software Foundation Bug-Debian: https://bugs.debian.org/779621 Origin: upstream, http://svn.apache.org/r1642442, http://svn.apache.org/r1642613 Forwarded: not-needed Last-Update: 2015-03-14 Index: jakarta-taglibs-standard-1.1.2-src/standard/src/javax/servlet/jsp/jstl/tlv/ParserUtil.java =================================================================== --- /dev/null +++ jakarta-taglibs-standard-1.1.2-src/standard/src/javax/servlet/jsp/jstl/tlv/ParserUtil.java @@ -0,0 +1,86 @@ +/* + * 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.jsp.jstl.tlv; + +import java.io.IOException; +import java.io.InputStream; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.servlet.jsp.tagext.PageData; +import javax.xml.XMLConstants; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Support class for working with the SAX Parser. + */ +class ParserUtil { + + private static final SAXParserFactory PARSER_FACTORY; + static { + PARSER_FACTORY = AccessController.doPrivileged(new PrivilegedAction() { + public SAXParserFactory run() { + ClassLoader original = Thread.currentThread().getContextClassLoader(); + ClassLoader ours = ParserUtil.class.getClassLoader(); + try { + if (original != ours) { + Thread.currentThread().setContextClassLoader(ours); + } + return SAXParserFactory.newInstance(); + } finally { + if (original != ours) { + Thread.currentThread().setContextClassLoader(original); + } + } + } + }); + try { + PARSER_FACTORY.setValidating(true); + PARSER_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (ParserConfigurationException e) { + throw new ExceptionInInitializerError(e); + } catch (SAXNotRecognizedException e) { + throw new ExceptionInInitializerError(e); + } catch (SAXNotSupportedException e) { + throw new ExceptionInInitializerError(e); + } + } + + private ParserUtil() { + } + + static void parse(PageData pageData, DefaultHandler handler) throws ParserConfigurationException, SAXException, IOException { + SAXParser parser = PARSER_FACTORY.newSAXParser(); + InputStream is = pageData.getInputStream(); + try { + parser.parse(is, handler); + } finally { + try { + is.close(); + } catch (IOException e) { + // Suppress. + } + } + } +} Index: jakarta-taglibs-standard-1.1.2-src/standard/src/javax/servlet/jsp/jstl/tlv/PermittedTaglibsTLV.java =================================================================== --- jakarta-taglibs-standard-1.1.2-src.orig/standard/src/javax/servlet/jsp/jstl/tlv/PermittedTaglibsTLV.java +++ jakarta-taglibs-standard-1.1.2-src/standard/src/javax/servlet/jsp/jstl/tlv/PermittedTaglibsTLV.java @@ -17,6 +17,7 @@ package javax.servlet.jsp.jstl.tlv; import java.io.IOException; +import java.io.InputStream; import java.util.HashSet; import java.util.Set; import java.util.StringTokenizer; @@ -92,8 +93,7 @@ public class PermittedTaglibsTLV extends //********************************************************************* // Validation entry point - public synchronized ValidationMessage[] validate( - String prefix, String uri, PageData page) { + public synchronized ValidationMessage[] validate(String prefix, String uri, PageData page) { try { // initialize @@ -104,10 +104,7 @@ public class PermittedTaglibsTLV extends DefaultHandler h = new PermittedTaglibsHandler(); // parse the page - SAXParserFactory f = SAXParserFactory.newInstance(); - f.setValidating(true); - SAXParser p = f.newSAXParser(); - p.parse(page.getInputStream(), h); + ParserUtil.parse(page, h); if (failed) return vmFromString( Index: jakarta-taglibs-standard-1.1.2-src/standard/src/javax/servlet/jsp/jstl/tlv/ScriptFreeTLV.java =================================================================== --- jakarta-taglibs-standard-1.1.2-src.orig/standard/src/javax/servlet/jsp/jstl/tlv/ScriptFreeTLV.java +++ jakarta-taglibs-standard-1.1.2-src/standard/src/javax/servlet/jsp/jstl/tlv/ScriptFreeTLV.java @@ -24,7 +24,6 @@ import javax.servlet.jsp.tagext.PageData import javax.servlet.jsp.tagext.TagLibraryValidator; import javax.servlet.jsp.tagext.ValidationMessage; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; @@ -100,32 +99,19 @@ public class ScriptFreeTLV extends TagLi * @return null, if the page is valid; otherwise, a ValidationMessage[] * containing one or more messages indicating why the page is not valid. */ - public ValidationMessage[] validate - (String prefix, String uri, PageData page) { - InputStream in = null; - SAXParser parser; - MyContentHandler handler = new MyContentHandler(); - try { - synchronized (factory) { - parser = factory.newSAXParser(); - } - in = page.getInputStream(); - parser.parse(in, handler); + public ValidationMessage[] validate(String prefix, String uri, PageData page) { + try { + MyContentHandler handler = new MyContentHandler(); + ParserUtil.parse(page, handler); + return handler.reportResults(); + } catch (ParserConfigurationException e) { + return vmFromString(e.toString()); + } catch (SAXException e) { + return vmFromString(e.toString()); + } catch (IOException e) { + return vmFromString(e.toString()); + } } - catch (ParserConfigurationException e) { - return vmFromString(e.toString()); - } - catch (SAXException e) { - return vmFromString(e.toString()); - } - catch (IOException e) { - return vmFromString(e.toString()); - } - finally { - if (in != null) try { in.close(); } catch (IOException e) {} - } - return handler.reportResults(); - } /** * Handler for SAX events. Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/core/ImportSupport.java =================================================================== --- jakarta-taglibs-standard-1.1.2-src.orig/standard/src/org/apache/taglibs/standard/tag/common/core/ImportSupport.java +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/core/ImportSupport.java @@ -45,6 +45,7 @@ import javax.servlet.jsp.tagext.BodyTagS import javax.servlet.jsp.tagext.TryCatchFinally; import org.apache.taglibs.standard.resources.Resources; +import org.apache.taglibs.standard.util.UrlUtil; /** *

Support for tag handlers for <import>, the general-purpose @@ -60,22 +61,6 @@ public abstract class ImportSupport exte //********************************************************************* // Public constants - /**

Valid characters in a scheme.

- *

RFC 1738 says the following:

- *
- * Scheme names consist of a sequence of characters. The lower - * case letters "a"--"z", digits, and the characters plus ("+"), - * period ("."), and hyphen ("-") are allowed. For resiliency, - * programs interpreting URLs should treat upper case letters as - * equivalent to lower case in scheme names (e.g., allow "HTTP" as - * well as "http"). - *
- *

We treat as absolute any URL that begins with such a scheme name, - * followed by a colon.

- */ - public static final String VALID_SCHEME_CHARS = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-"; - /** Default character encoding for response. */ public static final String DEFAULT_ENCODING = "ISO-8859-1"; @@ -133,7 +118,7 @@ public abstract class ImportSupport exte throw new NullAttributeException("import", "url"); // Record whether our URL is absolute or relative - isAbsoluteUrl = isAbsoluteUrl(); + isAbsoluteUrl = UrlUtil.isAbsoluteUrl(url); try { // If we need to expose a Reader, we've got to do it right away @@ -494,43 +479,10 @@ public abstract class ImportSupport exte return urlWithParams; } - /** - * Returns true if our current URL is absolute, - * false otherwise. - */ - private boolean isAbsoluteUrl() throws JspTagException { - return isAbsoluteUrl(url); - } - - //********************************************************************* // Public utility methods /** - * Returns true if our current URL is absolute, - * false otherwise. - */ - public static boolean isAbsoluteUrl(String url) { - // a null URL is not absolute, by our definition - if (url == null) - return false; - - // do a fast, simple check first - int colonPos; - if ((colonPos = url.indexOf(":")) == -1) - return false; - - // if we DO have a colon, make sure that every character - // leading up to it is a valid scheme character - for (int i = 0; i < colonPos; i++) - if (VALID_SCHEME_CHARS.indexOf(url.charAt(i)) == -1) - return false; - - // if so, we've got an absolute url - return true; - } - - /** * Strips a servlet session ID from url. The session ID * is encoded as a URL "path parameter" beginning with "jsessionid=". * We thus remove anything we find between ";jsessionid=" (inclusive) Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/core/RedirectSupport.java =================================================================== --- jakarta-taglibs-standard-1.1.2-src.orig/standard/src/org/apache/taglibs/standard/tag/common/core/RedirectSupport.java +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/core/RedirectSupport.java @@ -22,6 +22,8 @@ import javax.servlet.jsp.JspTagException import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.BodyTagSupport; +import org.apache.taglibs.standard.util.UrlUtil; + /** *

Support for tag handlers for <redirect>, JSTL 1.0's tag * for redirecting to a new URL (with optional query parameters).

@@ -90,29 +92,30 @@ public abstract class RedirectSupport ex return EVAL_BODY_BUFFERED; } - // gets the right value, encodes it, and prints or stores it + public int doEndTag() throws JspException { - String result; // the eventual result + String result; // the eventual result - // add (already encoded) parameters + // add (already encoded) parameters String baseUrl = UrlSupport.resolveUrl(url, context, pageContext); result = params.aggregateParams(baseUrl); // if the URL is relative, rewrite it with 'redirect' encoding rules HttpServletResponse response = - ((HttpServletResponse) pageContext.getResponse()); - if (!ImportSupport.isAbsoluteUrl(result)) + ((HttpServletResponse) pageContext.getResponse()); + if (!UrlUtil.isAbsoluteUrl(result)) { result = response.encodeRedirectURL(result); + } - // redirect! - try { - response.sendRedirect(result); - } catch (java.io.IOException ex) { - throw new JspTagException(ex.toString(), ex); - } + // redirect! + try { + response.sendRedirect(result); + } catch (java.io.IOException ex) { + throw new JspTagException(ex.toString(), ex); + } - return SKIP_PAGE; + return SKIP_PAGE; } // Releases any resources we may have (or inherit) Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/core/UrlSupport.java =================================================================== --- jakarta-taglibs-standard-1.1.2-src.orig/standard/src/org/apache/taglibs/standard/tag/common/core/UrlSupport.java +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/core/UrlSupport.java @@ -24,6 +24,7 @@ import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.BodyTagSupport; import org.apache.taglibs.standard.resources.Resources; +import org.apache.taglibs.standard.util.UrlUtil; /** *

Support for tag handlers for <url>, the URL creation @@ -104,7 +105,7 @@ public abstract class UrlSupport extends result = params.aggregateParams(baseUrl); // if the URL is relative, rewrite it - if (!ImportSupport.isAbsoluteUrl(result)) { + if (!UrlUtil.isAbsoluteUrl(result)) { HttpServletResponse response = ((HttpServletResponse) pageContext.getResponse()); result = response.encodeURL(result); @@ -134,29 +135,32 @@ public abstract class UrlSupport extends public static String resolveUrl( String url, String context, PageContext pageContext) - throws JspException { - // don't touch absolute URLs - if (ImportSupport.isAbsoluteUrl(url)) - return url; - - // normalize relative URLs against a context root - HttpServletRequest request = - (HttpServletRequest) pageContext.getRequest(); - if (context == null) { - if (url.startsWith("/")) - return (request.getContextPath() + url); - else - return url; - } else { + throws JspException { + // don't touch absolute URLs + if (UrlUtil.isAbsoluteUrl(url)) { + return url; + } + + // normalize relative URLs against a context root + HttpServletRequest request = + (HttpServletRequest) pageContext.getRequest(); + if (context == null) { + if (url.startsWith("/")) { + return (request.getContextPath() + url); + } else { + return url; + } + } else { if (!context.startsWith("/") || !url.startsWith("/")) { throw new JspTagException( - Resources.getMessage("IMPORT_BAD_RELATIVE")); + Resources.getMessage("IMPORT_BAD_RELATIVE")); } - if (context.equals("/")) { + if (context.endsWith("/") && url.startsWith("/")) { // Don't produce string starting with '//', many // browsers interpret this as host name, not as - // path on same host. - return url; + // path on same host. Bug 22860 + // Also avoid // inside the url. Bug 34109 + return (context.substring(0, context.length() - 1) + url); } else { return (context + url); } Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/JSTLVariableStack.java =================================================================== --- /dev/null +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/JSTLVariableStack.java @@ -0,0 +1,132 @@ +/* + * 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 org.apache.taglibs.standard.tag.common.xml; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.jsp.PageContext; +import javax.xml.transform.TransformerException; + +import org.apache.taglibs.standard.resources.Resources; +import org.apache.xml.utils.QName; +import org.apache.xpath.VariableStack; +import org.apache.xpath.XPathContext; +import org.apache.xpath.objects.XObject; +import org.apache.xpath.objects.XObjectFactory; + +/** + */ +public class JSTLVariableStack extends VariableStack { + + private static enum Scope { + PARAM, + HEADER, + COOKIE, + INITPARAM, + PAGE, + REQUEST, + SESSION, + APPLICATION + } + + // Prefixes for JSTL implicit variables + private static final String PARAM_PREFIX = "param"; + private static final String HEADER_PREFIX = "header"; + private static final String COOKIE_PREFIX = "cookie"; + private static final String INITPARAM_PREFIX = "initParam"; + private static final String PAGE_PREFIX = "pageScope"; + private static final String REQUEST_PREFIX = "requestScope"; + private static final String SESSION_PREFIX = "sessionScope"; + private static final String APP_PREFIX = "applicationScope"; + + // map prefixes to scopes + private static final Map SCOPES; + static { + SCOPES = new HashMap(8); + SCOPES.put(PARAM_PREFIX, Scope.PARAM); + SCOPES.put(HEADER_PREFIX, Scope.HEADER); + SCOPES.put(COOKIE_PREFIX, Scope.COOKIE); + SCOPES.put(INITPARAM_PREFIX, Scope.INITPARAM); + SCOPES.put(PAGE_PREFIX, Scope.PAGE); + SCOPES.put(REQUEST_PREFIX, Scope.REQUEST); + SCOPES.put(SESSION_PREFIX, Scope.SESSION); + SCOPES.put(APP_PREFIX, Scope.APPLICATION); + } + + private final PageContext pageContext; + + public JSTLVariableStack(PageContext pageContext) { + super(2); + this.pageContext = pageContext; + } + + @Override + public XObject getVariableOrParam(XPathContext xctxt, QName qname) throws TransformerException { + String prefix = qname.getNamespaceURI(); + String name = qname.getLocalPart(); + Object value = getValue(prefix, name); + if (value == null) { + StringBuilder var = new StringBuilder(); + var.append('$'); + if (prefix != null) { + var.append(prefix); + var.append(':'); + } + var.append(name); + throw new TransformerException(Resources.getMessage("XPATH_UNABLE_TO_RESOLVE_VARIABLE", var.toString())); + } + return XObjectFactory.create(value, xctxt); + } + + private Object getValue(String prefix, String name) { + if (prefix == null) { + return pageContext.findAttribute(name); + } + Scope scope = SCOPES.get(prefix); + switch (scope) { + case PARAM: + return pageContext.getRequest().getParameter(name); + case HEADER: + return ((HttpServletRequest) pageContext.getRequest()).getHeader(name); + case COOKIE: + Cookie[] cookies = ((HttpServletRequest) pageContext.getRequest()).getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals(name)) { + return cookie.getValue(); + } + } + } + return null; + case INITPARAM: + return pageContext.getServletContext().getInitParameter(name); + case PAGE: + return pageContext.getAttribute(name, PageContext.PAGE_SCOPE); + case REQUEST: + return pageContext.getAttribute(name, PageContext.REQUEST_SCOPE); + case SESSION: + return pageContext.getAttribute(name, PageContext.SESSION_SCOPE); + case APPLICATION: + return pageContext.getAttribute(name, PageContext.APPLICATION_SCOPE); + default: + throw new AssertionError(); + } + } +} Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/ParseSupport.java =================================================================== --- jakarta-taglibs-standard-1.1.2-src.orig/standard/src/org/apache/taglibs/standard/tag/common/xml/ParseSupport.java +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/ParseSupport.java @@ -16,36 +16,26 @@ package org.apache.taglibs.standard.tag.common.xml; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.io.Reader; import java.io.StringReader; -import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspTagException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.BodyTagSupport; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; -import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import org.apache.taglibs.standard.resources.Resources; -import org.apache.taglibs.standard.tag.common.core.ImportSupport; import org.apache.taglibs.standard.tag.common.core.Util; import org.w3c.dom.Document; -import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLFilter; import org.xml.sax.XMLReader; -import org.xml.sax.helpers.XMLReaderFactory; /** *

Support for tag handlers for <parse>, the XML parsing tag.

@@ -68,12 +58,7 @@ public abstract class ParseSupport exten private String varDom; // 'varDom' attribute private int scope; // processed 'scope' attr private int scopeDom; // processed 'scopeDom' attr - - // state in support of XML parsing... - private DocumentBuilderFactory dbf; - private DocumentBuilder db; - private TransformerFactory tf; - private TransformerHandler th; + private XmlUtil.JstlEntityResolver entityResolver; //********************************************************************* @@ -89,76 +74,50 @@ public abstract class ParseSupport exten xml = null; systemId = null; filter = null; - dbf = null; - db = null; - tf = null; - th = null; scope = PageContext.PAGE_SCOPE; scopeDom = PageContext.PAGE_SCOPE; } - //********************************************************************* // Tag logic // parse 'source' or body, storing result in 'var' public int doEndTag() throws JspException { - try { - - // set up our DocumentBuilder - if (dbf == null) { - dbf = DocumentBuilderFactory.newInstance(); - dbf.setNamespaceAware(true); - dbf.setValidating(false); + // produce a Document by parsing whatever the attributes tell us to use + Object xmlText = this.xml; + if (xmlText == null) { + // if the attribute was specified, use the body as 'xml' + if (bodyContent != null && bodyContent.getString() != null) { + xmlText = bodyContent.getString().trim(); + } else { + xmlText = ""; + } + } + if (xmlText instanceof String) { + xmlText = new StringReader((String) xmlText); + } + if (!(xmlText instanceof Reader)) { + throw new JspTagException(Resources.getMessage("PARSE_INVALID_SOURCE")); + } + InputSource source = XmlUtil.newInputSource(((Reader) xmlText), systemId); + + Document d; + if (filter != null) { + d = parseInputSourceWithFilter(source, filter); + } else { + d = parseInputSource(source); + } + + // we've got a Document object; store it out as appropriate + // (let any exclusivity or other constraints be enforced by TEI/TLV) + if (var != null) { + pageContext.setAttribute(var, d, scope); + } + if (varDom != null) { + pageContext.setAttribute(varDom, d, scopeDom); } - db = dbf.newDocumentBuilder(); - // if we've gotten a filter, set up a transformer to support it - if (filter != null) { - if (tf == null) - tf = TransformerFactory.newInstance(); - if (!tf.getFeature(SAXTransformerFactory.FEATURE)) - throw new JspTagException( - Resources.getMessage("PARSE_NO_SAXTRANSFORMER")); - SAXTransformerFactory stf = (SAXTransformerFactory) tf; - th = stf.newTransformerHandler(); - } - - // produce a Document by parsing whatever the attributes tell us to use - Document d; - Object xmlText = this.xml; - if (xmlText == null) { - // if the attribute was specified, use the body as 'xml' - if (bodyContent != null && bodyContent.getString() != null) - xmlText = bodyContent.getString().trim(); - else - xmlText = ""; - } - if (xmlText instanceof String) - d = parseStringWithFilter((String) xmlText, filter); - else if (xmlText instanceof Reader) - d = parseReaderWithFilter((Reader) xmlText, filter); - else - throw new JspTagException( - Resources.getMessage("PARSE_INVALID_SOURCE")); - - // we've got a Document object; store it out as appropriate - // (let any exclusivity or other constraints be enforced by TEI/TLV) - if (var != null) - pageContext.setAttribute(var, d, scope); - if (varDom != null) - pageContext.setAttribute(varDom, d, scopeDom); - - return EVAL_PAGE; - } catch (SAXException ex) { - throw new JspException(ex); - } catch (IOException ex) { - throw new JspException(ex); - } catch (ParserConfigurationException ex) { - throw new JspException(ex); - } catch (TransformerConfigurationException ex) { - throw new JspException(ex); - } + return EVAL_PAGE; } // Releases any resources we may have (or inherit) @@ -171,126 +130,48 @@ public abstract class ParseSupport exten // Private utility methods /** Parses the given InputSource after, applying the given XMLFilter. */ - private Document parseInputSourceWithFilter(InputSource s, XMLFilter f) - throws SAXException, IOException { - if (f != null) { - // prepare an output Document - Document o = db.newDocument(); - - // use TrAX to adapt SAX events to a Document object - th.setResult(new DOMResult(o)); - XMLReader xr = XMLReaderFactory.createXMLReader(); - xr.setEntityResolver(new JstlEntityResolver(pageContext)); + private Document parseInputSourceWithFilter(InputSource s, XMLFilter f) throws JspException { + try { + XMLReader xr = XmlUtil.newXMLReader(entityResolver); // (note that we overwrite the filter's parent. this seems // to be expected usage. we could cache and reset the old // parent, but you can't setParent(null), so this wouldn't // be perfect.) f.setParent(xr); - f.setContentHandler(th); - f.parse(s); - return o; - } else - return parseInputSource(s); - } - /** Parses the given Reader after applying the given XMLFilter. */ - private Document parseReaderWithFilter(Reader r, XMLFilter f) - throws SAXException, IOException { - return parseInputSourceWithFilter(new InputSource(r), f); - } + TransformerHandler th = XmlUtil.newTransformerHandler(); + Document o = XmlUtil.newEmptyDocument(); + th.setResult(new DOMResult(o)); - /** Parses the given String after applying the given XMLFilter. */ - private Document parseStringWithFilter(String s, XMLFilter f) - throws SAXException, IOException { - StringReader r = new StringReader(s); - return parseReaderWithFilter(r, f); - } + f.setContentHandler(th); - /** Parses the given Reader after applying the given XMLFilter. */ - private Document parseURLWithFilter(String url, XMLFilter f) - throws SAXException, IOException { - return parseInputSourceWithFilter(new InputSource(url), f); + f.parse(s); + return o; + } catch (IOException e) { + throw new JspException(e); + } catch (SAXException e) { + throw new JspException(e); + } catch (TransformerConfigurationException e) { + throw new JspException(e); + } } /** Parses the given InputSource into a Document. */ - private Document parseInputSource(InputSource s) - throws SAXException, IOException { - db.setEntityResolver(new JstlEntityResolver(pageContext)); - - // normalize URIs so they can be processed consistently by resolver - if (systemId == null) - s.setSystemId("jstl:"); - else if (ImportSupport.isAbsoluteUrl(systemId)) - s.setSystemId(systemId); - else - s.setSystemId("jstl:" + systemId); - return db.parse(s); - } - - /** Parses the given Reader into a Document. */ - private Document parseReader(Reader r) throws SAXException, IOException { - return parseInputSource(new InputSource(r)); - } - - /** Parses the given String into a Document. */ - private Document parseString(String s) throws SAXException, IOException { - StringReader r = new StringReader(s); - return parseReader(r); - } - - /** Parses the URL (passed as a String) into a Document. */ - private Document parseURL(String url) throws SAXException, IOException { - return parseInputSource(new InputSource(url)); - } - - //********************************************************************* - // JSTL-specific EntityResolver class - - /** Lets us resolve relative external entities. */ - public static class JstlEntityResolver implements EntityResolver { - private final PageContext ctx; - public JstlEntityResolver(PageContext ctx) { - this.ctx = ctx; + private Document parseInputSource(InputSource s) throws JspException { + try { + DocumentBuilder db = XmlUtil.newDocumentBuilder(); + db.setEntityResolver(entityResolver); + return db.parse(s); + } catch (SAXException e) { + throw new JspException(e); + } catch (IOException e) { + throw new JspException(e); } - public InputSource resolveEntity(String publicId, String systemId) - throws FileNotFoundException { + } - // pass if we don't have a systemId - if (systemId == null) - return null; - - // strip leading "jstl:" off URL if applicable - if (systemId.startsWith("jstl:")) - systemId = systemId.substring(5); - - // we're only concerned with relative URLs - if (ImportSupport.isAbsoluteUrl(systemId)) - return null; - - // for relative URLs, load and wrap the resource. - // don't bother checking for 'null' since we specifically want - // the parser to fail if the resource doesn't exist - InputStream s; - if (systemId.startsWith("/")) { - s = ctx.getServletContext().getResourceAsStream(systemId); - if (s == null) - throw new FileNotFoundException( - Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY", - systemId)); - } else { - String pagePath = - ((HttpServletRequest) ctx.getRequest()).getServletPath(); - String basePath = - pagePath.substring(0, pagePath.lastIndexOf("/")); - s = ctx.getServletContext().getResourceAsStream( - basePath + "/" + systemId); - if (s == null) - throw new FileNotFoundException( - Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY", - systemId)); - } - return new InputSource(s); - } + public void setPageContext(PageContext pageContext) { + super.setPageContext(pageContext); + entityResolver = pageContext == null ? null: new XmlUtil.JstlEntityResolver(pageContext); } //********************************************************************* Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/TransformSupport.java =================================================================== --- jakarta-taglibs-standard-1.1.2-src.orig/standard/src/org/apache/taglibs/standard/tag/common/xml/TransformSupport.java +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/TransformSupport.java @@ -16,43 +16,29 @@ package org.apache.taglibs.standard.tag.common.xml; -import java.io.IOException; -import java.io.InputStream; import java.io.Reader; import java.io.StringReader; -import java.io.Writer; import java.util.List; -import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspTagException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.BodyTagSupport; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.URIResolver; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; import org.apache.taglibs.standard.resources.Resources; -import org.apache.taglibs.standard.tag.common.core.ImportSupport; import org.apache.taglibs.standard.tag.common.core.Util; +import org.apache.taglibs.standard.util.UnclosableWriter; import org.w3c.dom.Document; import org.w3c.dom.Node; -import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.XMLReaderFactory; /** *

Support for tag handlers for <transform>, the XML transformation @@ -66,6 +52,7 @@ public abstract class TransformSupport e // Protected state protected Object xml; // attribute + protected boolean xmlSpecified; // true if xml attribute was specified protected String xmlSystemId; // attribute protected Object xslt; // attribute protected String xsltSystemId; // attribute @@ -77,25 +64,22 @@ public abstract class TransformSupport e private String var; // 'var' attribute private int scope; // processed 'scope' attr private Transformer t; // actual Transformer - private TransformerFactory tf; // reusable factory - private DocumentBuilder db; // reusable factory - private DocumentBuilderFactory dbf; // reusable factory - + private XmlUtil.JstlEntityResolver entityResolver; + private XmlUtil.JstlUriResolver uriResolver; //********************************************************************* // Constructor and initialization public TransformSupport() { - super(); init(); } private void init() { xml = xslt = null; + xmlSpecified = false; xmlSystemId = xsltSystemId = null; var = null; result = null; - tf = null; scope = PageContext.PAGE_SCOPE; } @@ -104,107 +88,70 @@ public abstract class TransformSupport e // Tag logic public int doStartTag() throws JspException { - /* - * We can set up our Transformer here, so we do so, and we let - * it receive parameters directly from subtags (instead of - * caching them. - */ - try { - - //************************************ - // Initialize - - // set up our DocumentBuilderFactory if necessary - if (dbf == null) { - dbf = DocumentBuilderFactory.newInstance(); - dbf.setNamespaceAware(true); - dbf.setValidating(false); - } - if (db == null) - db = dbf.newDocumentBuilder(); - - // set up the TransformerFactory if necessary - if (tf == null) - tf = TransformerFactory.newInstance(); - - //************************************ - // Produce transformer - - Source s; - if (xslt != null) { - if (!(xslt instanceof String) && !(xslt instanceof Reader) - && !(xslt instanceof javax.xml.transform.Source)) - throw new JspTagException( - Resources.getMessage("TRANSFORM_XSLT_UNRECOGNIZED")); - s = getSource(xslt, xsltSystemId); - } else { - throw new JspTagException( - Resources.getMessage("TRANSFORM_NO_TRANSFORMER")); - } - tf.setURIResolver(new JstlUriResolver(pageContext)); - t = tf.newTransformer(s); - - return EVAL_BODY_BUFFERED; - - } catch (SAXException ex) { - throw new JspException(ex); - } catch (ParserConfigurationException ex) { - throw new JspException(ex); - } catch (IOException ex) { - throw new JspException(ex); - } catch (TransformerConfigurationException ex) { - throw new JspException(ex); - } + // set up transformer in the start tag so that nested tags can set parameters directly + if (xslt == null) { + throw new JspTagException(Resources.getMessage("TRANSFORM_XSLT_IS_NULL")); + } + + Source source; + try { + if (xslt instanceof Source) { + source = (Source) xslt; + } else if (xslt instanceof String) { + String s = (String) xslt; + s = s.trim(); + if (s.length() == 0) { + throw new JspTagException(Resources.getMessage("TRANSFORM_XSLT_IS_EMPTY")); + } + source = XmlUtil.newSAXSource(new StringReader(s), xsltSystemId, entityResolver); + } else if (xslt instanceof Reader) { + source = XmlUtil.newSAXSource((Reader) xslt, xsltSystemId, entityResolver); + } else { + throw new JspTagException(Resources.getMessage("TRANSFORM_XSLT_UNSUPPORTED_TYPE", xslt.getClass())); + } + } catch (SAXException e) { + throw new JspException(e); + } + + try { + t = XmlUtil.newTransformer(source); + t.setURIResolver(uriResolver); + } catch (TransformerConfigurationException e) { + throw new JspTagException(e); + } catch (RuntimeException e) { + throw e; + } + return EVAL_BODY_BUFFERED; } - // parse 'xml' or body, transform via our Transformer, - // and store as 'var' or through 'result' public int doEndTag() throws JspException { - try { - //************************************ - // Determine source XML + try { + Source source = xmlSpecified ? getSourceFromXmlAttribute() : getSourceFromBodyContent(); - // if we haven't gotten a source, use the body (which may be empty) - Object xml = this.xml; - if (xml == null) // still equal - if (bodyContent != null && bodyContent.getString() != null) - xml = bodyContent.getString().trim(); - else - xml = ""; - - // let the Source be with you - Source source = getSource(xml, xmlSystemId); - - //************************************ - // Conduct the transformation - - // we can assume at most one of 'var' or 'result' is specified - if (result != null) - // we can write directly to the Result - t.transform(source, result); - else if (var != null) { - // we need a Document - Document d = db.newDocument(); - Result doc = new DOMResult(d); - t.transform(source, doc); - pageContext.setAttribute(var, d, scope); - } else { - Result page = - new StreamResult(new SafeWriter(pageContext.getOut())); - t.transform(source, page); - } - - return EVAL_PAGE; - } catch (SAXException ex) { - throw new JspException(ex); - } catch (ParserConfigurationException ex) { - throw new JspException(ex); - } catch (IOException ex) { - throw new JspException(ex); - } catch (TransformerException ex) { - throw new JspException(ex); - } + // Conduct the transformation + if (var != null) { + // Save the result to var. + Document d = XmlUtil.newEmptyDocument(); + Result doc = new DOMResult(d); + t.transform(source, doc); + pageContext.setAttribute(var, d, scope); + } else { + // Write to out if result is not specified. + Result out = result; + if (out == null) { + out = new StreamResult(new UnclosableWriter(pageContext.getOut())); + } + t.transform(source, out); + } + return EVAL_PAGE; + } catch (TransformerException ex) { + throw new JspException(ex); + } catch (SAXException e) { + throw new JspException(e); + } finally { + t = null; + } } // Releases any resources we may have (or inherit) @@ -212,6 +159,11 @@ public abstract class TransformSupport e init(); } + public void setPageContext(PageContext pageContext) { + super.setPageContext(pageContext); + uriResolver = pageContext == null ? null : new XmlUtil.JstlUriResolver(pageContext); + entityResolver = pageContext == null ? null : new XmlUtil.JstlEntityResolver(pageContext); + } //********************************************************************* // Public methods for subtags @@ -226,64 +178,67 @@ public abstract class TransformSupport e // Utility methods /** - * Wraps systemId with a "jstl:" prefix to prevent the parser from - * thinking that the URI is truly relative and resolving it against - * the current directory in the filesystem. + * Return the Source for a document specified in the "doc" or "xml" attribute. + * + * @return the document Source + * @throws JspTagException if there is a problem with the attribute */ - private static String wrapSystemId(String systemId) { - if (systemId == null) - return "jstl:"; - else if (ImportSupport.isAbsoluteUrl(systemId)) - return systemId; - else - return ("jstl:" + systemId); + Source getSourceFromXmlAttribute() throws JspTagException, SAXException { + Object xml = this.xml; + if (xml == null) { + throw new JspTagException(Resources.getMessage("TRANSFORM_XML_IS_NULL")); + } + + // other JSTL XML tags may produce a list + if (xml instanceof List) { + List list = (List) xml; + if (list.size() != 1) { + throw new JspTagException(Resources.getMessage("TRANSFORM_XML_LIST_SIZE")); + } + xml = list.get(0); + } + + if (xml instanceof Source) { + return (Source) xml; + } + if (xml instanceof String) { + String s = (String) xml; + s = s.trim(); + if (s.length() == 0) { + throw new JspTagException(Resources.getMessage("TRANSFORM_XML_IS_EMPTY")); + } + return XmlUtil.newSAXSource(new StringReader(s), xmlSystemId, entityResolver); + } + if (xml instanceof Reader) { + return XmlUtil.newSAXSource((Reader) xml, xmlSystemId, entityResolver); + } + if (xml instanceof Node) { + return new DOMSource((Node) xml, xmlSystemId); + } + throw new JspTagException(Resources.getMessage("TRANSFORM_XML_UNSUPPORTED_TYPE", xml.getClass())); } /** - * Retrieves a Source from the given Object, whether it be a String, - * Reader, Node, or other supported types (even a Source already). - * If 'url' is true, then we must be passed a String and will interpret - * it as a URL. A null input always results in a null output. + * Return the Source for a document specified as body content. + * + * @return the document Source + * @throws JspTagException if there is a problem with the body content */ - private Source getSource(Object o, String systemId) - throws SAXException, ParserConfigurationException, IOException { - if (o == null) - return null; - else if (o instanceof Source) { - return (Source) o; - } else if (o instanceof String) { - // if we've got a string, chain to Reader below - return getSource(new StringReader((String) o), systemId); - } else if (o instanceof Reader) { - // explicitly go through SAX to maintain control - // over how relative external entities resolve - XMLReader xr = XMLReaderFactory.createXMLReader(); - xr.setEntityResolver( - new ParseSupport.JstlEntityResolver(pageContext)); - InputSource s = new InputSource((Reader) o); - s.setSystemId(wrapSystemId(systemId)); - Source result = new SAXSource(xr, s); - result.setSystemId(wrapSystemId(systemId)); - return result; - } else if (o instanceof Node) { - return new DOMSource((Node) o); - } else if (o instanceof List) { - // support 1-item List because our XPath processor outputs them - List l = (List) o; - if (l.size() == 1) { - return getSource(l.get(0), systemId); // unwrap List - } else { - throw new IllegalArgumentException( - Resources.getMessage("TRANSFORM_SOURCE_INVALID_LIST")); - } - } else { - throw new IllegalArgumentException( - Resources.getMessage("TRANSFORM_SOURCE_UNRECOGNIZED") - + o.getClass()); - } + Source getSourceFromBodyContent() throws JspTagException, SAXException { + if (bodyContent == null) { + throw new JspTagException(Resources.getMessage("TRANSFORM_BODY_IS_NULL")); + } + String s = bodyContent.getString(); + if (s == null) { + throw new JspTagException(Resources.getMessage("TRANSFORM_BODY_CONTENT_IS_NULL")); + } + s = s.trim(); + if (s.length() == 0) { + throw new JspTagException(Resources.getMessage("TRANSFORM_BODY_IS_EMPTY")); + } + return XmlUtil.newSAXSource(new StringReader(s), xmlSystemId, entityResolver); } - //********************************************************************* // Tag attributes @@ -294,88 +249,4 @@ public abstract class TransformSupport e public void setScope(String scope) { this.scope = Util.getScope(scope); } - - - //********************************************************************* - // Private utility classes - - /** - * A Writer based on a wrapped Writer but ignoring requests to - * close() and flush() it. (Someone must have wrapped the - * toilet in my office similarly...) - */ - private static class SafeWriter extends Writer { - private Writer w; - public SafeWriter(Writer w) { this.w = w; } - public void close() { } - public void flush() { } - public void write(char[] cbuf, int off, int len) throws IOException { - w.write(cbuf, off, len); - } - } - - //********************************************************************* - // JSTL-specific URIResolver class - - /** Lets us resolve relative external entities. */ - private static class JstlUriResolver implements URIResolver { - private final PageContext ctx; - public JstlUriResolver(PageContext ctx) { - this.ctx = ctx; - } - public Source resolve(String href, String base) - throws TransformerException { - - // pass if we don't have a systemId - if (href == null) - return null; - - // remove "jstl" marker from 'base' - // NOTE: how 'base' is determined varies among different Xalan - // xsltc implementations - int index; - if (base != null && (index = base.indexOf("jstl:")) != -1) { - base = base.substring(index + 5); - } - - // we're only concerned with relative URLs - if (ImportSupport.isAbsoluteUrl(href) - || (base != null && ImportSupport.isAbsoluteUrl(base))) - return null; - - // base is relative; remove everything after trailing '/' - if (base == null || base.lastIndexOf("/") == -1) - base = ""; - else - base = base.substring(0, base.lastIndexOf("/") + 1); - - // concatenate to produce the real URL we're interested in - String target = base + href; - - // for relative URLs, load and wrap the resource. - // don't bother checking for 'null' since we specifically want - // the parser to fail if the resource doesn't exist - InputStream s; - if (target.startsWith("/")) { - s = ctx.getServletContext().getResourceAsStream(target); - if (s == null) - throw new TransformerException( - Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY", - href)); - } else { - String pagePath = - ((HttpServletRequest) ctx.getRequest()).getServletPath(); - String basePath = - pagePath.substring(0, pagePath.lastIndexOf("/")); - s = ctx.getServletContext().getResourceAsStream( - basePath + "/" + target); - if (s == null) - throw new TransformerException( - Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY", - href)); - } - return new StreamSource(s); - } - } - } Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/XalanUtil.java =================================================================== --- /dev/null +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/XalanUtil.java @@ -0,0 +1,90 @@ +/* + * 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 org.apache.taglibs.standard.tag.common.xml; + +import javax.servlet.jsp.PageContext; +import javax.servlet.jsp.tagext.Tag; +import javax.servlet.jsp.tagext.TagSupport; +import javax.xml.transform.TransformerException; + +import org.apache.xpath.VariableStack; +import org.apache.xpath.XPathContext; +import org.apache.xpath.objects.XBoolean; +import org.apache.xpath.objects.XNodeSet; +import org.apache.xpath.objects.XNumber; +import org.apache.xpath.objects.XObject; +import org.apache.xpath.objects.XString; +import org.w3c.dom.NodeList; + +/** + */ +public class XalanUtil { + /** + * Return the XPathContext to be used for evaluating expressions. + * + * If the child is nested withing a forEach tag its iteration context is used. + * Otherwise, a new context is created based on an empty Document. + * + * @param child the tag whose context should be returned + * @param pageContext the current page context + * @return the XPath evaluation context + */ + public static XPathContext getContext(Tag child, PageContext pageContext) { + // if within a forEach tag, use its context + ForEachTag forEachTag = (ForEachTag) TagSupport.findAncestorWithClass(child, ForEachTag.class); + if (forEachTag != null) { + throw new UnsupportedOperationException("getContext: not implemented method in org.apache.taglibs.standard.tag.common.xml.ForEachTag class!"); + //return forEachTag.getContext(); + } + + // otherwise, create a new context referring to an empty document + XPathContext context = new XPathContext(false); + VariableStack variableStack = new JSTLVariableStack(pageContext); + context.setVarStack(variableStack); + int dtm = context.getDTMHandleFromNode(XmlUtil.newEmptyDocument()); + context.pushCurrentNodeAndExpression(dtm, dtm); + return context; + } + + /** + * Return the Java value corresponding to an XPath result. + * + * @param xo the XPath type + * @return the corresponding Java value per the JSTL mapping rules + * @throws TransformerException if there was a problem converting the type + */ + static Object coerceToJava(XObject xo) throws TransformerException { + if (xo instanceof XBoolean) { + return xo.bool(); + } else if (xo instanceof XNumber) { + return xo.num(); + } else if (xo instanceof XString) { + return xo.str(); + } else if (xo instanceof XNodeSet) { + NodeList nodes = xo.nodelist(); + // if there is only one node in the nodeset return it rather than the list + if (nodes.getLength() == 1) { + return nodes.item(0); + } else { + return nodes; + } + } else { + // unexpected result type + throw new AssertionError(); + } + } +} Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/XmlUtil.java =================================================================== --- /dev/null +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tag/common/xml/XmlUtil.java @@ -0,0 +1,279 @@ +/* + * 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 org.apache.taglibs.standard.tag.common.xml; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.Reader; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.jsp.PageContext; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.URIResolver; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamSource; + +import org.apache.taglibs.standard.resources.Resources; +import org.apache.taglibs.standard.util.UrlUtil; +import org.w3c.dom.Document; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * Utilities for working with JAXP and SAX. + */ +public class XmlUtil { + private static final DocumentBuilderFactory dbf; + private static final SAXTransformerFactory stf; + + static { + // from Java5 on DocumentBuilderFactory is thread safe and hence can be cached + dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setValidating(false); + try { + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (ParserConfigurationException e) { + throw new AssertionError("Parser does not support secure processing"); + } + + TransformerFactory tf = TransformerFactory.newInstance(); + if (!(tf instanceof SAXTransformerFactory)) { + throw new AssertionError("TransformerFactory does not support SAX"); + } + try { + tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (TransformerConfigurationException e) { + throw new AssertionError("TransformerFactory does not support secure processing"); + } + stf = (SAXTransformerFactory) tf; + } + + + /** + * Create a new empty document. + * + * This method always allocates a new document as its root node might be + * exposed to other tags and potentially be mutated. + * + * @return a new empty document + */ + static Document newEmptyDocument() { + return newDocumentBuilder().newDocument(); + } + + /** + * Create a new DocumentBuilder configured for namespaces but not validating. + * + * @return a new, configured DocumentBuilder + */ + static DocumentBuilder newDocumentBuilder() { + try { + return dbf.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new AssertionError(); + } + } + + /** + * Create a new TransformerHandler. + * @return a new TransformerHandler + */ + static TransformerHandler newTransformerHandler() throws TransformerConfigurationException { + return stf.newTransformerHandler(); + } + + static Transformer newTransformer(Source source) throws TransformerConfigurationException { + Transformer transformer = stf.newTransformer(source); + if (transformer == null) { + throw new TransformerConfigurationException("newTransformer returned null"); + } + return transformer; + } + + /** + * Create an InputSource from a Reader. + * + * The systemId will be wrapped for use with JSTL's EntityResolver and UriResolver. + * + * @param reader the source of the XML + * @param systemId the system id + * @return a configured InputSource + */ + static InputSource newInputSource(Reader reader, String systemId) { + InputSource source = new InputSource(reader); + source.setSystemId(wrapSystemId(systemId)); + return source; + } + + /** + * Create an XMLReader that resolves entities using JSTL semantics. + * @param entityResolver for resolving using JSTL semamtics + * @return a new XMLReader + * @throws SAXException if there was a problem creating the reader + */ + static XMLReader newXMLReader(JstlEntityResolver entityResolver) throws SAXException { + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + xmlReader.setEntityResolver(entityResolver); + return xmlReader; + } + + /** + * Create a SAXSource from a Reader. Any entities will be resolved using JSTL semantics. + * + * @param reader the source of the XML + * @param systemId the system id + * @param entityResolver for resolving using JSTL semamtics + * @return a new SAXSource + * @throws SAXException if there was a problem creating the source + */ + static SAXSource newSAXSource(Reader reader, String systemId, JstlEntityResolver entityResolver) throws SAXException { + SAXSource source = new SAXSource(newXMLReader(entityResolver), new InputSource(reader)); + source.setSystemId(wrapSystemId(systemId)); + return source; + } + + /** + * Wraps systemId with a "jstl:" prefix to prevent the parser from + * thinking that the URI is truly relative and resolving it against + * the current directory in the filesystem. + */ + private static String wrapSystemId(String systemId) { + if (systemId == null) { + return "jstl:"; + } else if (UrlUtil.isAbsoluteUrl(systemId)) { + return systemId; + } else { + return ("jstl:" + systemId); + } + } + + /** + * JSTL-specific implementation of EntityResolver. + */ + static class JstlEntityResolver implements EntityResolver { + private final PageContext ctx; + + public JstlEntityResolver(PageContext ctx) { + this.ctx = ctx; + } + + public InputSource resolveEntity(String publicId, String systemId) throws FileNotFoundException { + + // pass if we don't have a systemId + if (systemId == null) { + return null; + } + + // strip leading "jstl:" off URL if applicable + if (systemId.startsWith("jstl:")) { + systemId = systemId.substring(5); + } + + // we're only concerned with relative URLs + if (UrlUtil.isAbsoluteUrl(systemId)) { + return null; + } + + // for relative URLs, load and wrap the resource. + // don't bother checking for 'null' since we specifically want + // the parser to fail if the resource doesn't exist + String path = systemId; + if (!path.startsWith("/")) { + String pagePath = ((HttpServletRequest) ctx.getRequest()).getServletPath(); + String basePath = pagePath.substring(0, pagePath.lastIndexOf("/")); + path = basePath + "/" + systemId; + } + + InputStream s = ctx.getServletContext().getResourceAsStream(path); + if (s == null) { + throw new FileNotFoundException(Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY", systemId)); + } + return new InputSource(s); + } + } + + /** + * JSTL-specific implementation of URIResolver. + */ + static class JstlUriResolver implements URIResolver { + private final PageContext ctx; + + public JstlUriResolver(PageContext ctx) { + this.ctx = ctx; + } + + public Source resolve(String href, String base) throws TransformerException { + + // pass if we don't have a systemId + if (href == null) { + return null; + } + + // remove "jstl" marker from 'base' + // NOTE: how 'base' is determined varies among different Xalan + // xsltc implementations + int index; + if (base != null && (index = base.indexOf("jstl:")) != -1) { + base = base.substring(index + 5); + } + + // we're only concerned with relative URLs + if (UrlUtil.isAbsoluteUrl(href) + || (base != null && UrlUtil.isAbsoluteUrl(base))) { + return null; + } + + // base is relative; remove everything after trailing '/' + if (base == null || base.lastIndexOf("/") == -1) { + base = ""; + } else { + base = base.substring(0, base.lastIndexOf("/") + 1); + } + + // concatenate to produce the real URL we're interested in + String target = base + href; + + // for relative URLs, load and wrap the resource. + // don't bother checking for 'null' since we specifically want + // the parser to fail if the resource doesn't exist + if (!target.startsWith("/")) { + String pagePath = ((HttpServletRequest) ctx.getRequest()).getServletPath(); + String basePath = pagePath.substring(0, pagePath.lastIndexOf("/")); + target = basePath + "/" + target; + } + InputStream s = ctx.getServletContext().getResourceAsStream(target); + if (s == null) { + throw new TransformerException(Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY", href)); + } + return new StreamSource(s); + } + } +} Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tlv/JstlBaseTLV.java =================================================================== --- jakarta-taglibs-standard-1.1.2-src.orig/standard/src/org/apache/taglibs/standard/tlv/JstlBaseTLV.java +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/tlv/JstlBaseTLV.java @@ -17,6 +17,7 @@ package org.apache.taglibs.standard.tlv; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -30,15 +31,15 @@ import javax.servlet.jsp.tagext.PageData import javax.servlet.jsp.tagext.TagData; import javax.servlet.jsp.tagext.TagLibraryValidator; import javax.servlet.jsp.tagext.ValidationMessage; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; import org.apache.taglibs.standard.lang.support.ExpressionEvaluator; import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager; import org.apache.taglibs.standard.resources.Resources; +import org.apache.taglibs.standard.util.XmlUtil; import org.xml.sax.Attributes; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; /** @@ -149,11 +150,19 @@ public abstract class JstlBaseTLV extend DefaultHandler h = getHandler(); // parse the page - SAXParserFactory f = SAXParserFactory.newInstance(); - f.setValidating(false); - f.setNamespaceAware(true); - SAXParser p = f.newSAXParser(); - p.parse(page.getInputStream(), h); + XMLReader xmlReader = XmlUtil.newXMLReader(null); + xmlReader.setContentHandler(h); + InputStream inputStream = page.getInputStream(); + try { + xmlReader.parse(new InputSource(inputStream)); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + // Suppressed. + } + } + if (messageVector.size() == 0) return null; @@ -162,8 +171,6 @@ public abstract class JstlBaseTLV extend } catch (SAXException ex) { return vmFromString(ex.toString()); - } catch (ParserConfigurationException ex) { - return vmFromString(ex.toString()); } catch (IOException ex) { return vmFromString(ex.toString()); } Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/util/UnclosableWriter.java =================================================================== --- /dev/null +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/util/UnclosableWriter.java @@ -0,0 +1,44 @@ +/* + * 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 org.apache.taglibs.standard.util; + +import java.io.IOException; +import java.io.Writer; + +/** + * A Writer based on a wrapped Writer but ignoring requests to + * close() and flush() it. (Someone must have wrapped the + * toilet in my office similarly...) + */ +public class UnclosableWriter extends Writer { + // TODO: shouldn't we be delegating all methods? + private Writer w; + + public UnclosableWriter(Writer w) { + this.w = w; + } + + public void close() { + } + + public void flush() { + } + + public void write(char[] cbuf, int off, int len) throws IOException { + w.write(cbuf, off, len); + } +} Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/util/UrlUtil.java =================================================================== --- /dev/null +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/util/UrlUtil.java @@ -0,0 +1,80 @@ +/* + * 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 org.apache.taglibs.standard.util; + +import java.util.BitSet; + +/** + * Utilities for working with URLs. + */ +public class UrlUtil { + /** + *

Valid characters in a scheme.

+ *

RFC 1738 says the following:

+ *
+ * Scheme names consist of a sequence of characters. The lower + * case letters "a"--"z", digits, and the characters plus ("+"), + * period ("."), and hyphen ("-") are allowed. For resiliency, + * programs interpreting URLs should treat upper case letters as + * equivalent to lower case in scheme names (e.g., allow "HTTP" as + * well as "http"). + *
+ *

We treat as absolute any URL that begins with such a scheme name, + * followed by a colon.

+ */ +/* + private static final String VALID_SCHEME_CHARS = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-"; +*/ + private static final BitSet VALID_SCHEME_CHARS; + static { + VALID_SCHEME_CHARS = new BitSet(128); + VALID_SCHEME_CHARS.set('A', 'Z' + 1); + VALID_SCHEME_CHARS.set('a', 'z' + 1); + VALID_SCHEME_CHARS.set('0', '9' + 1); + VALID_SCHEME_CHARS.set('+'); + VALID_SCHEME_CHARS.set('.'); + VALID_SCHEME_CHARS.set('-'); + } + + /** + * Determine if a URL is absolute by JSTL's definition. + */ + public static boolean isAbsoluteUrl(String url) { + // a null URL is not absolute, by our definition + if (url == null) { + return false; + } + + // do a fast, simple check first + int colonPos = url.indexOf(":"); + if (colonPos == -1) { + return false; + } + + // if we DO have a colon, make sure that every character + // leading up to it is a valid scheme character + for (int i = 0; i < colonPos; i++) { + if (!VALID_SCHEME_CHARS.get(url.charAt(i))) { + return false; + } + } + + // if so, we've got an absolute url + return true; + } +} Index: jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/util/XmlUtil.java =================================================================== --- /dev/null +++ jakarta-taglibs-standard-1.1.2-src/standard/src/org/apache/taglibs/standard/util/XmlUtil.java @@ -0,0 +1,345 @@ +/* + * 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 org.apache.taglibs.standard.util; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.Reader; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.concurrent.Callable; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.jsp.PageContext; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.URIResolver; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamSource; + +import org.apache.taglibs.standard.resources.Resources; +import org.w3c.dom.Document; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * Utilities for working with JAXP and SAX. + */ +public class XmlUtil { + /* Cache factory classes when this class is initialized (since Java1.5 factories are required + * to be thread safe). + * + * As JavaEE 5 requires JSTL to be provided by the container we use our ClassLoader to locate + * the implementations rather than the application's. As we don't know the actual implementation + * class in use we can't use the newInstance() variant that allows the ClassLoader to be + * specified so we use the no-arg form and coerce the TCCL (which may be restricted by the + * AccessController). + */ + private static final DocumentBuilderFactory PARSER_FACTORY; + private static final SAXTransformerFactory TRANSFORMER_FACTORY; + static { + try { + PARSER_FACTORY = runWithOurClassLoader(new Callable() { + public DocumentBuilderFactory call() throws ParserConfigurationException { + return DocumentBuilderFactory.newInstance(); + } + }, ParserConfigurationException.class); + PARSER_FACTORY.setNamespaceAware(true); + PARSER_FACTORY.setValidating(false); + PARSER_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (ParserConfigurationException e) { + throw new ExceptionInInitializerError(e); + } + try { + TRANSFORMER_FACTORY = runWithOurClassLoader(new Callable() { + public SAXTransformerFactory call() throws TransformerConfigurationException { + TransformerFactory tf = TransformerFactory.newInstance(); + if (!(tf instanceof SAXTransformerFactory)) { + throw new TransformerConfigurationException("TransformerFactory does not support SAX"); + } + return (SAXTransformerFactory) tf; + } + }, TransformerConfigurationException.class); + TRANSFORMER_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (TransformerConfigurationException e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Create a new empty document. + * + * @return a new empty document + */ + public static Document newEmptyDocument() { + return newDocumentBuilder().newDocument(); + } + + /** + * Create a new DocumentBuilder configured for namespaces but not validating. + * + * @return a new, configured DocumentBuilder + */ + public static DocumentBuilder newDocumentBuilder() { + try { + return PARSER_FACTORY.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw (Error) new AssertionError().initCause(e); + } + } + + /** + * Create a new TransformerHandler. + * @return a new TransformerHandler + */ + public static TransformerHandler newTransformerHandler() throws TransformerConfigurationException { + return TRANSFORMER_FACTORY.newTransformerHandler(); + } + + /** + * Create a new Transformer from an XSLT. + * @param source the source of the XSLT. + * @return a new Transformer + * @throws TransformerConfigurationException if there was a problem creating the Transformer from the XSLT + */ + public static Transformer newTransformer(Source source) throws TransformerConfigurationException { + Transformer transformer = TRANSFORMER_FACTORY.newTransformer(source); + // Although newTansformer() is not allowed to return null, Xalan does. + // Trap that here by throwing the expected TransformerConfigurationException. + if (transformer == null) { + throw new TransformerConfigurationException("newTransformer returned null. XSLT may be invalid."); + } + return transformer; + } + + /** + * Create an InputSource from a Reader. + * + * The systemId will be wrapped for use with JSTL's EntityResolver and UriResolver. + * + * @param reader the source of the XML + * @param systemId the system id + * @return a configured InputSource + */ + public static InputSource newInputSource(Reader reader, String systemId) { + InputSource source = new InputSource(reader); + source.setSystemId(wrapSystemId(systemId)); + return source; + } + + /** + * Create an XMLReader that resolves entities using JSTL semantics. + * @param entityResolver for resolving using JSTL semamtics + * @return a new XMLReader + * @throws SAXException if there was a problem creating the reader + */ + public static XMLReader newXMLReader(JstlEntityResolver entityResolver) throws SAXException { + XMLReader xmlReader = runWithOurClassLoader(new Callable() { + public XMLReader call() throws SAXException { + return XMLReaderFactory.createXMLReader(); + } + }, SAXException.class); + xmlReader.setEntityResolver(entityResolver); + xmlReader.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + return xmlReader; + } + + /** + * Create a SAXSource from a Reader. Any entities will be resolved using JSTL semantics. + * + * @param reader the source of the XML + * @param systemId the system id + * @param entityResolver for resolving using JSTL semamtics + * @return a new SAXSource + * @throws SAXException if there was a problem creating the source + */ + public static SAXSource newSAXSource(Reader reader, String systemId, JstlEntityResolver entityResolver) throws SAXException { + SAXSource source = new SAXSource(newXMLReader(entityResolver), new InputSource(reader)); + source.setSystemId(wrapSystemId(systemId)); + return source; + } + + /** + * Wraps systemId with a "jstl:" prefix to prevent the parser from + * thinking that the URI is truly relative and resolving it against + * the current directory in the filesystem. + */ + private static String wrapSystemId(String systemId) { + if (systemId == null) { + return "jstl:"; + } else if (UrlUtil.isAbsoluteUrl(systemId)) { + return systemId; + } else { + return ("jstl:" + systemId); + } + } + + /** + * JSTL-specific implementation of EntityResolver. + */ + public static class JstlEntityResolver implements EntityResolver { + private final PageContext ctx; + + public JstlEntityResolver(PageContext ctx) { + this.ctx = ctx; + } + + public InputSource resolveEntity(String publicId, String systemId) throws FileNotFoundException { + + // pass if we don't have a systemId + if (systemId == null) { + return null; + } + + // strip leading "jstl:" off URL if applicable + if (systemId.startsWith("jstl:")) { + systemId = systemId.substring(5); + } + + // we're only concerned with relative URLs + if (UrlUtil.isAbsoluteUrl(systemId)) { + return null; + } + + // for relative URLs, load and wrap the resource. + // don't bother checking for 'null' since we specifically want + // the parser to fail if the resource doesn't exist + String path = systemId; + if (!path.startsWith("/")) { + String pagePath = ((HttpServletRequest) ctx.getRequest()).getServletPath(); + String basePath = pagePath.substring(0, pagePath.lastIndexOf("/")); + path = basePath + "/" + systemId; + } + + InputStream s = ctx.getServletContext().getResourceAsStream(path); + if (s == null) { + throw new FileNotFoundException(Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY", systemId)); + } + return new InputSource(s); + } + } + + /** + * JSTL-specific implementation of URIResolver. + */ + public static class JstlUriResolver implements URIResolver { + private final PageContext ctx; + + public JstlUriResolver(PageContext ctx) { + this.ctx = ctx; + } + + public Source resolve(String href, String base) throws TransformerException { + + // pass if we don't have a systemId + if (href == null) { + return null; + } + + // remove "jstl" marker from 'base' + // NOTE: how 'base' is determined varies among different Xalan + // xsltc implementations + int index; + if (base != null && (index = base.indexOf("jstl:")) != -1) { + base = base.substring(index + 5); + } + + // we're only concerned with relative URLs + if (UrlUtil.isAbsoluteUrl(href) + || (base != null && UrlUtil.isAbsoluteUrl(base))) { + return null; + } + + // base is relative; remove everything after trailing '/' + if (base == null || base.lastIndexOf("/") == -1) { + base = ""; + } else { + base = base.substring(0, base.lastIndexOf("/") + 1); + } + + // concatenate to produce the real URL we're interested in + String target = base + href; + + // for relative URLs, load and wrap the resource. + // don't bother checking for 'null' since we specifically want + // the parser to fail if the resource doesn't exist + if (!target.startsWith("/")) { + String pagePath = ((HttpServletRequest) ctx.getRequest()).getServletPath(); + String basePath = pagePath.substring(0, pagePath.lastIndexOf("/")); + target = basePath + "/" + target; + } + InputStream s = ctx.getServletContext().getResourceAsStream(target); + if (s == null) { + throw new TransformerException(Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY", href)); + } + return new StreamSource(s); + } + } + + /** + * Performs an action using this Class's ClassLoader as the Thread context ClassLoader. + * + * @param action the action to perform + * @param allowed an Exception that might be thrown by the action + * @param the type of the result + * @param the type of the allowed Exception + * @return the result of the action + * @throws E if the action threw the allowed Exception + */ + private static T runWithOurClassLoader(final Callable action, Class allowed) throws E { + PrivilegedExceptionAction actionWithClassloader = new PrivilegedExceptionAction() { + public T run() throws Exception { + ClassLoader original = Thread.currentThread().getContextClassLoader(); + ClassLoader ours = XmlUtil.class.getClassLoader(); + // Don't override the TCCL if it is not needed. + if (original == ours) { + return action.call(); + } else { + try { + Thread.currentThread().setContextClassLoader(ours); + return action.call(); + } finally { + Thread.currentThread().setContextClassLoader(original); + } + } + } + }; + try { + return AccessController.doPrivileged(actionWithClassloader); + } catch (PrivilegedActionException e) { + Throwable cause = e.getCause(); + if (allowed.isInstance(cause)) { + throw allowed.cast(cause); + } else { + throw (Error) new AssertionError().initCause(cause); + } + } + } +}