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.1-src/standard/src/javax/servlet/jsp/jstl/tlv/ParserUtil.java
===================================================================
--- /dev/null
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/javax/servlet/jsp/jstl/tlv/PermittedTaglibsTLV.java
===================================================================
--- jakarta-taglibs-standard-1.1.1-src.orig/standard/src/javax/servlet/jsp/jstl/tlv/PermittedTaglibsTLV.java
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/javax/servlet/jsp/jstl/tlv/ScriptFreeTLV.java
===================================================================
--- jakarta-taglibs-standard-1.1.1-src.orig/standard/src/javax/servlet/jsp/jstl/tlv/ScriptFreeTLV.java
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/tag/common/core/ImportSupport.java
===================================================================
--- jakarta-taglibs-standard-1.1.1-src.orig/standard/src/org/apache/taglibs/standard/tag/common/core/ImportSupport.java
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/tag/common/core/RedirectSupport.java
===================================================================
--- jakarta-taglibs-standard-1.1.1-src.orig/standard/src/org/apache/taglibs/standard/tag/common/core/RedirectSupport.java
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/tag/common/core/UrlSupport.java
===================================================================
--- jakarta-taglibs-standard-1.1.1-src.orig/standard/src/org/apache/taglibs/standard/tag/common/core/UrlSupport.java
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/tag/common/xml/JSTLVariableStack.java
===================================================================
--- /dev/null
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/tag/common/xml/ParseSupport.java
===================================================================
--- jakarta-taglibs-standard-1.1.1-src.orig/standard/src/org/apache/taglibs/standard/tag/common/xml/ParseSupport.java
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/tag/common/xml/TransformSupport.java
===================================================================
--- jakarta-taglibs-standard-1.1.1-src.orig/standard/src/org/apache/taglibs/standard/tag/common/xml/TransformSupport.java
+++ jakarta-taglibs-standard-1.1.1-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,84 +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'
-	    if (base != null && base.startsWith("jstl:"))
-		base = base.substring(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.1-src/standard/src/org/apache/taglibs/standard/tag/common/xml/XalanUtil.java
===================================================================
--- /dev/null
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/tag/common/xml/XmlUtil.java
===================================================================
--- /dev/null
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/tlv/JstlBaseTLV.java
===================================================================
--- jakarta-taglibs-standard-1.1.1-src.orig/standard/src/org/apache/taglibs/standard/tlv/JstlBaseTLV.java
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/util/UnclosableWriter.java
===================================================================
--- /dev/null
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/util/UrlUtil.java
===================================================================
--- /dev/null
+++ jakarta-taglibs-standard-1.1.1-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.1-src/standard/src/org/apache/taglibs/standard/util/XmlUtil.java
===================================================================
--- /dev/null
+++ jakarta-taglibs-standard-1.1.1-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);
+            }
+        }
+    }
+}
