ASF Bugzilla – Attachment 30568 Details for
Bug 53737
Use ServletContext.getJspConfigDescriptor() in Jasper instead of XML-parsing of merged web.xml [PATCHES]
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Incremental, adds merging of fragments and scanning using ServletContext
53737-2.patch (text/plain), 54.33 KB, created by
Jeremy Boynes
on 2013-07-08 05:24:05 UTC
(
hide
)
Description:
Incremental, adds merging of fragments and scanning using ServletContext
Filename:
MIME Type:
Creator:
Jeremy Boynes
Created:
2013-07-08 05:24:05 UTC
Size:
54.33 KB
patch
obsolete
>Index: java/org/apache/jasper/JspC.java >=================================================================== >--- java/org/apache/jasper/JspC.java (revision 1500579) >+++ java/org/apache/jasper/JspC.java (working copy) >@@ -30,17 +30,17 @@ > import java.io.PrintWriter; > import java.io.Reader; > import java.io.Writer; >-import java.net.MalformedURLException; > import java.net.URL; > import java.net.URLClassLoader; > import java.util.ArrayList; >+import java.util.Arrays; > import java.util.HashMap; > import java.util.HashSet; > import java.util.Iterator; >+import java.util.LinkedList; > import java.util.List; > import java.util.Map; > import java.util.Set; >-import java.util.Stack; > import java.util.StringTokenizer; > import java.util.Vector; > >@@ -1217,37 +1217,23 @@ > } > > /** >- * Locate all jsp files in the webapp. Used if no explicit >- * jsps are specified. >+ * Locate all jsp files in the webapp, including those from web-fragments. >+ * Used if no explicit jsps are specified. > */ >- public void scanFiles( File base ) throws JasperException { >- Stack<String> dirs = new Stack<>(); >- dirs.push(base.toString()); >- >- // Make sure default extensions are always included >- if ((getExtensions() == null) || (getExtensions().size() < 2)) { >- addExtension("jsp"); >- addExtension("jspx"); >+ public void scanFiles() { >+ if (extensions == null) { >+ extensions = Arrays.asList("jsp", "jspx"); > } >- >- while (!dirs.isEmpty()) { >- String s = dirs.pop(); >- File f = new File(s); >- if (f.exists() && f.isDirectory()) { >- String[] files = f.list(); >- String ext; >- for (int i = 0; (files != null) && i < files.length; i++) { >- File f2 = new File(s, files[i]); >- if (f2.isDirectory()) { >- dirs.push(f2.getPath()); >- } else { >- String path = f2.getPath(); >- String uri = path.substring(uriRoot.length()); >- ext = files[i].substring(files[i].lastIndexOf('.') +1); >- if (getExtensions().contains(ext) || >- jspConfig.isJspPage(uri)) { >- pages.add(path); >- } >+ LinkedList<String> dirs = new LinkedList<>(); >+ dirs.add("/"); >+ for (String dir = dirs.poll(); dir != null; dir = dirs.poll()) { >+ for (String file : context.getResourcePaths(dir)) { >+ if (file.endsWith("/")) { >+ dirs.add(file); >+ } else { >+ String ext = file.substring(file.lastIndexOf('.') + 1); >+ if (extensions.contains(ext) || jspConfig.isJspPage(file)) { >+ pages.add(file.substring(1)); > } > } > } >@@ -1296,7 +1282,7 @@ > > // No explicit pages, we'll process all .jsp in the webapp > if (pages.size() == 0) { >- scanFiles(uriRootF); >+ scanFiles(); > } > > initWebXml(); >@@ -1414,15 +1400,11 @@ > } > } > >- protected void initServletContext() { >- try { >- context =new JspCServletContext >- (new PrintWriter(System.out), >- new URL("file:" + uriRoot.replace('\\','/') + '/')); >- tldLocationsCache = TldLocationsCache.getInstance(context); >- } catch (MalformedURLException me) { >- System.out.println("**" + me); >- } >+ protected void initServletContext() throws IOException, JasperException { >+ // TODO: should we use the Ant Project's log? >+ PrintWriter log = new PrintWriter(System.out); >+ URL resourceBase = new File(uriRoot).getCanonicalFile().toURI().toURL(); >+ context = new JspCServletContext(log, resourceBase); > rctxt = new JspRuntimeContext(context, this); > jspConfig = new JspConfig(context); > tagPluginManager = new TagPluginManager(context); >Index: java/org/apache/jasper/compiler/JspConfig.java >=================================================================== >--- java/org/apache/jasper/compiler/JspConfig.java (revision 1500579) >+++ java/org/apache/jasper/compiler/JspConfig.java (working copy) >@@ -17,14 +17,15 @@ > > package org.apache.jasper.compiler; > >-import java.util.Iterator; >+import java.util.ArrayList; >+import java.util.Collection; >+import java.util.List; > import java.util.Vector; > > import javax.servlet.ServletContext; >+import javax.servlet.descriptor.JspConfigDescriptor; >+import javax.servlet.descriptor.JspPropertyGroupDescriptor; > >-import org.apache.jasper.JasperException; >-import org.apache.jasper.xmlparser.ParserUtils; >-import org.apache.jasper.xmlparser.TreeNode; > import org.apache.juli.logging.Log; > import org.apache.juli.logging.LogFactory; > >@@ -41,209 +42,107 @@ > // Logger > private final Log log = LogFactory.getLog(JspConfig.class); > >- private Vector<JspPropertyGroup> jspProperties = null; >- private final ServletContext ctxt; >- private volatile boolean initialized = false; >+ private final JspProperty defaultJspProperty; >+ private final List<JspPropertyGroup> jspProperties; > >- private static final String defaultIsXml = null; // unspecified >- private String defaultIsELIgnored = null; // unspecified >- private static final String defaultIsScriptingInvalid = null; >- private String defaultDeferedSyntaxAllowedAsLiteral = null; >- private static final String defaultTrimDirectiveWhitespaces = null; >- private static final String defaultDefaultContentType = null; >- private static final String defaultBuffer = null; >- private static final String defaultErrorOnUndeclaredNamespace = "false"; >- private JspProperty defaultJspProperty; >- > public JspConfig(ServletContext ctxt) { >- this.ctxt = ctxt; >- } >+ // set up default behaviour based on effective Servlet specification version >+ int major = ctxt.getEffectiveMajorVersion(); >+ int minor = ctxt.getEffectiveMinorVersion(); > >- private double getVersion(TreeNode webApp) { >- String v = webApp.findAttribute("version"); >- if (v != null) { >- try { >- return Double.parseDouble(v); >- } catch (NumberFormatException e) { >- } >+ if (major < 2 || major == 2 && minor < 4) { >+ // < 2.4 : elIgnored, deferedSyntaxAllowedAsLiteral, and no jsp-config >+ defaultJspProperty = new JspProperty("true", "true"); >+ jspProperties = null; >+ return; >+ } else if (major == 2 && minor == 4) { >+ // 2.4 : deferedSyntaxAllowedAsLiteral >+ defaultJspProperty = new JspProperty(null, "true"); >+ } else { >+ defaultJspProperty = new JspProperty(null, null); > } >- return 2.3; >+ jspProperties = processJspConfig(ctxt); > } > >- private void processWebDotXml() throws JasperException { >+ private List<JspPropertyGroup> processJspConfig(ServletContext ctxt) { >+ JspConfigDescriptor jspConfig = ctxt.getJspConfigDescriptor(); >+ if (jspConfig == null) { >+ // no jsp-config for this application >+ return null; >+ } > >- WebXml webXml = null; >+ Collection<JspPropertyGroupDescriptor> jspPropertyGroups = jspConfig.getJspPropertyGroups(); >+ List<JspPropertyGroup> jspProperties = new ArrayList<>(); >+ for (JspPropertyGroupDescriptor jspPropertyGroup : jspPropertyGroups) { > >- try { >- webXml = new WebXml(ctxt); >- >- TreeNode webApp = null; >- if (webXml.getInputSource() != null) { >- ParserUtils pu = new ParserUtils(); >- webApp = pu.parseXMLDocument(webXml.getSystemId(), >- webXml.getInputSource()); >+ Collection<String> urlPatterns = jspPropertyGroup.getUrlPatterns(); >+ if (urlPatterns.isEmpty()) { >+ continue; > } > >- if (webApp == null >- || getVersion(webApp) < 2.4) { >- defaultIsELIgnored = "true"; >- defaultDeferedSyntaxAllowedAsLiteral = "true"; >- return; >- } >- if (getVersion(webApp) < 2.5) { >- defaultDeferedSyntaxAllowedAsLiteral = "true"; >- } >- TreeNode jspConfig = webApp.findChild("jsp-config"); >- if (jspConfig == null) { >- return; >- } >+ JspProperty property = new JspProperty(jspPropertyGroup.getIsXml(), >+ jspPropertyGroup.getElIgnored(), >+ jspPropertyGroup.getScriptingInvalid(), >+ jspPropertyGroup.getPageEncoding(), >+ new Vector<>(jspPropertyGroup.getIncludePreludes()), >+ new Vector<>(jspPropertyGroup.getIncludeCodas()), >+ jspPropertyGroup.getDeferredSyntaxAllowedAsLiteral(), >+ jspPropertyGroup.getTrimDirectiveWhitespaces(), >+ jspPropertyGroup.getDefaultContentType(), >+ jspPropertyGroup.getBuffer(), >+ jspPropertyGroup.getErrorOnUndeclaredNamespace()); > >- jspProperties = new Vector<>(); >- Iterator<TreeNode> jspPropertyList = >- jspConfig.findChildren("jsp-property-group"); >- while (jspPropertyList.hasNext()) { >+ // Add one JspPropertyGroup for each URL Pattern. This makes >+ // the matching logic easier. >+ for (String urlPattern : urlPatterns) { >+ String path = null; >+ String extension = null; > >- TreeNode element = jspPropertyList.next(); >- Iterator<TreeNode> list = element.findChildren(); >- >- Vector<String> urlPatterns = new Vector<>(); >- String pageEncoding = null; >- String scriptingInvalid = null; >- String elIgnored = null; >- String isXml = null; >- Vector<String> includePrelude = new Vector<>(); >- Vector<String> includeCoda = new Vector<>(); >- String deferredSyntaxAllowedAsLiteral = null; >- String trimDirectiveWhitespaces = null; >- String defaultContentType = null; >- String buffer = null; >- String errorOnUndeclaredNamespace = null; >- >- while (list.hasNext()) { >- >- element = list.next(); >- String tname = element.getName(); >- >- if ("url-pattern".equals(tname)) >- urlPatterns.addElement( element.getBody() ); >- else if ("page-encoding".equals(tname)) >- pageEncoding = element.getBody(); >- else if ("is-xml".equals(tname)) >- isXml = element.getBody(); >- else if ("el-ignored".equals(tname)) >- elIgnored = element.getBody(); >- else if ("scripting-invalid".equals(tname)) >- scriptingInvalid = element.getBody(); >- else if ("include-prelude".equals(tname)) >- includePrelude.addElement(element.getBody()); >- else if ("include-coda".equals(tname)) >- includeCoda.addElement(element.getBody()); >- else if ("deferred-syntax-allowed-as-literal".equals(tname)) >- deferredSyntaxAllowedAsLiteral = element.getBody(); >- else if ("trim-directive-whitespaces".equals(tname)) >- trimDirectiveWhitespaces = element.getBody(); >- else if ("default-content-type".equals(tname)) >- defaultContentType = element.getBody(); >- else if ("buffer".equals(tname)) >- buffer = element.getBody(); >- else if ("error-on-undeclared-namespace".equals(tname)) >- errorOnUndeclaredNamespace = element.getBody(); >- } >- >- if (urlPatterns.size() == 0) { >- continue; >- } >- >- // Add one JspPropertyGroup for each URL Pattern. This makes >- // the matching logic easier. >- for( int p = 0; p < urlPatterns.size(); p++ ) { >- String urlPattern = urlPatterns.elementAt( p ); >- String path = null; >- String extension = null; >- >- if (urlPattern.indexOf('*') < 0) { >- // Exact match >- path = urlPattern; >+ if (urlPattern.indexOf('*') < 0) { >+ // Exact match >+ path = urlPattern; >+ } else { >+ int i = urlPattern.lastIndexOf('/'); >+ String file; >+ if (i >= 0) { >+ path = urlPattern.substring(0, i + 1); >+ file = urlPattern.substring(i + 1); > } else { >- int i = urlPattern.lastIndexOf('/'); >- String file; >- if (i >= 0) { >- path = urlPattern.substring(0,i+1); >- file = urlPattern.substring(i+1); >- } else { >- file = urlPattern; >- } >+ file = urlPattern; >+ } > >- // pattern must be "*", or of the form "*.jsp" >- if (file.equals("*")) { >- extension = "*"; >- } else if (file.startsWith("*.")) { >- extension = file.substring(file.indexOf('.')+1); >- } >+ // pattern must be "*", or of the form "*.jsp" >+ if (file.equals("*")) { >+ extension = "*"; >+ } else if (file.startsWith("*.")) { >+ extension = file.substring(file.indexOf('.') + 1); >+ } > >- // The url patterns are reconstructed as the following: >- // path != null, extension == null: / or /foo/bar.ext >- // path == null, extension != null: *.ext >- // path != null, extension == "*": /foo/* >- boolean isStar = "*".equals(extension); >- if ((path == null && (extension == null || isStar)) >- || (path != null && !isStar)) { >- if (log.isWarnEnabled()) { >- log.warn(Localizer.getMessage( >- "jsp.warning.bad.urlpattern.propertygroup", >- urlPattern)); >- } >- continue; >+ // The url patterns are reconstructed as the following: >+ // path != null, extension == null: / or /foo/bar.ext >+ // path == null, extension != null: *.ext >+ // path != null, extension == "*": /foo/* >+ boolean isStar = "*".equals(extension); >+ if ((path == null && (extension == null || isStar)) >+ || (path != null && !isStar)) { >+ if (log.isWarnEnabled()) { >+ log.warn(Localizer.getMessage( >+ "jsp.warning.bad.urlpattern.propertygroup", >+ urlPattern)); > } >+ continue; > } >+ } > >- JspProperty property = new JspProperty(isXml, >- elIgnored, >- scriptingInvalid, >- pageEncoding, >- includePrelude, >- includeCoda, >- deferredSyntaxAllowedAsLiteral, >- trimDirectiveWhitespaces, >- defaultContentType, >- buffer, >- errorOnUndeclaredNamespace); >- JspPropertyGroup propertyGroup = >+ JspPropertyGroup propertyGroup = > new JspPropertyGroup(path, extension, property); > >- jspProperties.addElement(propertyGroup); >- } >+ jspProperties.add(propertyGroup); > } >- } catch (Exception ex) { >- throw new JasperException(ex); >- } finally { >- if (webXml != null) { >- webXml.close(); >- } > } >+ return jspProperties; > } > >- private void init() throws JasperException { >- >- if (!initialized) { >- synchronized (this) { >- if (!initialized) { >- processWebDotXml(); >- defaultJspProperty = new JspProperty(defaultIsXml, >- defaultIsELIgnored, >- defaultIsScriptingInvalid, >- null, null, null, >- defaultDeferedSyntaxAllowedAsLiteral, >- defaultTrimDirectiveWhitespaces, >- defaultDefaultContentType, >- defaultBuffer, >- defaultErrorOnUndeclaredNamespace); >- initialized = true; >- } >- } >- } >- } >- > /** > * Select the property group that has more restrictive url-pattern. > * In case of tie, select the first. >@@ -268,10 +167,10 @@ > // Both specifies a *.ext, keep the first one > return prev; > } >- if (prevPath == null && currPath != null) { >+ if (prevPath == null) { > return curr; > } >- if (prevPath != null && currPath == null) { >+ if (currPath == null) { > return prev; > } > if (prevPath.length() >= currPath.length()) { >@@ -286,10 +185,8 @@ > * @param uri the resource supplied. > * @return a JspProperty indicating the best match, or some default. > */ >- public JspProperty findJspProperty(String uri) throws JasperException { >+ public JspProperty findJspProperty(String uri) { > >- init(); >- > // JSP Configuration settings do not apply to tag files > if (jspProperties == null || uri.endsWith(".tag") > || uri.endsWith(".tagx")) { >@@ -320,10 +217,8 @@ > JspPropertyGroup bufferMatch = null; > JspPropertyGroup errorOnUndeclaredNamespaceMatch = null; > >- Iterator<JspPropertyGroup> iter = jspProperties.iterator(); >- while (iter.hasNext()) { >+ for (JspPropertyGroup jpg : jspProperties) { > >- JspPropertyGroup jpg = iter.next(); > JspProperty jp = jpg.getJspProperty(); > > // (arrays will be the same length) >@@ -339,7 +234,7 @@ > } else { > // Matching patterns *.ext or /p/* > if (path != null && uriPath != null && >- ! uriPath.startsWith(path)) { >+ !uriPath.startsWith(path)) { > // not matched > continue; > } >@@ -368,90 +263,63 @@ > } > if (jp.isScriptingInvalid() != null) { > scriptingInvalidMatch = >- selectProperty(scriptingInvalidMatch, jpg); >+ selectProperty(scriptingInvalidMatch, jpg); > } > if (jp.getPageEncoding() != null) { > pageEncodingMatch = selectProperty(pageEncodingMatch, jpg); > } > if (jp.isDeferedSyntaxAllowedAsLiteral() != null) { > deferedSyntaxAllowedAsLiteralMatch = >- selectProperty(deferedSyntaxAllowedAsLiteralMatch, jpg); >+ selectProperty(deferedSyntaxAllowedAsLiteralMatch, jpg); > } > if (jp.isTrimDirectiveWhitespaces() != null) { > trimDirectiveWhitespacesMatch = >- selectProperty(trimDirectiveWhitespacesMatch, jpg); >+ selectProperty(trimDirectiveWhitespacesMatch, jpg); > } > if (jp.getDefaultContentType() != null) { > defaultContentTypeMatch = >- selectProperty(defaultContentTypeMatch, jpg); >+ selectProperty(defaultContentTypeMatch, jpg); > } > if (jp.getBuffer() != null) { > bufferMatch = selectProperty(bufferMatch, jpg); > } > if (jp.isErrorOnUndeclaredNamespace() != null) { > errorOnUndeclaredNamespaceMatch = >- selectProperty(errorOnUndeclaredNamespaceMatch, jpg); >+ selectProperty(errorOnUndeclaredNamespaceMatch, jpg); > } > } > > >- String isXml = defaultIsXml; >- String isELIgnored = defaultIsELIgnored; >- String isScriptingInvalid = defaultIsScriptingInvalid; >- String pageEncoding = null; >- String isDeferedSyntaxAllowedAsLiteral = >- defaultDeferedSyntaxAllowedAsLiteral; >- String isTrimDirectiveWhitespaces = defaultTrimDirectiveWhitespaces; >- String defaultContentType = defaultDefaultContentType; >- String buffer = defaultBuffer; >- String errorOnUndelcaredNamespace = defaultErrorOnUndeclaredNamespace; >+ String isXml = defaultProperty(isXmlMatch).isXml(); >+ String isELIgnored = defaultProperty(elIgnoredMatch).isELIgnored(); >+ String isScriptingInvalid = defaultProperty(scriptingInvalidMatch).isScriptingInvalid(); >+ String pageEncoding = defaultProperty(pageEncodingMatch).getPageEncoding(); >+ String isDeferedSyntaxAllowedAsLiteral = defaultProperty(defaultContentTypeMatch).isDeferedSyntaxAllowedAsLiteral(); >+ String isTrimDirectiveWhitespaces = defaultProperty(trimDirectiveWhitespacesMatch).isTrimDirectiveWhitespaces(); >+ String defaultContentType = defaultProperty(defaultContentTypeMatch).getDefaultContentType(); >+ String buffer = defaultProperty(bufferMatch).getBuffer(); >+ String errorOnUndeclaredNamespace = defaultProperty(errorOnUndeclaredNamespaceMatch).isErrorOnUndeclaredNamespace(); > >- if (isXmlMatch != null) { >- isXml = isXmlMatch.getJspProperty().isXml(); >- } >- if (elIgnoredMatch != null) { >- isELIgnored = elIgnoredMatch.getJspProperty().isELIgnored(); >- } >- if (scriptingInvalidMatch != null) { >- isScriptingInvalid = >- scriptingInvalidMatch.getJspProperty().isScriptingInvalid(); >- } >- if (pageEncodingMatch != null) { >- pageEncoding = pageEncodingMatch.getJspProperty().getPageEncoding(); >- } >- if (deferedSyntaxAllowedAsLiteralMatch != null) { >- isDeferedSyntaxAllowedAsLiteral = >- deferedSyntaxAllowedAsLiteralMatch.getJspProperty().isDeferedSyntaxAllowedAsLiteral(); >- } >- if (trimDirectiveWhitespacesMatch != null) { >- isTrimDirectiveWhitespaces = >- trimDirectiveWhitespacesMatch.getJspProperty().isTrimDirectiveWhitespaces(); >- } >- if (defaultContentTypeMatch != null) { >- defaultContentType = >- defaultContentTypeMatch.getJspProperty().getDefaultContentType(); >- } >- if (bufferMatch != null) { >- buffer = bufferMatch.getJspProperty().getBuffer(); >- } >- if (errorOnUndeclaredNamespaceMatch != null) { >- errorOnUndelcaredNamespace = >- errorOnUndeclaredNamespaceMatch.getJspProperty().isErrorOnUndeclaredNamespace(); >- } >- > return new JspProperty(isXml, isELIgnored, isScriptingInvalid, > pageEncoding, includePreludes, includeCodas, > isDeferedSyntaxAllowedAsLiteral, isTrimDirectiveWhitespaces, >- defaultContentType, buffer, errorOnUndelcaredNamespace); >+ defaultContentType, buffer, errorOnUndeclaredNamespace); > } > >+ private JspProperty defaultProperty(JspPropertyGroup match) { >+ if (match == null) { >+ return defaultJspProperty; >+ } else { >+ return match.getJspProperty(); >+ } >+ } >+ > /** > * To find out if an uri matches an url pattern in jsp config. If so, > * then the uri is a JSP page. This is used primarily for jspc. > */ >- public boolean isJspPage(String uri) throws JasperException { >+ public boolean isJspPage(String uri) { > >- init(); > if (jspProperties == null) { > return false; > } >@@ -467,11 +335,8 @@ > uriExtension = uri.substring(index+1); > } > >- Iterator<JspPropertyGroup> iter = jspProperties.iterator(); >- while (iter.hasNext()) { >+ for (JspPropertyGroup jpg : jspProperties) { > >- JspPropertyGroup jpg = iter.next(); >- > String extension = jpg.getExtension(); > String path = jpg.getPath(); > >@@ -530,6 +395,10 @@ > private final String buffer; > private final String errorOnUndeclaredNamespace; > >+ private JspProperty(String elIgnored, String deferedSyntaxAllowedAsLiteral) { >+ this(null, elIgnored, null, null, null, null, deferedSyntaxAllowedAsLiteral, null, null, null, null); >+ } >+ > public JspProperty(String isXml, String elIgnored, > String scriptingInvalid, String pageEncoding, > Vector<String> includePrelude, Vector<String> includeCoda, >Index: java/org/apache/jasper/compiler/TldLocationsCache.java >=================================================================== >--- java/org/apache/jasper/compiler/TldLocationsCache.java (revision 1500579) >+++ java/org/apache/jasper/compiler/TldLocationsCache.java (working copy) >@@ -27,6 +27,8 @@ > import java.util.Set; > > import javax.servlet.ServletContext; >+import javax.servlet.descriptor.JspConfigDescriptor; >+import javax.servlet.descriptor.TaglibDescriptor; > > import org.apache.jasper.JasperException; > import org.apache.jasper.util.ExceptionUtils; >@@ -235,60 +237,37 @@ > /* > * Populates taglib map described in web.xml. > * >- * This is not kept in sync with o.a.c.startup.TldConfig as the Jasper only >- * needs the URI to TLD mappings from scan web.xml whereas TldConfig needs >- * to scan the actual TLD files. >+ * This is not kept in sync with o.a.c.startup.TldConfig as a) Jasper only >+ * needs the URI to TLD mappings and b) Jasper can obtain the information >+ * from the ServletContext. > */ > private void tldScanWebXml() throws Exception { > >- WebXml webXml = null; >- try { >- webXml = new WebXml(ctxt); >- if (webXml.getInputSource() == null) { >- return; >- } >+ JspConfigDescriptor jspConfig = ctxt.getJspConfigDescriptor(); >+ if (jspConfig == null) { >+ // no jsp-config for this application >+ return; >+ } > >- // Parse the web application deployment descriptor >- TreeNode webtld = null; >- webtld = new ParserUtils().parseXMLDocument(webXml.getSystemId(), >- webXml.getInputSource()); >+ for (TaglibDescriptor taglib : jspConfig.getTaglibs()) { > >- // Allow taglib to be an element of the root or jsp-config (JSP2.0) >- TreeNode jspConfig = webtld.findChild("jsp-config"); >- if (jspConfig != null) { >- webtld = jspConfig; >- } >- Iterator<TreeNode> taglibs = webtld.findChildren("taglib"); >- while (taglibs.hasNext()) { >+ String tagUri = taglib.getTaglibURI(); >+ String tagLoc = taglib.getTaglibLocation(); > >- // Parse the next <taglib> element >- TreeNode taglib = taglibs.next(); >- String tagUri = null; >- String tagLoc = null; >- TreeNode child = taglib.findChild("taglib-uri"); >- if (child != null) >- tagUri = child.getBody(); >- child = taglib.findChild("taglib-location"); >- if (child != null) >- tagLoc = child.getBody(); >- >- // Save this location if appropriate >- if (tagLoc == null) >- continue; >- if (uriType(tagLoc) == NOROOT_REL_URI) >- tagLoc = "/WEB-INF/" + tagLoc; >- TldLocation location; >- if (tagLoc.endsWith(JAR_EXT)) { >- location = new TldLocation("META-INF/taglib.tld", ctxt.getResource(tagLoc).toString()); >- } else { >- location = new TldLocation(tagLoc); >- } >- mappings.put(tagUri, location); >+ // Save this location if appropriate >+ if (tagLoc == null) { >+ continue; > } >- } finally { >- if (webXml != null) { >- webXml.close(); >+ if (uriType(tagLoc) == NOROOT_REL_URI) { >+ tagLoc = "/WEB-INF/" + tagLoc; > } >+ TldLocation location; >+ if (tagLoc.endsWith(JAR_EXT)) { >+ location = new TldLocation("META-INF/taglib.tld", ctxt.getResource(tagLoc).toString()); >+ } else { >+ location = new TldLocation(tagLoc); >+ } >+ mappings.put(tagUri, location); > } > } > >Index: java/org/apache/jasper/compiler/WebXml.java >=================================================================== >--- java/org/apache/jasper/compiler/WebXml.java (revision 1500579) >+++ java/org/apache/jasper/compiler/WebXml.java (working copy) >@@ -39,7 +39,10 @@ > * annotations with the main web.xml > * > * Clients *must* ensure that they call {@link #close()} to clean up resources. >+ * >+ * @deprecated Unused - will be removed in Tomcat 8.0.x > */ >+@Deprecated > public class WebXml { > private static final String FILE_PROTOCOL = "file:"; > private static final String WEB_XML = "/WEB-INF/web.xml"; >Index: java/org/apache/jasper/servlet/JspCServletContext.java >=================================================================== >--- java/org/apache/jasper/servlet/JspCServletContext.java (revision 1500579) >+++ java/org/apache/jasper/servlet/JspCServletContext.java (working copy) >@@ -19,18 +19,27 @@ > > > import java.io.File; >+import java.io.IOException; > import java.io.InputStream; > import java.io.PrintWriter; > import java.net.MalformedURLException; >+import java.net.URISyntaxException; > import java.net.URL; >+import java.util.ArrayList; >+import java.util.Collection; >+import java.util.Collections; > import java.util.EnumSet; > import java.util.Enumeration; > import java.util.EventListener; > import java.util.HashSet; > import java.util.Hashtable; >+import java.util.Iterator; >+import java.util.List; > import java.util.Map; > import java.util.Set; > import java.util.Vector; >+import java.util.jar.JarEntry; >+import java.util.jar.JarFile; > > import javax.servlet.Filter; > import javax.servlet.FilterRegistration; >@@ -43,8 +52,13 @@ > import javax.servlet.SessionCookieConfig; > import javax.servlet.SessionTrackingMode; > import javax.servlet.descriptor.JspConfigDescriptor; >+import javax.servlet.descriptor.JspPropertyGroupDescriptor; >+import javax.servlet.descriptor.TaglibDescriptor; > >+import org.apache.jasper.JasperException; > import org.apache.jasper.util.ExceptionUtils; >+import org.apache.jasper.xmlparser.ParserUtils; >+import org.apache.jasper.xmlparser.TreeNode; > > > /** >@@ -56,6 +70,7 @@ > > public class JspCServletContext implements ServletContext { > >+ private static final String JSP_CONFIG = "jsp-config"; > > // ----------------------------------------------------- Instance Variables > >@@ -78,11 +93,15 @@ > private final URL myResourceBaseURL; > > >+ > /** > * Web application class loader. > */ > private ClassLoader loader; > >+ private final int effectiveMajorVersion; >+ private final int effectiveMinorVersion; >+ private final JspConfigDescriptor jspConfigDescriptor; > > // ----------------------------------------------------------- Constructors > >@@ -92,15 +111,87 @@ > * @param aLogWriter PrintWriter which is used for <code>log()</code> calls > * @param aResourceBaseURL Resource base URL > */ >- public JspCServletContext(PrintWriter aLogWriter, URL aResourceBaseURL) { >+ public JspCServletContext(PrintWriter aLogWriter, URL aResourceBaseURL) throws IOException, JasperException { > > myAttributes = new Hashtable<>(); > myLogWriter = aLogWriter; > myResourceBaseURL = aResourceBaseURL; > >+ TreeNode mergedWebApp = loadAndMerge(); >+ >+ // set effective version for this application >+ Version version = Version.fromString(mergedWebApp.findAttribute("version")); >+ effectiveMajorVersion = version.getMajor(); >+ effectiveMinorVersion = version.getMinor(); >+ >+ jspConfigDescriptor = new JspCJspConfigDescriptor(mergedWebApp, version); > } > >+ private TreeNode loadAndMerge() throws IOException, JasperException { >+ ParserUtils pu = new ParserUtils(); >+ TreeNode root = load(pu); >+ mergeFragments(pu, root); >+ return root; >+ } > >+ private TreeNode load(ParserUtils pu) throws JasperException, IOException { >+ URL webUrl = getResource("/WEB-INF/web.xml"); >+ if (webUrl == null) { >+ // No web.xml in the application, use a empty one as default to support Servlet 3.0 applications. >+ // This avoids inconsistency between compilation triggered by JspServlet which would use the >+ // container's specification level and JspC which would default to Servlet 2.3 behaviour (including >+ // unanticipated consequences such as disabling EL evaluation). >+ webUrl = JspCServletContext.class.getResource("web.xml"); >+ } >+ try (InputStream is = webUrl.openStream()) { >+ return pu.parseXMLDocument(webUrl.toExternalForm(), is); >+ } >+ } >+ >+ private void mergeFragments(ParserUtils pu, TreeNode root) throws IOException, JasperException { >+ Set<String> libs = getResourcePaths("/WEB-INF/lib/"); >+ if (libs == null) { >+ return; >+ } >+ >+ // HACK: just iterate all jars as fragment ordering does not matter for merging jsp-config entries >+ for (String lib : libs) { >+ try { >+ URL url = getResource(lib); >+ try (JarFile jarFile = new JarFile(new File(url.toURI()))) { >+ JarEntry entry = jarFile.getJarEntry("META-INF/web-fragment.xml"); >+ if (entry == null) { >+ continue; >+ } >+ >+ try (InputStream is = jarFile.getInputStream(entry)) { >+ TreeNode fragment = pu.parseXMLDocument(url.toExternalForm(), is); >+ merge(root, fragment); >+ } >+ } >+ } catch (URISyntaxException e) { >+ throw new JasperException(e); >+ } >+ } >+ } >+ >+ private void merge(TreeNode root, TreeNode fragment) { >+ TreeNode childConfig = fragment.findChild(JSP_CONFIG); >+ if (childConfig == null) { >+ return; >+ } >+ TreeNode rootConfig = root.findChild(JSP_CONFIG); >+ Iterator<TreeNode> children = childConfig.findChildren(); >+ while (children.hasNext()) { >+ TreeNode node = children.next(); >+ if (rootConfig == null) { >+ // automatically adds to parent >+ rootConfig = new TreeNode(JSP_CONFIG, root); >+ } >+ rootConfig.addChild(node); >+ } >+ } >+ > // --------------------------------------------------------- Public Methods > > >@@ -628,13 +719,13 @@ > > @Override > public int getEffectiveMajorVersion() { >- return 3; >+ return effectiveMajorVersion; > } > > > @Override > public int getEffectiveMinorVersion() { >- return 0; >+ return effectiveMinorVersion; > } > > >@@ -646,7 +737,7 @@ > > @Override > public JspConfigDescriptor getJspConfigDescriptor() { >- return null; >+ return jspConfigDescriptor; > } > > >@@ -660,4 +751,295 @@ > public String getVirtualServerName() { > return null; > } >+ >+ private static enum Version { >+ VERSION_2_3(2, 3), >+ VERSION_2_4(2, 4), >+ VERSION_2_5(2, 5), >+ VERSION_3_0(3, 0), >+ VERSION_3_1(3, 1); >+ private final int major; >+ private final int minor; >+ private Version(int major, int minor) { >+ this.major = major; >+ this.minor = minor; >+ } >+ >+ private int getMajor() { >+ return major; >+ } >+ >+ private int getMinor() { >+ return minor; >+ } >+ >+ private static Version fromString(String version) { >+ if (version == null) { >+ return VERSION_2_3; >+ } >+ switch (version) { >+ case "2.4": >+ return VERSION_2_4; >+ case "2.5": >+ return VERSION_2_5; >+ case "3.0": >+ return VERSION_3_0; >+ case "3.1": >+ return VERSION_3_1; >+ default: >+ return VERSION_2_3; >+ } >+ } >+ } >+ >+ private static class JspCJspConfigDescriptor implements JspConfigDescriptor { >+ private final Collection<TaglibDescriptor> taglibs; >+ private final Collection<JspPropertyGroupDescriptor> jspPropertyGroups; >+ >+ private JspCJspConfigDescriptor(TreeNode webapp, Version version) { >+ // In Servlet 2.3, <taglib> elements were under the root and there were no property groups >+ if (version == Version.VERSION_2_3) { >+ taglibs = taglibDescriptors(webapp); >+ jspPropertyGroups = Collections.emptyList(); >+ return; >+ } >+ >+ // In later versions, JSP configuration is under the <jsp-config> element >+ TreeNode jspConfig = webapp.findChild(JSP_CONFIG); >+ if (jspConfig == null) { >+ taglibs = Collections.emptyList(); >+ jspPropertyGroups = Collections.emptyList(); >+ return; >+ } >+ taglibs = taglibDescriptors(jspConfig); >+ jspPropertyGroups = propertyGroups(jspConfig); >+ } >+ >+ private Collection<TaglibDescriptor> taglibDescriptors(TreeNode parent) { >+ Collection<TaglibDescriptor> descriptors = new ArrayList<>(); >+ Iterator<TreeNode> taglibs = parent.findChildren("taglib"); >+ while (taglibs.hasNext()) { >+ TreeNode taglib = taglibs.next(); >+ final String tagUri = optionalChild(taglib, "taglib-uri"); >+ final String tagLoc = optionalChild(taglib, "taglib-location"); >+ descriptors.add(new JspCTaglibDescriptor(tagUri, tagLoc)); >+ } >+ return Collections.unmodifiableCollection(descriptors); >+ } >+ >+ private Collection<JspPropertyGroupDescriptor> propertyGroups(TreeNode parent) { >+ List<JspPropertyGroupDescriptor> descriptors = new ArrayList<>(); >+ Iterator<TreeNode> groups = parent.findChildren("jsp-property-group"); >+ while (groups.hasNext()) { >+ TreeNode group = groups.next(); >+ String buffer = null; >+ String defaultContentType = null; >+ String deferedSyntaxAllowedAsLiteral = null; >+ String elIgnored = null; >+ String errorOnUndeclaredNamespace = null; >+ List<String> includeCodas = new ArrayList<>(); >+ List<String> includePreludes = new ArrayList<>(); >+ String isXml = null; >+ String pageEncoding = null; >+ String scriptingInvalid = null; >+ String trimDirectiveWhitespaces = null; >+ List<String> urlPatterns = new ArrayList<>(); >+ Iterator<TreeNode> child = group.findChildren(); >+ while (child.hasNext()) { >+ TreeNode node = child.next(); >+ String body = node.getBody(); >+ switch (node.getName()) { >+ case "buffer": >+ buffer = body; >+ break; >+ case "default-content-type": >+ defaultContentType = body; >+ break; >+ case "deferred-syntax-allowed-as-literal": >+ deferedSyntaxAllowedAsLiteral = body; >+ break; >+ case "el-ignored": >+ elIgnored = body; >+ break; >+ case "error-on-undeclared-namespace": >+ errorOnUndeclaredNamespace = body; >+ break; >+ case "include-coda": >+ includeCodas.add(body); >+ break; >+ case "include-prelude": >+ includePreludes.add(body); >+ break; >+ case "is-xml": >+ isXml = body; >+ break; >+ case "page-encoding": >+ pageEncoding = body; >+ break; >+ case "scripting-invalid": >+ scriptingInvalid = body; >+ break; >+ case "trim-directive-whitespaces": >+ buffer = body; >+ break; >+ case "url-pattern": >+ urlPatterns.add(body); >+ break; >+ } >+ } >+ descriptors.add(new JspCPropertyGroupDescriptor( >+ buffer, >+ defaultContentType, >+ deferedSyntaxAllowedAsLiteral, >+ elIgnored, >+ errorOnUndeclaredNamespace, >+ Collections.unmodifiableCollection(includeCodas), >+ Collections.unmodifiableCollection(includePreludes), >+ isXml, >+ pageEncoding, >+ scriptingInvalid, >+ trimDirectiveWhitespaces, >+ Collections.unmodifiableCollection(urlPatterns))); >+ } >+ return Collections.unmodifiableList(descriptors); >+ } >+ >+ private static String optionalChild(TreeNode parent, String name) { >+ TreeNode child = parent.findChild(name); >+ return child == null ? null : child.getBody(); >+ } >+ >+ @Override >+ public Collection<TaglibDescriptor> getTaglibs() { >+ return taglibs; >+ } >+ >+ @Override >+ public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups() { >+ return jspPropertyGroups; >+ } >+ >+ // TODO: share with org.apache.catalina.core.ApplicationTaglibDescriptor >+ private static class JspCTaglibDescriptor implements TaglibDescriptor { >+ private final String tagUri; >+ private final String tagLoc; >+ >+ public JspCTaglibDescriptor(String tagUri, String tagLoc) { >+ this.tagUri = tagUri; >+ this.tagLoc = tagLoc; >+ } >+ >+ @Override >+ public String getTaglibURI() { >+ return tagUri; >+ } >+ >+ @Override >+ public String getTaglibLocation() { >+ return tagLoc; >+ } >+ } >+ >+ // TODO: share with org.apache.catalina.core.ApplicationJspPropertyGroupDescriptor >+ private static class JspCPropertyGroupDescriptor implements JspPropertyGroupDescriptor { >+ private final String buffer; >+ private final String defaultContentType; >+ private final String deferedSyntaxAllowedAsLiteral; >+ private final String elIgnored; >+ private final String errorOnUndeclaredNamespace; >+ private final Collection<String> includeCodas; >+ private final Collection<String> includePreludes; >+ private final String isXml; >+ private final String pageEncoding; >+ private final String scriptingInvalid; >+ private final String trimDirectiveWhitespaces; >+ private final Collection<String> urlPatterns; >+ >+ private JspCPropertyGroupDescriptor(String buffer, >+ String defaultContentType, >+ String deferedSyntaxAllowedAsLiteral, >+ String elIgnored, >+ String errorOnUndeclaredNamespace, >+ Collection<String> includeCodas, >+ Collection<String> includePreludes, >+ String isXml, >+ String pageEncoding, >+ String scriptingInvalid, >+ String trimDirectiveWhitespaces, >+ Collection<String> urlPatterns) { >+ this.buffer = buffer; >+ this.defaultContentType = defaultContentType; >+ this.deferedSyntaxAllowedAsLiteral = deferedSyntaxAllowedAsLiteral; >+ this.elIgnored = elIgnored; >+ this.errorOnUndeclaredNamespace = errorOnUndeclaredNamespace; >+ this.includeCodas = includeCodas; >+ this.includePreludes = includePreludes; >+ this.isXml = isXml; >+ this.pageEncoding = pageEncoding; >+ this.scriptingInvalid = scriptingInvalid; >+ this.trimDirectiveWhitespaces = trimDirectiveWhitespaces; >+ this.urlPatterns = urlPatterns; >+ } >+ >+ @Override >+ public Collection<String> getUrlPatterns() { >+ return urlPatterns; >+ } >+ >+ @Override >+ public String getElIgnored() { >+ return elIgnored; >+ } >+ >+ @Override >+ public String getPageEncoding() { >+ return pageEncoding; >+ } >+ >+ @Override >+ public String getScriptingInvalid() { >+ return scriptingInvalid; >+ } >+ >+ @Override >+ public String getIsXml() { >+ return isXml; >+ } >+ >+ @Override >+ public Collection<String> getIncludePreludes() { >+ return includePreludes; >+ } >+ >+ @Override >+ public Collection<String> getIncludeCodas() { >+ return includeCodas; >+ } >+ >+ @Override >+ public String getDeferredSyntaxAllowedAsLiteral() { >+ return deferedSyntaxAllowedAsLiteral; >+ } >+ >+ @Override >+ public String getTrimDirectiveWhitespaces() { >+ return trimDirectiveWhitespaces; >+ } >+ >+ @Override >+ public String getDefaultContentType() { >+ return defaultContentType; >+ } >+ >+ @Override >+ public String getBuffer() { >+ return buffer; >+ } >+ >+ @Override >+ public String getErrorOnUndeclaredNamespace() { >+ return errorOnUndeclaredNamespace; >+ } >+ } >+ } > } >Index: java/org/apache/jasper/servlet/web.xml >=================================================================== >--- java/org/apache/jasper/servlet/web.xml (revision 0) >+++ java/org/apache/jasper/servlet/web.xml (working copy) >@@ -0,0 +1,25 @@ >+<?xml version="1.0" encoding="ISO-8859-1"?> >+<!-- >+ ~ 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. >+ --> >+<!-- >+ Default web.xml used by Jasper if one is not provided in the application. >+--> >+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" >+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >+ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" >+ version="3.1"> >+</web-app> > >Property changes on: java/org/apache/jasper/servlet/web.xml >___________________________________________________________________ >Added: svn:eol-style >## -0,0 +1 ## >+native >\ No newline at end of property >Index: test/org/apache/jasper/servlet/TestJspCServletContext.java >=================================================================== >--- test/org/apache/jasper/servlet/TestJspCServletContext.java (revision 0) >+++ test/org/apache/jasper/servlet/TestJspCServletContext.java (working copy) >@@ -0,0 +1,97 @@ >+/* >+ * 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.jasper.servlet; >+ >+import java.io.File; >+import java.util.Collection; >+import java.util.Iterator; >+ >+import javax.servlet.descriptor.JspConfigDescriptor; >+import javax.servlet.descriptor.JspPropertyGroupDescriptor; >+ >+import org.junit.Assert; >+import org.junit.Test; >+ >+public class TestJspCServletContext { >+ >+ @Test >+ public void testWebapp() throws Exception { >+ File appDir = new File("test/webapp"); >+ JspCServletContext context = new JspCServletContext(null, appDir.toURI().toURL()); >+ Assert.assertEquals(3, context.getEffectiveMajorVersion()); >+ Assert.assertEquals(1, context.getEffectiveMinorVersion()); >+ JspConfigDescriptor jspConfigDescriptor = context.getJspConfigDescriptor(); >+ Assert.assertTrue(jspConfigDescriptor.getTaglibs().isEmpty()); >+ Collection<JspPropertyGroupDescriptor> propertyGroups = jspConfigDescriptor.getJspPropertyGroups(); >+ Assert.assertEquals(1, propertyGroups.size()); >+ JspPropertyGroupDescriptor groupDescriptor = propertyGroups.iterator().next(); >+ Assert.assertEquals("text/plain", groupDescriptor.getDefaultContentType()); >+ Collection<String> urlPatterns = groupDescriptor.getUrlPatterns(); >+ Assert.assertEquals(2, urlPatterns.size()); >+ Iterator<String> iterator = urlPatterns.iterator(); >+ Assert.assertEquals("/bug49nnn/bug49726a.jsp", iterator.next()); >+ Assert.assertEquals("/bug49nnn/bug49726b.jsp", iterator.next()); >+ } >+ >+ @Test >+ public void testWebapp_2_3() throws Exception { >+ File appDir = new File("test/webapp-2.3"); >+ JspCServletContext context = new JspCServletContext(null, appDir.toURI().toURL()); >+ Assert.assertEquals(2, context.getEffectiveMajorVersion()); >+ Assert.assertEquals(3, context.getEffectiveMinorVersion()); >+ } >+ >+ @Test >+ public void testWebapp_2_4() throws Exception { >+ File appDir = new File("test/webapp-2.4"); >+ JspCServletContext context = new JspCServletContext(null, appDir.toURI().toURL()); >+ Assert.assertEquals(2, context.getEffectiveMajorVersion()); >+ Assert.assertEquals(4, context.getEffectiveMinorVersion()); >+ } >+ >+ @Test >+ public void testWebapp_2_5() throws Exception { >+ File appDir = new File("test/webapp-2.5"); >+ JspCServletContext context = new JspCServletContext(null, appDir.toURI().toURL()); >+ Assert.assertEquals(2, context.getEffectiveMajorVersion()); >+ Assert.assertEquals(5, context.getEffectiveMinorVersion()); >+ } >+ >+ @Test >+ public void testWebapp_3_0() throws Exception { >+ File appDir = new File("test/webapp-3.0"); >+ JspCServletContext context = new JspCServletContext(null, appDir.toURI().toURL()); >+ Assert.assertEquals(3, context.getEffectiveMajorVersion()); >+ Assert.assertEquals(0, context.getEffectiveMinorVersion()); >+ } >+ >+ @Test >+ public void testWebapp_3_1() throws Exception { >+ File appDir = new File("test/webapp-3.1"); >+ JspCServletContext context = new JspCServletContext(null, appDir.toURI().toURL()); >+ Assert.assertEquals(3, context.getEffectiveMajorVersion()); >+ Assert.assertEquals(1, context.getEffectiveMinorVersion()); >+ } >+ >+ @Test >+ public void testWebresources() throws Exception { >+ File appDir = new File("test/webresources/dir1"); >+ JspCServletContext context = new JspCServletContext(null, appDir.toURI().toURL()); >+ Assert.assertEquals(3, context.getEffectiveMajorVersion()); >+ Assert.assertEquals(1, context.getEffectiveMinorVersion()); >+ } >+} > >Property changes on: test/org/apache/jasper/servlet/TestJspCServletContext.java >___________________________________________________________________ >Added: svn:eol-style >## -0,0 +1 ## >+native >\ No newline at end of property
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 53737
:
30504
|
30533
| 30568