diff options
| author | Neil Fuller <nfuller@google.com> | 2018-07-24 10:30:45 -0700 |
|---|---|---|
| committer | android-build-merger <android-build-merger@google.com> | 2018-07-24 10:30:45 -0700 |
| commit | b432ba4ee07c197e8932ee031b3bf459d0099a43 (patch) | |
| tree | 8ab42b77e5f471f0bb6cfd66163e00e0f583ddef /core/java | |
| parent | f5ec7a9be4d0740e27185f1a138c2e96aa0eb680 (diff) | |
| parent | 56f169cf648eb0aa6a3f240caebccd3076998330 (diff) | |
Merge "Move UriCodec to be near its one user" am: 55d8b54ac3 am: 3b3a29e316 am: a37c7c3199
am: 56f169cf64
Change-Id: Ib619a3a49f504aa7b47a123fd44eeaee939bf0b9
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/net/Uri.java | 5 | ||||
| -rw-r--r-- | core/java/android/net/UriCodec.java | 176 |
2 files changed, 178 insertions, 3 deletions
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 0fb84b723634..b7f5cdfabc46 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -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; @@ -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 */); } /** diff --git a/core/java/android/net/UriCodec.java b/core/java/android/net/UriCodec.java new file mode 100644 index 000000000000..e1470e0a958f --- /dev/null +++ b/core/java/android/net/UriCodec.java @@ -0,0 +1,176 @@ +/* + * 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()); + } + } +} |
