Author: markt
Date: Thu Apr 24 08:35:06 2014
New Revision: 1589635

URL: http://svn.apache.org/r1589635
Log:
Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=56334
Correct double unescaping

Modified:
    tomcat/tc6.0.x/trunk/STATUS.txt
    tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/ELParser.java
    tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/Validator.java
    tomcat/tc6.0.x/trunk/test/org/apache/jasper/compiler/TestELParser.java
    tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml

Modified: tomcat/tc6.0.x/trunk/STATUS.txt
URL: 
http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/STATUS.txt?rev=1589635&r1=1589634&r2=1589635&view=diff
==============================================================================
--- tomcat/tc6.0.x/trunk/STATUS.txt (original)
+++ tomcat/tc6.0.x/trunk/STATUS.txt Thu Apr 24 08:35:06 2014
@@ -28,12 +28,6 @@ None
 PATCHES PROPOSED TO BACKPORT:
   [ New proposals should be added at the end of the list ]
 
-* Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=56334
-  Correct double unescaping
-  
http://people.apache.org/~markt/patches/2014-04-17-attribute-escaping-tc6-v2.patch
-  +1: markt, remm, fhanik
-  -1:
-
 * Enabling building with Java 8
   
http://people.apache.org/~markt/patches/2014-04-12-build-with-java8-tc6-v1.patch
   (Note: It is easier to verify the AbstractReplicatedMap changes by diffing

Modified: tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/ELParser.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/ELParser.java?rev=1589635&r1=1589634&r2=1589635&view=diff
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/ELParser.java 
(original)
+++ tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/ELParser.java Thu Apr 
24 08:35:06 2014
@@ -203,15 +203,22 @@ public class ELParser {
         while (hasNextChar()) {
             char ch = nextChar();
             if (prev == '\\') {
+                if (ch == '$' || (!isDeferredSyntaxAllowedAsLiteral && ch == 
'#')) {
                 prev = 0;
-                if (ch == '\\') {
+                    buf.append(ch);
+                    continue;
+                } else if (ch == '\\') {
+                    // Not an escape (this time).
+                    // Optimisation - no need to set prev as it is unchanged
+                    buf.append('\\');
+                    continue;
+                } else {
+                    // Not an escape
+                    prev = 0;
                     buf.append('\\');
-                    prev = '\\';
-                } else if (ch == '$'
-                        || (!isDeferredSyntaxAllowedAsLiteral && ch == '#')) {
                     buf.append(ch);
+                    continue;
                 }
-                // else error!
             } else if (prev == '$'
                     || (!isDeferredSyntaxAllowedAsLiteral && prev == '#')) {
                 if (ch == '{') {
@@ -235,6 +242,98 @@ public class ELParser {
         return buf.toString();
     }
 
+
+    /**
+     * Escape '\\', '$' and '#', inverting the unescaping performed in
+     * {@link #skipUntilEL()}.
+     *
+     * @param input Non-EL input to be escaped
+     * @param isDeferredSyntaxAllowedAsLiteral
+     *
+     * @return The escaped version of the input
+     */
+    private static String escapeLiteralExpression(String input,
+            boolean isDeferredSyntaxAllowedAsLiteral) {
+        int len = input.length();
+        int lastAppend = 0;
+        StringBuilder output = null;
+        for (int i = 0; i < len; i++) {
+            char ch = input.charAt(i);
+            if (ch =='$' || (!isDeferredSyntaxAllowedAsLiteral && ch == '#')) {
+                if (output == null) {
+                    output = new StringBuilder(len + 20);
+                }
+                output.append(input.subSequence(lastAppend, i));
+                lastAppend = i + 1;
+                output.append('\\');
+                output.append(ch);
+            }
+        }
+        if (output == null) {
+            return input;
+        } else {
+            output.append(input.substring(lastAppend, len));
+            return output.toString();
+        }
+    }
+
+
+    /**
+     * Escape '\\', '\'' and '\"', inverting the unescaping performed in
+     * {@link #skipUntilEL()}.
+     *
+     * @param input Non-EL input to be escaped
+     * @param isDeferredSyntaxAllowedAsLiteral
+     *
+     * @return The escaped version of the input
+     */
+    private static String escapeELText(String input) {
+        int len = input.length();
+        char quote = 0;
+        int lastAppend = 0;
+
+        if (len > 1) {
+            // Might be quoted
+            quote = input.charAt(0);
+            if (quote == '\'' || quote == '\"') {
+                if (input.charAt(len - 1) != quote) {
+                    throw new IllegalArgumentException(Localizer.getMessage(
+                            
"org.apache.jasper.compiler.ELParser.invalidQuotesForStringLiteral",
+                            input));
+                }
+                lastAppend = 1;
+                len--;
+            } else {
+                quote = 0;
+            }
+        }
+
+        StringBuilder output = null;
+        for (int i = lastAppend; i < len; i++) {
+            char ch = input.charAt(i);
+            if (ch == '\\' || ch == quote) {
+                if (output == null) {
+                    output = new StringBuilder(len + 20);
+                    output.append(quote);
+                }
+                output.append(input.subSequence(lastAppend, i));
+                lastAppend = i + 1;
+                output.append('\\');
+                output.append(ch);
+            }
+        }
+        if (output == null) {
+            return input;
+        } else {
+            output.append(input.substring(lastAppend, len));
+            if (quote != 0) {
+                output.append(quote);
+            }
+            return output.toString();
+        }
+    }
+
+
     /*
      * @return true if there is something left in EL expression buffer other
      * than white spaces.
@@ -281,7 +380,7 @@ public class ELParser {
 
     /*
      * Parse a string in single or double quotes, allowing for escape sequences
-     * '\\', and ('\"', or "\'")
+     * '\\', '\"' and "\'"
      */
     private Token parseQuotedChars(char quote) {
         StringBuilder buf = new StringBuilder();
@@ -290,10 +389,13 @@ public class ELParser {
             char ch = nextChar();
             if (ch == '\\') {
                 ch = nextChar();
-                if (ch == '\\' || ch == quote) {
+                if (ch == '\\' || ch == '\'' || ch == '\"') {
                     buf.append(ch);
+                } else {
+                    throw new IllegalArgumentException(Localizer.getMessage(
+                            
"org.apache.jasper.compiler.ELParser.invalidQuoting",
+                            expression));
                 }
-                // else error!
             } else if (ch == quote) {
                 buf.append(ch);
                 break;
@@ -448,8 +550,13 @@ public class ELParser {
 
     protected static class TextBuilder extends ELNode.Visitor {
 
+        private final boolean isDeferredSyntaxAllowedAsLiteral;
         protected StringBuilder output = new StringBuilder();
 
+        protected TextBuilder(boolean isDeferredSyntaxAllowedAsLiteral) {
+            this.isDeferredSyntaxAllowedAsLiteral = 
isDeferredSyntaxAllowedAsLiteral;
+        }
+
         public String getText() {
             return output.toString();
         }
@@ -464,18 +571,18 @@ public class ELParser {
 
         @Override
         public void visit(Function n) throws JasperException {
-            output.append(n.getOriginalText());
+            output.append(escapeLiteralExpression(n.getOriginalText(), 
isDeferredSyntaxAllowedAsLiteral));
             output.append('(');
         }
 
         @Override
         public void visit(Text n) throws JasperException {
-            output.append(n.getText());
+            
output.append(escapeLiteralExpression(n.getText(),isDeferredSyntaxAllowedAsLiteral));
         }
 
         @Override
         public void visit(ELText n) throws JasperException {
-            output.append(n.getText());
+            output.append(escapeELText(n.getText()));
         }
     }
 }

Modified: tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/Validator.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/Validator.java?rev=1589635&r1=1589634&r2=1589635&view=diff
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/Validator.java 
(original)
+++ tomcat/tc6.0.x/trunk/java/org/apache/jasper/compiler/Validator.java Thu Apr 
24 08:35:06 2014
@@ -1359,7 +1359,8 @@ class Validator {
                         // The wrinkle is that the output of any EL must not be
                         // re-escaped as that must be output as is.
                         if (el != null) {
-                            XmlEscapeNonELVisitor v = new 
XmlEscapeNonELVisitor();
+                            XmlEscapeNonELVisitor v = new 
XmlEscapeNonELVisitor(
+                                    
pageInfo.isDeferredSyntaxAllowedAsLiteral());
                             el.visit(v);
                             value = v.getText();
                         } else {
@@ -1403,6 +1404,11 @@ class Validator {
 
         private static class XmlEscapeNonELVisitor extends 
ELParser.TextBuilder {
 
+            protected XmlEscapeNonELVisitor(
+                    boolean isDeferredSyntaxAllowedAsLiteral) {
+                super(isDeferredSyntaxAllowedAsLiteral);
+            }
+            
             @Override
             public void visit(Text n) throws JasperException {
                 output.append(xmlEscape(n.getText()));

Modified: tomcat/tc6.0.x/trunk/test/org/apache/jasper/compiler/TestELParser.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/test/org/apache/jasper/compiler/TestELParser.java?rev=1589635&r1=1589634&r2=1589635&view=diff
==============================================================================
--- tomcat/tc6.0.x/trunk/test/org/apache/jasper/compiler/TestELParser.java 
(original)
+++ tomcat/tc6.0.x/trunk/test/org/apache/jasper/compiler/TestELParser.java Thu 
Apr 24 08:35:06 2014
@@ -16,126 +16,289 @@
  */
 package org.apache.jasper.compiler;
 
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.el.ExpressionFactoryImpl;
 import org.apache.jasper.JasperException;
 import org.apache.jasper.compiler.ELNode.Nodes;
 import org.apache.jasper.compiler.ELParser.TextBuilder;
+import org.apache.jasper.el.ELContextImpl;
 
-import junit.framework.TestCase;
-
-public class TestELParser extends TestCase {
+/**
+ * You will need to keep your wits about you when working with this class. Keep
+ * in mind the following:
+ * <ul>
+ * <li>If in doubt, read the EL and JSP specifications. Twice.</li>
+ * <li>The escaping rules are complex and subtle. The explanation below (as 
well
+ *     as the tests and the implementation) may have missed an edge case 
despite
+ *     trying hard not to.
+ * <li>The strings passed to {@link #doTestParser(String,String)} are Java
+ *     escaped in the source code and will be unescaped before being used.</li>
+ * <li>LiteralExpressions always occur outside of "${...}" and "#{...}". 
Literal
+ *     expressions escape '$' and '#' with '\\' if '$' or '#' is followed by 
'{'
+ *     but neither '\\' nor '{' is escaped.</li>
+ * <li>LiteralStrings always occur inside "${...}" or "#{...}". Literal strings
+ *     escape '\'', '\"' and '\\' with '\\'. Escaping '\"' is optional if the
+ *     literal string is delimited by '\''. Escaping '\'' is optional if the
+ *     literal string is delimited by '\"'.</li>
+ * </ul>
+ */
+public class TestELParser {
 
+    @Test
     public void testText() throws JasperException {
-        doTestParser("foo");
+        doTestParser("foo", "foo");
     }
 
 
+    @Test
     public void testLiteral() throws JasperException {
-        doTestParser("${'foo'}");
+        doTestParser("${'foo'}", "foo");
     }
 
 
+    @Test
     public void testVariable() throws JasperException {
-        doTestParser("${test}");
+        doTestParser("${test}", null);
     }
 
 
+    @Test
     public void testFunction01() throws JasperException {
-        doTestParser("${do(x)}");
+        doTestParser("${do(x)}", null);
     }
 
 
+    @Test
     public void testFunction02() throws JasperException {
-        doTestParser("${do:it(x)}");
+        doTestParser("${do:it(x)}", null);
     }
 
 
+    @Test
     public void testFunction03() throws JasperException {
-        doTestParser("${do:it(x,y)}");
+        doTestParser("${do:it(x,y)}", null);
     }
 
 
+    @Test
     public void testFunction04() throws JasperException {
-        doTestParser("${do:it(x,y,z)}");
+        doTestParser("${do:it(x,y,z)}", null);
     }
 
 
+    @Test
     public void testCompound01() throws JasperException {
-        doTestParser("1${'foo'}1");
+        doTestParser("1${'foo'}1", "1foo1");
     }
 
 
+    @Test
     public void testCompound02() throws JasperException {
-        doTestParser("1${test}1");
+        doTestParser("1${test}1", null);
     }
 
 
+    @Test
     public void testCompound03() throws JasperException {
-        doTestParser("${foo}${bar}");
+        doTestParser("${foo}${bar}", null);
     }
 
 
+    @Test
     public void testTernary01() throws JasperException {
-        doTestParser("${true?true:false}");
+        doTestParser("${true?true:false}", "true");
     }
 
 
+    @Test
     public void testTernary02() throws JasperException {
-        doTestParser("${a==1?true:false}");
+        doTestParser("${a==1?true:false}", null);
     }
 
 
+    @Test
     public void testTernary03() throws JasperException {
-        doTestParser("${a eq1?true:false}");
+        doTestParser("${a eq1?true:false}", null);
     }
 
 
+    @Test
     public void testTernary04() throws JasperException {
-        doTestParser(" ${ a eq 1 ? true : false } ");
+        doTestParser(" ${ a eq 1 ? true : false } ", null);
     }
 
 
+    @Test
     public void testTernary05() throws JasperException {
         // Note this is invalid EL
-        doTestParser("${aeq1?true:false}");
+        doTestParser("${aeq1?true:false}", null);
     }
 
 
+    @Test
     public void testTernary06() throws JasperException {
-        doTestParser("${do:it(a eq1?true:false,y)}");
+        doTestParser("${do:it(a eq1?true:false,y)}", null);
     }
 
 
+    @Test
     public void testTernary07() throws JasperException {
-        doTestParser(" ${ do:it( a eq 1 ? true : false, y ) } ");
+        doTestParser(" ${ do:it( a eq 1 ? true : false, y ) } ", null);
     }
 
 
+    @Test
     public void testTernary08() throws JasperException {
-        doTestParser(" ${ do:it ( a eq 1 ? true : false, y ) } ");
+        doTestParser(" ${ do:it ( a eq 1 ? true : false, y ) } ", null);
     }
 
 
+    @Test
     public void testTernary09() throws JasperException {
-        doTestParser(" ${ do : it ( a eq 1 ? true : false, y ) } ");
+        doTestParser(" ${ do : it ( a eq 1 ? true : false, y ) } ", null);
     }
 
 
+    @Test
     public void testTernary10() throws JasperException {
-        doTestParser(" ${!empty my:link(foo)} ");
+        doTestParser(" ${!empty my:link(foo)} ", null);
     }
 
 
+    @Test
+    public void testTernary11() throws JasperException {
+        doTestParser("${true?'true':'false'}", "true");
+    }
+
+
+    @Test
+    public void testTernary12() throws JasperException {
+        doTestParser("${true?'tr\"ue':'false'}", "tr\"ue");
+    }
+
+
+    @Test
+    public void testTernary13() throws JasperException {
+        doTestParser("${true?'tr\\'ue':'false'}", "tr'ue");
+    }
+
+
+    @Test
     public void testTernaryBug56031() throws JasperException {
-        doTestParser("${my:link(!empty registration ? registration : 
'/test/registration')}");
+        doTestParser("${my:link(!empty registration ? registration : 
'/test/registration')}", null);
+    }
+
+
+    @Test
+    public void testQuotes01() throws JasperException {
+        doTestParser("'", "'");
+    }
+
+
+    @Test
+    public void testQuotes02() throws JasperException {
+        doTestParser("'${foo}'", null);
+    }
+
+
+    @Test
+    public void testQuotes03() throws JasperException {
+        doTestParser("'${'foo'}'", "'foo'");
+    }
+
+
+    @Test
+    public void testEscape01() throws JasperException {
+        doTestParser("${'\\\\'}", "\\");
+    }
+
+
+    @Test
+    public void testEscape02() throws JasperException {
+        doTestParser("\\\\x${'\\\\'}", "\\\\x\\");
+    }
+
+
+    @Test
+    public void testEscape03() throws JasperException {
+        doTestParser("\\\\", "\\\\");
+    }
+
+
+    @Test
+    public void testEscape04() throws JasperException {
+        doTestParser("\\$", "\\$");
+    }
+
+
+    @Test
+    public void testEscape05() throws JasperException {
+        doTestParser("\\#", "\\#");
+    }
+
+
+    @Test
+    public void testEscape07() throws JasperException {
+        doTestParser("${'\\\\$'}", "\\$");
     }
 
-    private void doTestParser(String input) throws JasperException {
-        Nodes nodes = ELParser.parse(input, false);
 
-        TextBuilder textBuilder = new TextBuilder();
+    @Test
+    public void testEscape08() throws JasperException {
+        doTestParser("${'\\\\#'}", "\\#");
+    }
+
+
+    @Test
+    public void testEscape09() throws JasperException {
+        doTestParser("\\${", "${");
+    }
+
+
+    @Test
+    public void testEscape10() throws JasperException {
+        doTestParser("\\#{", "#{");
+    }
+
+
+    private void doTestParser(String input, String expected) throws 
JasperException {
+        ELException elException = null;
+        String elResult = null;
+
+        // Don't try and evaluate expressions that depend on variables or 
functions
+        if (expected != null) {
+            try {
+                ExpressionFactory factory = new ExpressionFactoryImpl();
+                ELContext context = new ELContextImpl();
+                ValueExpression ve = factory.createValueExpression(context, 
input, String.class);
+                elResult = ve.getValue(context).toString();
+                Assert.assertEquals(expected, elResult);
+            } catch (ELException ele) {
+                elException = ele;
+            }
+        }
+
+        Nodes nodes = null;
+        try {
+            nodes = ELParser.parse(input, false);
+            Assert.assertNull(elException);
+        } catch (IllegalArgumentException iae) {
+            Assert.assertNotNull(elResult, elException);
+            // Not strictly true but enables us to report both
+            iae.initCause(elException);
+            throw iae;
+        }
+
+        TextBuilder textBuilder = new TextBuilder(false);
 
         nodes.visit(textBuilder);
 
-        assertEquals(input, textBuilder.getText());
+        Assert.assertEquals(input, textBuilder.getText());
     }
 }

Modified: tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml?rev=1589635&r1=1589634&r2=1589635&view=diff
==============================================================================
--- tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml Thu Apr 24 08:35:06 2014
@@ -135,6 +135,10 @@
         can only be used when running with Java 6 or later. The "1.8" options
         make sense only when running with Java 8 (or later). (kkolinko)
       </fix>
+      <fix>
+        <bug>56334</bug>: Fix a regression in the handling of back-slash
+        escaping introduced by the fix for <bug>55735</bug>. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Web applications">



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to