diff options
| author | 2016-04-05 15:24:43 -0700 | |
|---|---|---|
| committer | 2016-04-05 16:12:07 -0700 | |
| commit | 3ae3557ea3a9ad8429de9db14de62a4214a07cdc (patch) | |
| tree | 2cfc0a05fec7062be519690bdbd473a7dfbda41c | |
| parent | fdb3073a28fcb8c60bc067b524d370301eeda7c1 (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.java | 21 | ||||
| -rw-r--r-- | core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java | 105 |
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, """, null, null, null, "&", 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, "<", null, ">", null, // 56-63 + "�", "", "", "", "", "", "", "", // 0-7 + "", "	", " ", "", "", " ", "", "", // 8-15 + "", "", "", "", "", "", "", "", // 16-23 + "", "", "", "", "", "", "", "", // 24-31 + null, null, """, null, null, null, "&", 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, "<", null, ">", 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); + } } |