import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.util.UniqueAttributesImpl;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;
private static final String JAVAX_BODY_CONTENT_TEMPLATE_TEXT =
"JAVAX_BODY_CONTENT_TEMPLATE_TEXT";
+ /* System property that controls if the strict white space rules are
+ * applied.
+ */
+ private static final boolean STRICT_WHITESPACE = Boolean.valueOf(
+ System.getProperty(
+ "org.apache.jasper.compiler.Parser.STRICT_WHITESPACE",
+ "true")).booleanValue();
/**
* The constructor
*/
* Attributes ::= (S Attribute)* S?
*/
Attributes parseAttributes() throws JasperException {
- AttributesImpl attrs = new AttributesImpl();
+ UniqueAttributesImpl attrs = new UniqueAttributesImpl();
reader.skipSpaces();
- while (parseAttribute(attrs))
- reader.skipSpaces();
+ int ws = 1;
+
+ try {
+ while (parseAttribute(attrs)) {
+ if (ws == 0 && STRICT_WHITESPACE) {
+ err.jspError(reader.mark(),
+ "jsp.error.attribute.nowhitespace");
+ }
+ ws = reader.skipSpaces();
+ }
+ } catch (IllegalArgumentException iae) {
+ // Duplicate attribute
+ err.jspError(reader.mark(), "jsp.error.attribute.duplicate");
+ }
return attrs;
}
jsp.error.attribute.noquote=quote symbol expected
jsp.error.attribute.unterminated=attribute for {0} is not properly terminated
jsp.error.attribute.noescape=Attribute value {0} is quoted with {1} which must be escaped when used within the value
+jsp.error.attribute.nowhitespace=The JSP specification requires that an attribute name is preceded by whitespace
+jsp.error.attribute.duplicate=Attribute qualified names must be unique within an element
jsp.error.missing.tagInfo=TagInfo object for {0} is missing from TLD
jsp.error.deferredmethodsignaturewithoutdeferredmethod=Cannot specify a method signature if 'deferredMethod' is not 'true'
jsp.error.deferredvaluetypewithoutdeferredvalue=Cannot specify a value type if 'deferredValue' is not 'true'
# JavacErrorDetail
jsp.error.bug48498=Unable to display JSP extract. Probably due to an XML parser bug (see Tomcat bug 48498 for details).
+
+# UniqueAttributesImpl
+jsp.error.duplicateqname=An attribute with duplicate qualified name [{0}] was found. Attribute qualified names must be unique within an element.
\ No newline at end of file
--- /dev/null
+/*
+ * 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.util;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.jasper.compiler.Localizer;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Wraps the default attributes implementation and ensures that each attribute
+ * has a unique qname as required by the JSP specification.
+ */
+public class UniqueAttributesImpl extends AttributesImpl {
+
+ private Set<String> qNames = new HashSet<String>();
+
+ @Override
+ public void clear() {
+ qNames.clear();
+ super.clear();
+ }
+
+ @Override
+ public void setAttributes(Attributes atts) {
+ for (int i = 0; i < atts.getLength(); i++) {
+ if (!qNames.add(atts.getQName(i))) {
+ handleDuplicate(atts.getQName(i));
+ }
+ }
+ super.setAttributes(atts);
+ }
+
+ @Override
+ public void addAttribute(String uri, String localName, String qName,
+ String type, String value) {
+ if (qNames.add(qName)) {
+ super.addAttribute(uri, localName, qName, type, value);
+ } else {
+ handleDuplicate(qName);
+ }
+ }
+
+ @Override
+ public void setAttribute(int index, String uri, String localName,
+ String qName, String type, String value) {
+ qNames.remove(super.getQName(index));
+ if (qNames.add(qName)) {
+ super.setAttribute(index, uri, localName, qName, type, value);
+ } else {
+ handleDuplicate(qName);
+ }
+ }
+
+ @Override
+ public void removeAttribute(int index) {
+ qNames.remove(super.getQName(index));
+ super.removeAttribute(index);
+ }
+
+ @Override
+ public void setQName(int index, String qName) {
+ qNames.remove(super.getQName(index));
+ super.setQName(index, qName);
+ }
+
+ private void handleDuplicate(String qName) {
+ throw new IllegalArgumentException(
+ Localizer.getMessage("jsp.error.duplicateqname", qName));
+ }
+}
package org.apache.jasper.compiler;
import java.io.File;
+import java.util.HashMap;
+import java.util.List;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.tomcat.util.buf.ByteChunk;
+/**
+ * Tests are duplicated in {@link TestParserNoStrictWhitespace} with the strict
+ * whitespace parsing disabled.
+ */
public class TestParser extends TomcatBaseTest {
public void testBug48627() throws Exception {
public void testBug48668b() throws Exception {
Tomcat tomcat = getTomcatInstance();
- File appDir =
- new File("test/webapp-3.0");
+ File appDir = new File("test/webapp-3.0");
// app dir is relative to server home
tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
assertEcho(result, "01-Hello world</p>#{foo2");
}
+ public void testBug49297NoSpaceStrict() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp-3.0");
+ // app dir is relative to server home
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+ tomcat.start();
+
+ int sc = getUrl("http://localhost:" + getPort() +
+ "/test/bug49297NoSpace.jsp", new ByteChunk(),
+ new HashMap<String,List<String>>());
+
+ assertEquals(500, sc);
+ }
+
+ public void testBug49297DuplicateAttr() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp-3.0");
+ // app dir is relative to server home
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+ tomcat.start();
+
+ int sc = getUrl("http://localhost:" + getPort() +
+ "/test/bug49297DuplicateAttr.jsp", new ByteChunk(),
+ new HashMap<String,List<String>>());
+
+ assertEquals(500, sc);
+ }
+
/** Assertion for text printed by tags:echo */
private static void assertEcho(String result, String expected) {
assertTrue(result.indexOf("<p>" + expected + "</p>") > 0);
--- /dev/null
+/*
+ * 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.compiler;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Tests are duplicated in {@link TestParser} with the strict whitespace parsing
+ * enabled by default.
+ */
+public class TestParserNoStrictWhitespace extends TomcatBaseTest {
+
+ @Override
+ public void setUp() throws Exception {
+ System.setProperty(
+ "org.apache.jasper.compiler.Parser.STRICT_WHITESPACE",
+ "false");
+ super.setUp();
+ }
+
+ public void testBug48627() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir =
+ new File("test/webapp-3.0");
+ // app dir is relative to server home
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+ tomcat.start();
+
+ ByteChunk res = getUrl("http://localhost:" + getPort() +
+ "/test/bug48nnn/bug48627.jsp");
+
+ String result = res.toString();
+ // Beware of the differences between escaping in JSP attributes and
+ // in Java Strings
+ assertEcho(result, "00-\\");
+ assertEcho(result, "01-\\");
+ }
+
+ public void testBug48668a() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir =
+ new File("test/webapp-3.0");
+ // app dir is relative to server home
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+ tomcat.start();
+
+ ByteChunk res = getUrl("http://localhost:" + getPort() +
+ "/test/bug48nnn/bug48668a.jsp");
+ String result = res.toString();
+ assertEcho(result, "00-Hello world</p>#{foo.bar}");
+ assertEcho(result, "01-Hello world</p>${foo.bar}");
+ assertEcho(result, "10-Hello ${'foo.bar}");
+ assertEcho(result, "11-Hello ${'foo.bar}");
+ assertEcho(result, "12-Hello #{'foo.bar}");
+ assertEcho(result, "13-Hello #{'foo.bar}");
+ assertEcho(result, "14-Hello ${'foo}");
+ assertEcho(result, "15-Hello ${'foo}");
+ assertEcho(result, "16-Hello #{'foo}");
+ assertEcho(result, "17-Hello #{'foo}");
+ assertEcho(result, "18-Hello ${'foo.bar}");
+ assertEcho(result, "19-Hello ${'foo.bar}");
+ assertEcho(result, "20-Hello #{'foo.bar}");
+ assertEcho(result, "21-Hello #{'foo.bar}");
+ assertEcho(result, "30-Hello ${'foo}");
+ assertEcho(result, "31-Hello ${'foo}");
+ assertEcho(result, "32-Hello #{'foo}");
+ assertEcho(result, "33-Hello #{'foo}");
+ assertEcho(result, "34-Hello ${'foo}");
+ assertEcho(result, "35-Hello ${'foo}");
+ assertEcho(result, "36-Hello #{'foo}");
+ assertEcho(result, "37-Hello #{'foo}");
+ assertEcho(result, "40-Hello ${'foo}");
+ assertEcho(result, "41-Hello ${'foo}");
+ assertEcho(result, "42-Hello #{'foo}");
+ assertEcho(result, "43-Hello #{'foo}");
+ assertEcho(result, "50-Hello ${'foo}");
+ assertEcho(result, "51-Hello ${'foo}");
+ assertEcho(result, "52-Hello #{'foo}");
+ assertEcho(result, "53-Hello #{'foo}");
+ }
+
+ public void testBug48668b() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp-3.0");
+ // app dir is relative to server home
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+ tomcat.start();
+
+ ByteChunk res = getUrl("http://localhost:" + getPort() +
+ "/test/bug48nnn/bug48668b.jsp");
+ String result = res.toString();
+ assertEcho(result, "00-Hello world</p>#{foo.bar}");
+ assertEcho(result, "01-Hello world</p>#{foo2");
+ }
+
+ public void testBug49297NoSpaceNotStrict() throws Exception {
+
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp-3.0");
+ // app dir is relative to server home
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+ tomcat.start();
+
+ ByteChunk res = new ByteChunk();
+ int sc = getUrl("http://localhost:" + getPort() +
+ "/test/bug49297NoSpace.jsp", res,
+ new HashMap<String,List<String>>());
+
+
+ assertEquals(200, sc);
+ assertEcho(res.toString(), "Hello World");
+ }
+
+ public void testBug49297DuplicateAttr() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp-3.0");
+ // app dir is relative to server home
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+ tomcat.start();
+
+ int sc = getUrl("http://localhost:" + getPort() +
+ "/test/bug49297DuplicateAttr.jsp", new ByteChunk(),
+ new HashMap<String,List<String>>());
+
+ assertEquals(500, sc);
+ }
+
+ /** Assertion for text printed by tags:echo */
+ private static void assertEcho(String result, String expected) {
+ assertTrue(result.indexOf("<p>" + expected + "</p>") > 0);
+ }
+}
limitations under the License.
--%><%@ tag %><%@
attribute name="echo" type="java.lang.String"%><%@
+attribute name="dummy" type="java.lang.String" required="false"%><%@
tag body-content="empty" %><p>${echo}</p>
\ No newline at end of file
--- /dev/null
+<%--
+ 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.
+--%>
+<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
+<html>
+ <head><title>Bug 49297 duplicate attribute test case</title></head>
+ <body>
+ <tags:echo echo="Hello World" echo="error"/>
+ </body>
+</html>
\ No newline at end of file
--- /dev/null
+<%--
+ 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.
+--%>
+<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
+<html>
+ <head><title>Bug 49297 whitespace test case</title></head>
+ <body>
+ <tags:echo echo="Hello World"dummy="ignored"/>
+ </body>
+</html>
\ No newline at end of file
<subsection name="Jasper">
<changelog>
<fix>
+ <bug>49297</bug>: Enforce the rules in the JSP specification for parsing
+ the attributes of custom and standard actions that require that
+ the attribute names are unique within an element and that there is
+ whitespace before the attribute name. The whitespace test can be
+ disabled by setting the system property
+ <code>org.apache.jasper.compiler.Parser.STRICT_WHITESPACE</code> to
+ <code>false</code>. (markt)
+ </fix>
+ <fix>
<bug>50105</bug>: When processing composite EL expressions use
<code>Enum.name()</code> rather than <code>Enum.toString()</code> as
required by the EL specification. (markt)
<code>true</code> will be used.</p>
</property>
+ <property name="org.apache.jasper.compiler. Parser.STRICT_WHITESPACE">
+ <p>If <code>false</code> the requirements for whitespace before an
+ attribute name will be relaxed so that the lack of whitespace will not
+ cause an error. If not specified, the specification compliant default of
+ <code>true</code> will be used.</p>
+ </property>
+
<property name="org.apache.jasper.runtime. BodyContentImpl.LIMIT_BUFFER">
<p>If <code>true</code>, any tag buffer that expands beyond
<code>org.apache.jasper.Constants.DEFAULT_TAG_BUFFER_SIZE</code> will be