Loading core/java/android/net/Uri.java +2 −3 Original line number Diff line number Diff line Loading @@ -24,8 +24,6 @@ import android.os.Parcelable; import android.os.StrictMode; import android.util.Log; import libcore.net.UriCodec; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; Loading Loading @@ -1959,7 +1957,8 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { if (s == null) { return null; } return UriCodec.decode(s, false, StandardCharsets.UTF_8, false); return UriCodec.decode( s, false /* convertPlus */, StandardCharsets.UTF_8, false /* throwOnFailure */); } /** Loading core/java/android/net/UriCodec.java 0 → 100644 +176 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net; import java.net.URISyntaxException; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; /** * Decodes “application/x-www-form-urlencoded” content. * * @hide */ public final class UriCodec { private UriCodec() {} /** * Interprets a char as hex digits, returning a number from -1 (invalid char) to 15 ('f'). */ private static int hexCharToValue(char c) { if ('0' <= c && c <= '9') { return c - '0'; } if ('a' <= c && c <= 'f') { return 10 + c - 'a'; } if ('A' <= c && c <= 'F') { return 10 + c - 'A'; } return -1; } private static URISyntaxException unexpectedCharacterException( String uri, String name, char unexpected, int index) { String nameString = (name == null) ? "" : " in [" + name + "]"; return new URISyntaxException( uri, "Unexpected character" + nameString + ": " + unexpected, index); } private static char getNextCharacter(String uri, int index, int end, String name) throws URISyntaxException { if (index >= end) { String nameString = (name == null) ? "" : " in [" + name + "]"; throw new URISyntaxException( uri, "Unexpected end of string" + nameString, index); } return uri.charAt(index); } /** * Decode a string according to the rules of this decoder. * * - if {@code convertPlus == true} all ‘+’ chars in the decoded output are converted to ‘ ‘ * (white space) * - if {@code throwOnFailure == true}, an {@link IllegalArgumentException} is thrown for * invalid inputs. Else, U+FFFd is emitted to the output in place of invalid input octets. */ public static String decode( String s, boolean convertPlus, Charset charset, boolean throwOnFailure) { StringBuilder builder = new StringBuilder(s.length()); appendDecoded(builder, s, convertPlus, charset, throwOnFailure); return builder.toString(); } /** * Character to be output when there's an error decoding an input. */ private static final char INVALID_INPUT_CHARACTER = '\ufffd'; private static void appendDecoded( StringBuilder builder, String s, boolean convertPlus, Charset charset, boolean throwOnFailure) { CharsetDecoder decoder = charset.newDecoder() .onMalformedInput(CodingErrorAction.REPLACE) .replaceWith("\ufffd") .onUnmappableCharacter(CodingErrorAction.REPORT); // Holds the bytes corresponding to the escaped chars being read (empty if the last char // wasn't a escaped char). ByteBuffer byteBuffer = ByteBuffer.allocate(s.length()); int i = 0; while (i < s.length()) { char c = s.charAt(i); i++; switch (c) { case '+': flushDecodingByteAccumulator( builder, decoder, byteBuffer, throwOnFailure); builder.append(convertPlus ? ' ' : '+'); break; case '%': // Expect two characters representing a number in hex. byte hexValue = 0; for (int j = 0; j < 2; j++) { try { c = getNextCharacter(s, i, s.length(), null /* name */); } catch (URISyntaxException e) { // Unexpected end of input. if (throwOnFailure) { throw new IllegalArgumentException(e); } else { flushDecodingByteAccumulator( builder, decoder, byteBuffer, throwOnFailure); builder.append(INVALID_INPUT_CHARACTER); return; } } i++; int newDigit = hexCharToValue(c); if (newDigit < 0) { if (throwOnFailure) { throw new IllegalArgumentException( unexpectedCharacterException(s, null /* name */, c, i - 1)); } else { flushDecodingByteAccumulator( builder, decoder, byteBuffer, throwOnFailure); builder.append(INVALID_INPUT_CHARACTER); break; } } hexValue = (byte) (hexValue * 0x10 + newDigit); } byteBuffer.put(hexValue); break; default: flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure); builder.append(c); } } flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure); } private static void flushDecodingByteAccumulator( StringBuilder builder, CharsetDecoder decoder, ByteBuffer byteBuffer, boolean throwOnFailure) { if (byteBuffer.position() == 0) { return; } byteBuffer.flip(); try { builder.append(decoder.decode(byteBuffer)); } catch (CharacterCodingException e) { if (throwOnFailure) { throw new IllegalArgumentException(e); } else { builder.append(INVALID_INPUT_CHARACTER); } } finally { // Use the byte buffer to write again. byteBuffer.flip(); byteBuffer.limit(byteBuffer.capacity()); } } } core/tests/coretests/src/android/net/UriCodecTest.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net; import junit.framework.TestCase; import java.nio.charset.StandardCharsets; /** * Tests for {@link UriCodec} */ public class UriCodecTest extends TestCase { public void testDecode_emptyString_returnsEmptyString() { assertEquals("", UriCodec.decode("", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */)); } public void testDecode_wrongHexDigit_fails() { try { // %p in the end. UriCodec.decode("ab%2f$%C4%82%25%e0%a1%80%p", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */); fail("Expected URISyntaxException"); } catch (IllegalArgumentException expected) { // Expected. } } public void testDecode_secondHexDigitWrong_fails() { try { // %1p in the end. UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%1p", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */); fail("Expected URISyntaxException"); } catch (IllegalArgumentException expected) { // Expected. } } public void testDecode_endsWithPercent_fails() { try { // % in the end. UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */); fail("Expected URISyntaxException"); } catch (IllegalArgumentException expected) { // Expected. } } public void testDecode_dontThrowException_appendsUnknownCharacter() { assertEquals("ab/$\u0102%\u0840\ufffd", UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%", false /* convertPlus */, StandardCharsets.UTF_8, false /* throwOnFailure */)); } public void testDecode_convertPlus() { assertEquals("ab/$\u0102% \u0840", UriCodec.decode("ab%2f$%c4%82%25+%e0%a1%80", true /* convertPlus */, StandardCharsets.UTF_8, false /* throwOnFailure */)); } // Last character needs decoding (make sure we are flushing the buffer with chars to decode). public void testDecode_lastCharacter() { assertEquals("ab/$\u0102%\u0840", UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */)); } // Check that a second row of encoded characters is decoded properly (internal buffers are // reset properly). public void testDecode_secondRowOfEncoded() { assertEquals("ab/$\u0102%\u0840aa\u0840", UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80aa%e0%a1%80", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */)); } } Loading
core/java/android/net/Uri.java +2 −3 Original line number Diff line number Diff line Loading @@ -24,8 +24,6 @@ import android.os.Parcelable; import android.os.StrictMode; import android.util.Log; import libcore.net.UriCodec; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; Loading Loading @@ -1959,7 +1957,8 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { if (s == null) { return null; } return UriCodec.decode(s, false, StandardCharsets.UTF_8, false); return UriCodec.decode( s, false /* convertPlus */, StandardCharsets.UTF_8, false /* throwOnFailure */); } /** Loading
core/java/android/net/UriCodec.java 0 → 100644 +176 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net; import java.net.URISyntaxException; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; /** * Decodes “application/x-www-form-urlencoded” content. * * @hide */ public final class UriCodec { private UriCodec() {} /** * Interprets a char as hex digits, returning a number from -1 (invalid char) to 15 ('f'). */ private static int hexCharToValue(char c) { if ('0' <= c && c <= '9') { return c - '0'; } if ('a' <= c && c <= 'f') { return 10 + c - 'a'; } if ('A' <= c && c <= 'F') { return 10 + c - 'A'; } return -1; } private static URISyntaxException unexpectedCharacterException( String uri, String name, char unexpected, int index) { String nameString = (name == null) ? "" : " in [" + name + "]"; return new URISyntaxException( uri, "Unexpected character" + nameString + ": " + unexpected, index); } private static char getNextCharacter(String uri, int index, int end, String name) throws URISyntaxException { if (index >= end) { String nameString = (name == null) ? "" : " in [" + name + "]"; throw new URISyntaxException( uri, "Unexpected end of string" + nameString, index); } return uri.charAt(index); } /** * Decode a string according to the rules of this decoder. * * - if {@code convertPlus == true} all ‘+’ chars in the decoded output are converted to ‘ ‘ * (white space) * - if {@code throwOnFailure == true}, an {@link IllegalArgumentException} is thrown for * invalid inputs. Else, U+FFFd is emitted to the output in place of invalid input octets. */ public static String decode( String s, boolean convertPlus, Charset charset, boolean throwOnFailure) { StringBuilder builder = new StringBuilder(s.length()); appendDecoded(builder, s, convertPlus, charset, throwOnFailure); return builder.toString(); } /** * Character to be output when there's an error decoding an input. */ private static final char INVALID_INPUT_CHARACTER = '\ufffd'; private static void appendDecoded( StringBuilder builder, String s, boolean convertPlus, Charset charset, boolean throwOnFailure) { CharsetDecoder decoder = charset.newDecoder() .onMalformedInput(CodingErrorAction.REPLACE) .replaceWith("\ufffd") .onUnmappableCharacter(CodingErrorAction.REPORT); // Holds the bytes corresponding to the escaped chars being read (empty if the last char // wasn't a escaped char). ByteBuffer byteBuffer = ByteBuffer.allocate(s.length()); int i = 0; while (i < s.length()) { char c = s.charAt(i); i++; switch (c) { case '+': flushDecodingByteAccumulator( builder, decoder, byteBuffer, throwOnFailure); builder.append(convertPlus ? ' ' : '+'); break; case '%': // Expect two characters representing a number in hex. byte hexValue = 0; for (int j = 0; j < 2; j++) { try { c = getNextCharacter(s, i, s.length(), null /* name */); } catch (URISyntaxException e) { // Unexpected end of input. if (throwOnFailure) { throw new IllegalArgumentException(e); } else { flushDecodingByteAccumulator( builder, decoder, byteBuffer, throwOnFailure); builder.append(INVALID_INPUT_CHARACTER); return; } } i++; int newDigit = hexCharToValue(c); if (newDigit < 0) { if (throwOnFailure) { throw new IllegalArgumentException( unexpectedCharacterException(s, null /* name */, c, i - 1)); } else { flushDecodingByteAccumulator( builder, decoder, byteBuffer, throwOnFailure); builder.append(INVALID_INPUT_CHARACTER); break; } } hexValue = (byte) (hexValue * 0x10 + newDigit); } byteBuffer.put(hexValue); break; default: flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure); builder.append(c); } } flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure); } private static void flushDecodingByteAccumulator( StringBuilder builder, CharsetDecoder decoder, ByteBuffer byteBuffer, boolean throwOnFailure) { if (byteBuffer.position() == 0) { return; } byteBuffer.flip(); try { builder.append(decoder.decode(byteBuffer)); } catch (CharacterCodingException e) { if (throwOnFailure) { throw new IllegalArgumentException(e); } else { builder.append(INVALID_INPUT_CHARACTER); } } finally { // Use the byte buffer to write again. byteBuffer.flip(); byteBuffer.limit(byteBuffer.capacity()); } } }
core/tests/coretests/src/android/net/UriCodecTest.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net; import junit.framework.TestCase; import java.nio.charset.StandardCharsets; /** * Tests for {@link UriCodec} */ public class UriCodecTest extends TestCase { public void testDecode_emptyString_returnsEmptyString() { assertEquals("", UriCodec.decode("", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */)); } public void testDecode_wrongHexDigit_fails() { try { // %p in the end. UriCodec.decode("ab%2f$%C4%82%25%e0%a1%80%p", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */); fail("Expected URISyntaxException"); } catch (IllegalArgumentException expected) { // Expected. } } public void testDecode_secondHexDigitWrong_fails() { try { // %1p in the end. UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%1p", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */); fail("Expected URISyntaxException"); } catch (IllegalArgumentException expected) { // Expected. } } public void testDecode_endsWithPercent_fails() { try { // % in the end. UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */); fail("Expected URISyntaxException"); } catch (IllegalArgumentException expected) { // Expected. } } public void testDecode_dontThrowException_appendsUnknownCharacter() { assertEquals("ab/$\u0102%\u0840\ufffd", UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%", false /* convertPlus */, StandardCharsets.UTF_8, false /* throwOnFailure */)); } public void testDecode_convertPlus() { assertEquals("ab/$\u0102% \u0840", UriCodec.decode("ab%2f$%c4%82%25+%e0%a1%80", true /* convertPlus */, StandardCharsets.UTF_8, false /* throwOnFailure */)); } // Last character needs decoding (make sure we are flushing the buffer with chars to decode). public void testDecode_lastCharacter() { assertEquals("ab/$\u0102%\u0840", UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */)); } // Check that a second row of encoded characters is decoded properly (internal buffers are // reset properly). public void testDecode_secondRowOfEncoded() { assertEquals("ab/$\u0102%\u0840aa\u0840", UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80aa%e0%a1%80", false /* convertPlus */, StandardCharsets.UTF_8, true /* throwOnFailure */)); } }