Updated Branches:
  refs/heads/master 684b8363e -> a2cc21eba

WICKET-4937 Add IResponseFilter that can filter out invalid XML characters


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/a2cc21eb
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/a2cc21eb
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/a2cc21eb

Branch: refs/heads/master
Commit: a2cc21eba63e70afc486b771ab7f7e1109a5476f
Parents: 684b836
Author: Martin Tzvetanov Grigorov <[email protected]>
Authored: Thu Dec 20 10:31:53 2012 +0200
Committer: Martin Tzvetanov Grigorov <[email protected]>
Committed: Thu Dec 20 10:32:30 2012 +0200

----------------------------------------------------------------------
 .../response/filter/XmlCleaningResponseFilter.java |  119 +++++++++++++++
 .../filter/XmlCleaningResponseFilterTest.java      |   90 +++++++++++
 2 files changed, 209 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/a2cc21eb/wicket-core/src/main/java/org/apache/wicket/response/filter/XmlCleaningResponseFilter.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/response/filter/XmlCleaningResponseFilter.java
 
b/wicket-core/src/main/java/org/apache/wicket/response/filter/XmlCleaningResponseFilter.java
new file mode 100644
index 0000000..e55621d
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/response/filter/XmlCleaningResponseFilter.java
@@ -0,0 +1,119 @@
+package org.apache.wicket.response.filter;
+
+import org.apache.wicket.util.string.AppendingStringBuffer;
+
+/**
+ * An IResponseFilter that removes all invalid XML characters.
+ * By default it is used only for Wicket <em>Ajax</em> responses.
+ *
+ * <p>If the application needs to use it for other use cases then it can 
either override
+ * {@linkplain #shouldFilter(AppendingStringBuffer)} in the case it is used as 
IResponseFilter or
+ * {@linkplain #stripNonValidXMLCharacters(AppendingStringBuffer)} can be used 
directly.
+ * </p>
+ *
+ * <p>Usage:
+ *
+ *     MyApplication.java
+ *     <code><pre>
+ *         public void init() {
+ *             super.init();
+ *
+ *             getRequestCycleSettings().addResponseFilter(new 
XmlCleaningResponseFilter());
+ *         }
+ *     </pre></code>
+ * </p>
+ */
+public class XmlCleaningResponseFilter implements IResponseFilter
+{
+       @Override
+       public AppendingStringBuffer filter(AppendingStringBuffer 
responseBuffer)
+       {
+               AppendingStringBuffer result = responseBuffer;
+               if (shouldFilter(responseBuffer))
+               {
+                       result = stripNonValidXMLCharacters(responseBuffer);
+               }
+               return result;
+       }
+
+       /**
+        * Decides whether the filter should be applied.
+        *
+        * @param responseBuffer The buffer to filter
+        * @return {@code true} if the buffer brings Ajax response
+        */
+       protected boolean shouldFilter(AppendingStringBuffer responseBuffer)
+       {
+               // To avoid reading the whole buffer for non-Ajax responses
+               // read just the first N chars. A candidate can start with:
+               // <?xml version="1.0" encoding="UTF-8" 
standalone="yes"?><ajax-response>
+               int min = Math.min(150, responseBuffer.length());
+               String firstNChars = responseBuffer.substring(0, min);
+               return firstNChars.contains("<ajax-response>");
+       }
+
+       /**
+        * This method ensures that the output String has only
+        * valid XML unicode characters as specified by the
+        * XML 1.0 standard. For reference, please see
+        * <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char";>the
+        * standard</a>. This method will return an empty
+        * String if the input is null or empty.
+        *
+        * @param input The StringBuffer whose non-valid characters we want to 
remove.
+        * @return The in String, stripped of non-valid characters.
+        */
+       public AppendingStringBuffer 
stripNonValidXMLCharacters(AppendingStringBuffer input)
+       {
+               char[] chars = input.getValue();
+               AppendingStringBuffer out = null;
+
+               int codePoint;
+
+               int i = 0;
+
+               while (i < input.length())
+               {
+                       codePoint = Character.codePointAt(chars, i, 
chars.length);
+
+                       if (!isValidXmlChar(codePoint))
+                       {
+                               if (out == null)
+                               {
+                                       out = new 
AppendingStringBuffer(chars.length);
+                                       out.append(input.subSequence(0, i));
+                               }
+                               else
+                               {
+                                       
out.append(Character.toChars(codePoint));
+                               }
+                       }
+                       else if (out != null)
+                       {
+                               out.append(Character.toChars(codePoint));
+                       }
+
+                       // Increment with the number of code units(java chars) 
needed to represent a Unicode char.
+                       i += Character.charCount(codePoint);
+               }
+
+               return out != null ? out : input;
+       }
+
+       /**
+        * Checks whether the character represented by this codePoint is
+        * a valid in XML documents.
+        *
+        * @param codePoint The codePoint for the checked character
+        * @return {@code true} if the character can be used in XML documents
+        */
+       protected boolean isValidXmlChar(int codePoint)
+       {
+               return (codePoint == 0x9) ||
+                       (codePoint == 0xA) ||
+                       (codePoint == 0xD) ||
+                       ((codePoint >= 0x20) && (codePoint <= 0xD7FF)) ||
+                       ((codePoint >= 0xE000) && (codePoint <= 0xFFFD)) ||
+                       ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF));
+       }
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/a2cc21eb/wicket-core/src/test/java/org/apache/wicket/response/filter/XmlCleaningResponseFilterTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/response/filter/XmlCleaningResponseFilterTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/response/filter/XmlCleaningResponseFilterTest.java
new file mode 100644
index 0000000..fbba553
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/response/filter/XmlCleaningResponseFilterTest.java
@@ -0,0 +1,90 @@
+package org.apache.wicket.response.filter;
+
+import org.apache.wicket.util.string.AppendingStringBuffer;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for XmlCleaningResponseFilter
+ */
+public class XmlCleaningResponseFilterTest extends Assert {
+
+       public static final String AJAX_RESPONSE_START = "<ajax-response>b";
+       public static final String AJAX_RESPONSE_END = "b</ajax-response>";
+
+       /**
+        * Tests that invalid XML characters are removed
+        * @throws Exception
+        */
+       @Test
+       public void filterInvalid() throws Exception
+       {
+               XmlCleaningResponseFilter filter = new 
XmlCleaningResponseFilter();
+               int[] invalidChars = new int[] {0x0008, 0x0010, 0xD800, 0xDDDD, 
0xFFFE};
+
+               for (int invalidChar : invalidChars)
+               {
+                       CharSequence text = createText(invalidChar);
+
+                       AppendingStringBuffer filtered = filter.filter(new 
AppendingStringBuffer(text));
+                       assertEquals(String.format("checking Unicode codepoint 
0x%X:", invalidChar), AJAX_RESPONSE_START+AJAX_RESPONSE_END, 
filtered.toString());
+               }
+       }
+
+       /**
+        * Tests that valid XML characters are preserved
+        * @throws Exception
+        */
+       @Test
+       public void filterValid() throws Exception
+       {
+               XmlCleaningResponseFilter filter = new 
XmlCleaningResponseFilter();
+               int[] validChars = new int[] {0x9, 0xA, 'a', 0xE000, 0xFFFC, 
0x10400};
+
+               for (int validChar : validChars)
+               {
+                       CharSequence text = createText(validChar);
+
+                       AppendingStringBuffer filtered = filter.filter(new 
AppendingStringBuffer(text));
+                       assertEquals(String.format("checking Unicode codepoint 
0x%X:", validChar), text.toString(), filtered.toString());
+               }
+       }
+
+       // using a int because a Java char cannot represent all Unicode 
characters; some require two chars.
+       private CharSequence createText(int ch)
+       {
+               String character = new String(new int[] {ch}, 0, 1);
+               return new StringBuilder()
+                               .append(AJAX_RESPONSE_START)
+                               .append(character)
+                               .append(AJAX_RESPONSE_END);
+       }
+
+       /**
+        * Asserts that XmlCleaningResponseFilter#shouldFilter() returns true 
when
+        * there is <ajax-response> in the text to filter
+        * @throws Exception
+        */
+       @Test
+       public void shouldFilter() throws Exception
+       {
+               XmlFilter filter = new XmlFilter();
+
+               assertFalse(filter.shouldFilter(new 
AppendingStringBuffer("anything")));
+
+               assertTrue(filter.shouldFilter(
+                               new AppendingStringBuffer("<?xml 
version=\"1.0\" encoding=\"UTF-8\"" +
+                                               " 
standalone=\"yes\"><ajax-response></ajax-response>")));
+       }
+
+       /**
+        * Makes #shouldFilter() method public
+        */
+       private static class XmlFilter extends XmlCleaningResponseFilter
+       {
+               @Override
+               public boolean shouldFilter(AppendingStringBuffer 
responseBuffer) {
+                       return super.shouldFilter(responseBuffer);
+               }
+       }
+}

Reply via email to