Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a37c7c31 authored by Neil Fuller's avatar Neil Fuller Committed by android-build-merger
Browse files

Merge "Move UriCodec to be near its one user" am: 55d8b54a

am: 3b3a29e3

Change-Id: Icd6edcce578401741ccfad364d0eec0bcc0eff36
parents 0aacb806 3b3a29e3
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -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 */);
    }

    /**
+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());
        }
    }
}
+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 */));
    }
}