From 3364022313d07ee2f58ca7d6d1a095df21eef489 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 24 May 2019 17:34:33 +0300 Subject: [PATCH] classlib: add URLEncoder and URLDecoder --- .../teavm/classlib/java/net/TURLDecoder.java | 107 ++++++++++++++++++ .../teavm/classlib/java/net/TURLEncoder.java | 102 +++++++++++++++++ .../classlib/java/net/URLDecoderTest.java | 59 ++++++++++ .../classlib/java/net/URLEncoderTest.java | 63 +++++++++++ 4 files changed, 331 insertions(+) create mode 100644 classlib/src/main/java/org/teavm/classlib/java/net/TURLDecoder.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/net/TURLEncoder.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/net/URLDecoderTest.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/net/URLEncoderTest.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/net/TURLDecoder.java b/classlib/src/main/java/org/teavm/classlib/java/net/TURLDecoder.java new file mode 100644 index 000000000..0ef12adfa --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/net/TURLDecoder.java @@ -0,0 +1,107 @@ +/* + * Copyright 2019 Alexey Andreev. + * + * 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 org.teavm.classlib.java.net; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Objects; + +public final class TURLDecoder { + private TURLDecoder() { + } + + @Deprecated + public static String decode(String s) { + return decode(s, StandardCharsets.UTF_8); + } + + public static String decode(String s, String enc) throws UnsupportedEncodingException { + Objects.requireNonNull(enc); + + if (enc.isEmpty()) { + throw new UnsupportedEncodingException("Invalid parameter: enc"); + } + + if (s.indexOf('%') == -1) { + if (s.indexOf('+') == -1) { + return s; + } + char[] str = s.toCharArray(); + for (int i = 0; i < str.length; i++) { + if (str[i] == '+') { + str[i] = ' '; + } + } + return new String(str); + } + + Charset charset = null; + try { + charset = Charset.forName(enc); + } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { + UnsupportedEncodingException toThrow = new UnsupportedEncodingException(); + toThrow.initCause(e); + throw toThrow; + } + + return decode(s, charset); + } + + private static String decode(String s, Charset charset) { + char[] strBuf = new char[s.length()]; + byte[] buf = new byte[s.length() / 3]; + int bufLen = 0; + + for (int i = 0; i < s.length();) { + char c = s.charAt(i); + if (c == '+') { + strBuf[bufLen] = ' '; + } else if (c == '%') { + int len = 0; + do { + if (i + 2 >= s.length()) { + throw new IllegalArgumentException("Incomplete % sequence at: " + i); + } + int d1 = Character.digit(s.charAt(i + 1), 16); + int d2 = Character.digit(s.charAt(i + 2), 16); + if (d1 == -1 || d2 == -1) { + throw new IllegalArgumentException("Invalid % sequence (" + s.substring(i, i + 3) + + ") at " + i); + } + buf[len++] = (byte) ((d1 << 4) + d2); + i += 3; + } while (i < s.length() && s.charAt(i) == '%'); + + CharBuffer cb = charset.decode(ByteBuffer.wrap(buf, 0, len)); + len = cb.length(); + System.arraycopy(cb.array(), 0, strBuf, bufLen, len); + bufLen += len; + continue; + } else { + strBuf[bufLen] = c; + } + i++; + bufLen++; + } + return new String(strBuf, 0, bufLen); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/net/TURLEncoder.java b/classlib/src/main/java/org/teavm/classlib/java/net/TURLEncoder.java new file mode 100644 index 000000000..a1d552398 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/net/TURLEncoder.java @@ -0,0 +1,102 @@ +/* + * Copyright 2019 Alexey Andreev. + * + * 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 org.teavm.classlib.java.net; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Objects; + +public final class TURLEncoder { + + static final String digits = "0123456789ABCDEF"; + + private TURLEncoder() { + } + + @Deprecated + public static String encode(String s) { + // Guess a bit bigger for encoded form + StringBuilder buf = new StringBuilder(s.length() + 16); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' + || ch >= '0' && ch <= '9' || ".-*_".indexOf(ch) > -1) { + buf.append(ch); + } else if (ch == ' ') { + buf.append('+'); + } else { + byte[] bytes = new String(new char[] { ch }).getBytes(StandardCharsets.UTF_8); + for (byte b : bytes) { + buf.append('%'); + buf.append(digits.charAt((b & 0xf0) >> 4)); + buf.append(digits.charAt(b & 0xf)); + } + } + } + return buf.toString(); + } + + public static String encode(String s, String enc) throws UnsupportedEncodingException { + Objects.requireNonNull(s); + Objects.requireNonNull(enc); + + // check for UnsupportedEncodingException + try { + Charset.forName(enc); + } catch (UnsupportedCharsetException e) { + throw new UnsupportedEncodingException(enc); + } + + // Guess a bit bigger for encoded form + StringBuffer buf = new StringBuffer(s.length() + 16); + int start = -1; + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' + || ch >= '0' && ch <= '9' || " .-*_".indexOf(ch) > -1) { + if (start >= 0) { + convert(s.substring(start, i), buf, enc); + start = -1; + } + if (ch != ' ') { + buf.append(ch); + } else { + buf.append('+'); + } + } else { + if (start < 0) { + start = i; + } + } + } + if (start >= 0) { + convert(s.substring(start), buf, enc); + } + return buf.toString(); + } + + private static void convert(String s, StringBuffer buf, String enc) throws UnsupportedEncodingException { + byte[] bytes = s.getBytes(enc); + for (int j = 0; j < bytes.length; j++) { + buf.append('%'); + buf.append(digits.charAt((bytes[j] & 0xf0) >> 4)); + buf.append(digits.charAt(bytes[j] & 0xf)); + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/net/URLDecoderTest.java b/tests/src/test/java/org/teavm/classlib/java/net/URLDecoderTest.java new file mode 100644 index 000000000..a5c489eeb --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/net/URLDecoderTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 Alexey Andreev. + * + * 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 org.teavm.classlib.java.net; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class URLDecoderTest { + + @Test + public void test_Constructor() { + URLDecoder ud = new URLDecoder(); + assertNotNull("Constructor failed.", ud); + } + + @Test + public void test_decodeLjava_lang_String() { + final String url = "http://localhost."; + final String url2 = "telnet://justWantToHaveFun.com:400"; + final String url3 = "file://myServer.org/a file with spaces.jpg"; + assertTrue("1. Incorrect encoding/decoding", URLDecoder.decode(URLEncoder.encode(url)).equals(url)); + assertTrue("2. Incorrect encoding/decoding", URLDecoder.decode(URLEncoder.encode(url2)).equals(url2)); + assertTrue("3. Incorrect encoding/decoding", URLDecoder.decode(URLEncoder.encode(url3)).equals(url3)); + } + + @Test + public void test_decodeLjava_lang_String_Ljava_lang_String() { + // Regression for HARMONY-467 + try { + URLDecoder.decode("", ""); + fail("UnsupportedEncodingException expected"); + } catch (UnsupportedEncodingException e) { + // Expected + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/net/URLEncoderTest.java b/tests/src/test/java/org/teavm/classlib/java/net/URLEncoderTest.java new file mode 100644 index 000000000..7bf0bd11d --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/net/URLEncoderTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Alexey Andreev. + * + * 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 org.teavm.classlib.java.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class URLEncoderTest { + + @Test + @SuppressWarnings("deprecation") + public void test_encodeLjava_lang_String() { + final String url = "http://localhost."; + final String url2 = "telnet://justWantToHaveFun.com:400"; + final String url3 = "file://myServer.org/a file with spaces.jpg"; + + assertEquals("1. Incorrect encoding/decoding", url, URLDecoder.decode(URLEncoder.encode(url))); + assertEquals("2. Incorrect encoding/decoding", url2, URLDecoder.decode(URLEncoder.encode(url2))); + assertEquals("3. Incorrect encoding/decoding", url3, URLDecoder.decode(URLEncoder.encode(url3))); + } + + @Test + public void test_encodeLjava_lang_StringLjava_lang_String() + throws Exception { + // Regression for HARMONY-24 + try { + URLEncoder.encode("str", "unknown_enc"); + fail("Assert 0: Should throw UEE for invalid encoding"); + } catch (UnsupportedEncodingException e) { + // expected + } + + // Regression for HARMONY-1233 + try { + URLEncoder.encode(null, "harmony"); + fail("NullPointerException expected"); + } catch (NullPointerException e) { + // expected + } + } +}