1
0
jakarta-taglibs-standard/CVE-2015-0254.patch

2270 lines
87 KiB
Diff

Description: Fix CVE-2015-0254 XXE and RCE via XSL extension in JSTL XML tags
When an application uses <x:parse> or <x:transform> 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<SAXParserFactory>() {
+ 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;
/**
* <p>Support for tag handlers for &lt;import&gt;, the general-purpose
@@ -60,22 +61,6 @@ public abstract class ImportSupport exte
//*********************************************************************
// Public constants
- /** <p>Valid characters in a scheme.</p>
- * <p>RFC 1738 says the following:</p>
- * <blockquote>
- * 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").
- * </blockquote>
- * <p>We treat as absolute any URL that begins with such a scheme name,
- * followed by a colon.</p>
- */
- 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 <tt>true</tt> if our current URL is absolute,
- * <tt>false</tt> otherwise.
- */
- private boolean isAbsoluteUrl() throws JspTagException {
- return isAbsoluteUrl(url);
- }
-
-
//*********************************************************************
// Public utility methods
/**
- * Returns <tt>true</tt> if our current URL is absolute,
- * <tt>false</tt> 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 <tt>url</tt>. 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;
+
/**
* <p>Support for tag handlers for &lt;redirect&gt;, JSTL 1.0's tag
* for redirecting to a new URL (with optional query parameters).</p>
@@ -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;
/**
* <p>Support for tag handlers for &lt;url&gt;, 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<String, Scope> SCOPES;
+ static {
+ SCOPES = new HashMap<String, Scope>(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;
/**
* <p>Support for tag handlers for &lt;parse&gt;, the XML parsing tag.</p>
@@ -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;
/**
* <p>Support for tag handlers for &lt;transform&gt;, 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 <param> 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 {
+ /**
+ * <p>Valid characters in a scheme.</p>
+ * <p>RFC 1738 says the following:</p>
+ * <blockquote>
+ * 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").
+ * </blockquote>
+ * <p>We treat as absolute any URL that begins with such a scheme name,
+ * followed by a colon.</p>
+ */
+/*
+ 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<DocumentBuilderFactory>() {
+ 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<SAXTransformerFactory>() {
+ 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<XMLReader>() {
+ 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 <T> the type of the result
+ * @param <E> the type of the allowed Exception
+ * @return the result of the action
+ * @throws E if the action threw the allowed Exception
+ */
+ private static <T, E extends Exception> T runWithOurClassLoader(final Callable<T> action, Class<E> allowed) throws E {
+ PrivilegedExceptionAction<T> actionWithClassloader = new PrivilegedExceptionAction<T>() {
+ 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);
+ }
+ }
+ }
+}