You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ta...@apache.org on 2019/07/23 13:13:17 UTC

[myfaces] branch master updated: MYFACES-4294 Emoji characters are filtered in ajax requests

This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces.git


The following commit(s) were added to refs/heads/master by this push:
     new c241590  MYFACES-4294 Emoji characters are filtered in ajax requests
c241590 is described below

commit c241590aadafdfc478b86143c87f4b1000270238
Author: Thomas Andraschko <ta...@apache.org>
AuthorDate: Tue Jul 23 15:13:09 2019 +0200

    MYFACES-4294 Emoji characters are filtered in ajax requests
---
 .../util/IllegalXmlCharacterFilterWriter.java      | 94 +++++++++++++++++++---
 .../context/PartialResponseWriterImplTest.java     | 53 ++++++++++++
 2 files changed, 134 insertions(+), 13 deletions(-)

diff --git a/impl/src/main/java/org/apache/myfaces/util/IllegalXmlCharacterFilterWriter.java b/impl/src/main/java/org/apache/myfaces/util/IllegalXmlCharacterFilterWriter.java
index fc9a22a..3b6f5e1 100644
--- a/impl/src/main/java/org/apache/myfaces/util/IllegalXmlCharacterFilterWriter.java
+++ b/impl/src/main/java/org/apache/myfaces/util/IllegalXmlCharacterFilterWriter.java
@@ -41,7 +41,7 @@ public class IllegalXmlCharacterFilterWriter extends FilterWriter
     @Override
     public void write(int c) throws IOException 
     {
-        if (isInvalidChar((char) c))
+        if (isInvalidChar(c))
         {
             super.write((int) BLANK_CHAR);
         }
@@ -69,18 +69,48 @@ public class IllegalXmlCharacterFilterWriter extends FilterWriter
         {
             return null;
         }
-
-        char[] encoded = null;
         
-        for (int i = off; i < (off + len); i++)
+        int to = off + len;
+        boolean surrogateResolved = false;
+        char c;
+        int codePoint;
+        char cNext;
+        
+        char[] encoded = null;
+        for (int i = off; i < to; i++)
         {
-            if (isInvalidChar(str.charAt(i)))
+            if (surrogateResolved == true)
+            {
+                surrogateResolved = false;
+                continue;
+            }
+            
+            c = str.charAt(i);
+            codePoint = c;
+
+            if (Character.isHighSurrogate(c) && i + 1 < to)
+            {
+                cNext = str.charAt(i + 1);
+                if (Character.isLowSurrogate(cNext))
+                {
+                    codePoint = Character.toCodePoint(c, cNext);
+                    surrogateResolved = true;
+                }
+            }
+
+            // try to resolve surrogate, this is required e.g. for emojis
+            if ((!surrogateResolved && Character.isSurrogate(c)) || isInvalidChar(codePoint))
             {
                 if (encoded == null)
                 {
                     encoded = str.toCharArray();
                 }
                 encoded[i] = BLANK_CHAR;
+                
+                if (surrogateResolved)
+                {
+                    encoded[i + 1] = BLANK_CHAR;
+                }
             }
         }
 
@@ -97,36 +127,74 @@ public class IllegalXmlCharacterFilterWriter extends FilterWriter
         if (cbuf == null)
         {
             return null;
-        }        
+        }
 
-        for (int i = off; i < (off + len); i++)
+        int to = off + len;
+        
+        boolean surrogateResolved = false;
+        char c;
+        int codePoint;
+        char cNext;
+
+        for (int i = off; i < to; i++)
         {
-            if (isInvalidChar(cbuf[i]))
+            if (surrogateResolved == true)
+            {
+                surrogateResolved = false;
+                continue;
+            }
+            
+            c = cbuf[i];
+            codePoint = c;
+
+            // try to resolve surrogate, this is required e.g. for emojis
+            if (Character.isHighSurrogate(c) && i + 1 < to)
+            {
+                cNext = cbuf[i + 1];
+                if (Character.isLowSurrogate(cNext))
+                {
+                    codePoint = Character.toCodePoint(c, cNext);
+                    surrogateResolved = true;
+                }
+            }
+            
+            if ((!surrogateResolved && Character.isSurrogate(c)) || isInvalidChar(codePoint))
             {
                 cbuf[i] = BLANK_CHAR;
+                
+                if (surrogateResolved)
+                {
+                    cbuf[i + 1] = BLANK_CHAR;
+                }
             }
         }
         return cbuf;
     }
 
-    private static boolean isInvalidChar(char c)
+    private static boolean isInvalidChar(int codePoint)
     {
-        if (Character.isSurrogate(c)) 
+        if (codePoint == 1113088)
         {
             return true;
         }
-        if (c == '\u0009' || c == '\n' || c == '\r') 
+
+        if (codePoint == 0x9 || codePoint == 0xA || codePoint == 0xD) 
         {
             return false;
         }
-        if (c >= '\u0020' && c <= '\uD7FF') 
+        if (codePoint >= 0x20 && codePoint <= 0xD7FF) 
         {
             return false;
         }
-        if (c >= '\uE000' && c <= '\uFFFD') 
+        if (codePoint >= 0xE000 && codePoint <= 0xFFFD) 
         {
             return false;
         }
+        if (codePoint >= 0x10000 && codePoint <= 0x10FFFF) 
+        {
+            return false;
+        }
+
         return true;
     }
 }
diff --git a/impl/src/test/java/org/apache/myfaces/context/PartialResponseWriterImplTest.java b/impl/src/test/java/org/apache/myfaces/context/PartialResponseWriterImplTest.java
index a9c4d2d..896047e 100644
--- a/impl/src/test/java/org/apache/myfaces/context/PartialResponseWriterImplTest.java
+++ b/impl/src/test/java/org/apache/myfaces/context/PartialResponseWriterImplTest.java
@@ -32,6 +32,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
 
 import org.apache.myfaces.renderkit.html.HtmlResponseWriterImpl;
 import org.apache.myfaces.test.base.AbstractJsfTestCase;
+import org.junit.Test;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.xml.sax.InputSource;
@@ -339,6 +340,36 @@ public class PartialResponseWriterImplTest extends AbstractJsfTestCase {
             fail(e.toString());
         }
     }
+    
+    public void testWriteSkipEmoji() {
+        _writer = createTestProbe();
+        try {
+            String input = "foo😀";
+            _writer.writeText(input, null);
+            
+            String escaped = _contentCollector.toString();
+            
+            assertEquals("All illegal XML unicode characters should have been replaced by spaces", input, escaped.trim());
+
+        } catch (IOException e) {
+            fail(e.toString());
+        }
+    }
+    
+    public void testWriteSkipPictographs() {
+        _writer = createTestProbe();
+        try {
+            String input = "foo🏺";
+            _writer.writeText(input, null);
+            
+            String escaped = _contentCollector.toString();
+            
+            assertEquals("All illegal XML unicode characters should have been replaced by spaces", input, escaped.trim());
+
+        } catch (IOException e) {
+            fail(e.toString());
+        }
+    }
 
     /**
      * creates a new test probe (aka response writer)
@@ -349,4 +380,26 @@ public class PartialResponseWriterImplTest extends AbstractJsfTestCase {
         return new PartialResponseWriterImpl(new HtmlResponseWriterImpl(_contentCollector, null, "UTF-8"));
     }
 
+    /*
+    @Test
+    public void testPerf() throws IOException {
+        
+        _contentCollector = new StringWriter();
+        _writer = createTestProbe();
+        for (int i = 0; i < 1000000; i++)
+        {
+            _writer.write("test");
+        }
+        
+        long start = System.currentTimeMillis();
+        _contentCollector = new StringWriter();
+        _writer = createTestProbe();
+        for (int i = 0; i < 1000000; i++)
+        {
+            _writer.write("test");
+        }
+        long end = System.currentTimeMillis();
+        throw new RuntimeException((end - start) + "ms");
+    }
+    */
 }