summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Makoto Onuki <omakoto@google.com> 2016-04-05 15:24:43 -0700
committer Makoto Onuki <omakoto@google.com> 2016-04-05 16:12:07 -0700
commit3ae3557ea3a9ad8429de9db14de62a4214a07cdc (patch)
tree2cfc0a05fec7062be519690bdbd473a7dfbda41c
parentfdb3073a28fcb8c60bc067b524d370301eeda7c1 (diff)
Make FastXmlSerializer more suitable to persist arbitrary strings
- Encode '\u000' - '\u001F', so KXmlParser can read them properly. Otherwise KXmlParser will ignore CRs/LFs in attributes, and CRs in text. - Originally FastXmlSerializer would throw if a string contains dangling surrogate pairs. Now we REPLACE them with. Bug 27792649 Change-Id: I10c547dad2475b68f60e9e8208d9a3eae8e20063
-rw-r--r--core/java/com/android/internal/util/FastXmlSerializer.java21
-rw-r--r--core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java105
2 files changed, 117 insertions, 9 deletions
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
index 7a040801a9a5..3c1d2d6ce581 100644
--- a/core/java/com/android/internal/util/FastXmlSerializer.java
+++ b/core/java/com/android/internal/util/FastXmlSerializer.java
@@ -28,6 +28,7 @@ import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
@@ -38,14 +39,14 @@ import java.nio.charset.UnsupportedCharsetException;
*/
public class FastXmlSerializer implements XmlSerializer {
private static final String ESCAPE_TABLE[] = new String[] {
- null, null, null, null, null, null, null, null, // 0-7
- null, null, null, null, null, null, null, null, // 8-15
- null, null, null, null, null, null, null, null, // 16-23
- null, null, null, null, null, null, null, null, // 24-31
- null, null, "&quot;", null, null, null, "&amp;", null, // 32-39
- null, null, null, null, null, null, null, null, // 40-47
- null, null, null, null, null, null, null, null, // 48-55
- null, null, null, null, "&lt;", null, "&gt;", null, // 56-63
+ "&#0;", "&#1;", "&#2;", "&#3;", "&#4;", "&#5;", "&#6;", "&#7;", // 0-7
+ "&#8;", "&#9;", "&#10;", "&#11;", "&#12;", "&#13;", "&#14;", "&#15;", // 8-15
+ "&#16;", "&#17;", "&#18;", "&#19;", "&#20;", "&#21;", "&#22;", "&#23;", // 16-23
+ "&#24;", "&#25;", "&#26;", "&#27;", "&#28;", "&#29;", "&#30;", "&#31;", // 24-31
+ null, null, "&quot;", null, null, null, "&amp;", null, // 32-39
+ null, null, null, null, null, null, null, null, // 40-47
+ null, null, null, null, null, null, null, null, // 48-55
+ null, null, null, null, "&lt;", null, "&gt;", null, // 56-63
};
private static final int BUFFER_LEN = 8192;
@@ -310,7 +311,9 @@ public class FastXmlSerializer implements XmlSerializer {
throw new IllegalArgumentException();
if (true) {
try {
- mCharset = Charset.forName(encoding).newEncoder();
+ mCharset = Charset.forName(encoding).newEncoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE);
} catch (IllegalCharsetNameException e) {
throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
encoding).initCause(e));
diff --git a/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
index 5f36c2d0c29b..3cef33621a01 100644
--- a/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
@@ -16,16 +16,32 @@
package com.android.internal.util;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import android.util.Xml;
+
import junit.framework.TestCase;
+import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
/**
* Tests for {@link FastXmlSerializer}
*/
+@SmallTest
public class FastXmlSerializerTest extends TestCase {
+ private static final String TAG = "FastXmlSerializerTest";
+
+ private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH TRUE.
+
+ private static final String ROOT_TAG = "root";
+ private static final String ATTR = "attr";
+
public void testEmptyText() throws Exception {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
@@ -44,4 +60,93 @@ public class FastXmlSerializerTest extends TestCase {
assertEquals("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<string name=\"meow\"></string>\n", stream.toString());
}
+
+ private boolean checkPreserved(String description, String str) {
+ boolean ok = true;
+ byte[] data;
+ try (final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(baos, StandardCharsets.UTF_16.name());
+ out.startDocument(null, true);
+
+ out.startTag(null, ROOT_TAG);
+ out.attribute(null, ATTR, str);
+ out.text(str);
+ out.endTag(null, ROOT_TAG);
+
+ out.endDocument();
+ baos.flush();
+ data = baos.toByteArray();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to serialize: " + description, e);
+ return false;
+ }
+
+ if (ENABLE_DUMP) {
+ Log.d(TAG, "Dump:");
+ Log.d(TAG, new String(data));
+ }
+
+ try (final ByteArrayInputStream baos = new ByteArrayInputStream(data)) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(baos, StandardCharsets.UTF_16.name());
+
+ int type;
+ String tag = null;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG) {
+ tag = parser.getName();
+ if (ROOT_TAG.equals(tag)) {
+ String read = parser.getAttributeValue(null, ATTR);
+ if (!str.equals(read)) {
+ Log.e(TAG, "Attribute not preserved: " + description
+ + " input=\"" + str + "\", but read=\"" + read + "\"");
+ ok = false;
+ }
+ }
+ }
+ if (type == XmlPullParser.TEXT && ROOT_TAG.equals(tag)) {
+ String read = parser.getText();
+ if (!str.equals(parser.getText())) {
+ Log.e(TAG, "Text not preserved: " + description
+ + " input=\"" + str + "\", but read=\"" + read + "\"");
+ ok = false;
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to parse: " + description, e);
+ return false;
+ }
+ return ok;
+ }
+
+ private boolean check(String description, String str) throws Exception {
+ boolean ok = false;
+ ok |= checkPreserved(description, str);
+ ok |= checkPreserved(description + " wrapped with spaces" ," " + str + " ");
+ return ok;
+ }
+
+ @LargeTest
+ public void testAllCharacters() throws Exception {
+ boolean ok = true;
+ for (int i = 0; i < 0xffff; i++) {
+ if (0xd800 <= i && i <= 0xdfff) {
+ // Surrogate pair characters.
+ continue;
+ }
+ ok &= check("char: " + i, String.valueOf((char) i));
+ }
+ // Dangling surrogate pairs. We can't preserve them.
+ assertFalse(check("+ud800", "\ud800"));
+ assertFalse(check("+udc00", "\udc00"));
+
+ for (int i = 0xd800; i < 0xdc00; i ++) {
+ for (int j = 0xdc00; j < 0xe000; j++) {
+ ok &= check("char: " + i, String.valueOf((char) i) + String.valueOf((char) j));
+ }
+ }
+ assertTrue("Some tests failed. See logcat for details.", ok);
+ }
}