Base64 implementation (#650)

classlib: add Base64 support
This commit is contained in:
Ivan Hetman 2023-01-26 09:40:03 +02:00 committed by GitHub
parent dd0b9f70df
commit ada086a864
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 220 additions and 35 deletions

View File

@ -18,33 +18,47 @@ package org.teavm.classlib.impl;
import java.util.Arrays; import java.util.Arrays;
public final class Base64Impl { public final class Base64Impl {
private static byte[] alphabet = new byte[64]; public static byte[] alphabet = new byte[64];
private static int[] reverse = new int[256]; public static byte[] urlAlphabet = new byte[64];
public static int[] reverse = new int[256];
public static int[] urlReverse = new int[256];
static { static {
int i = 0; int i = 0;
for (char c = 'A'; c <= 'Z'; ++c) { for (char c = 'A'; c <= 'Z'; ++c) {
alphabet[i++] = (byte) c; alphabet[i] = (byte) c;
urlAlphabet[i] = (byte) c;
i++;
} }
for (char c = 'a'; c <= 'z'; ++c) { for (char c = 'a'; c <= 'z'; ++c) {
alphabet[i++] = (byte) c; alphabet[i] = (byte) c;
urlAlphabet[i] = (byte) c;
i++;
} }
for (char c = '0'; c <= '9'; ++c) { for (char c = '0'; c <= '9'; ++c) {
alphabet[i++] = (byte) c; alphabet[i] = (byte) c;
urlAlphabet[i] = (byte) c;
i++;
} }
alphabet[i++] = '+'; alphabet[i] = '+';
alphabet[i++] = '/'; urlAlphabet[i] = '-';
i++;
alphabet[i] = '/';
urlAlphabet[i] = '_';
i++;
Arrays.fill(reverse, -1); Arrays.fill(reverse, -1);
Arrays.fill(urlReverse, -1);
for (i = 0; i < alphabet.length; ++i) { for (i = 0; i < alphabet.length; ++i) {
reverse[alphabet[i]] = i; reverse[alphabet[i]] = i;
urlReverse[urlAlphabet[i]] = i;
} }
} }
private Base64Impl() { private Base64Impl() {
} }
public static byte[] decode(byte[] text) { public static byte[] decode(byte[] text, int[] mapping) {
int outputSize = (text.length / 4) * 3; int outputSize = (text.length / 4) * 3;
int rem = text.length % 4; int rem = text.length % 4;
if (rem == 2 || rem == 3) { if (rem == 2 || rem == 3) {
@ -55,11 +69,15 @@ public final class Base64Impl {
--outputSize; --outputSize;
} }
byte[] output = new byte[outputSize]; byte[] output = new byte[outputSize];
decode(text, output); decode(text, output, mapping);
return output; return output;
} }
public static void decode(byte[] text, byte[] output) { public static byte[] decode(byte[] text) {
return decode(text, reverse);
}
public static void decode(byte[] text, byte[] output, int[] mapping) {
int inputSize = text.length; int inputSize = text.length;
int i; int i;
for (i = text.length - 1; i >= 0 && text[i] == '='; --i) { for (i = text.length - 1; i >= 0 && text[i] == '='; --i) {
@ -70,10 +88,10 @@ public final class Base64Impl {
i = 0; i = 0;
int j = 0; int j = 0;
while (i < triples) { while (i < triples) {
int a = decode(text[i++]); int a = decode(mapping, text[i++]);
int b = decode(text[i++]); int b = decode(mapping, text[i++]);
int c = decode(text[i++]); int c = decode(mapping, text[i++]);
int d = decode(text[i++]); int d = decode(mapping, text[i++]);
int out = (a << 18) | (b << 12) | (c << 6) | d; int out = (a << 18) | (b << 12) | (c << 6) | d;
output[j++] = (byte) (out >>> 16); output[j++] = (byte) (out >>> 16);
output[j++] = (byte) (out >>> 8); output[j++] = (byte) (out >>> 8);
@ -82,23 +100,23 @@ public final class Base64Impl {
int rem = inputSize - i; int rem = inputSize - i;
if (rem == 2) { if (rem == 2) {
int a = decode(text[i]); int a = decode(mapping, text[i]);
int b = decode(text[i + 1]); int b = decode(mapping, text[i + 1]);
output[j] = (byte) ((a << 2) | (b >>> 4)); output[j] = (byte) ((a << 2) | (b >>> 4));
} else if (rem == 3) { } else if (rem == 3) {
int a = decode(text[i]); int a = decode(mapping, text[i]);
int b = decode(text[i + 1]); int b = decode(mapping, text[i + 1]);
int c = decode(text[i + 2]); int c = decode(mapping, text[i + 2]);
output[j] = (byte) ((a << 2) | (b >>> 4)); output[j] = (byte) ((a << 2) | (b >>> 4));
output[j + 1] = (byte) ((b << 4) | (c >>> 2)); output[j + 1] = (byte) ((b << 4) | (c >>> 2));
} }
} }
private static int decode(byte c) { private static int decode(int[] mapping, byte c) {
return reverse[c]; return mapping[c];
} }
public static byte[] encode(byte[] data, boolean pad) { public static byte[] encode(byte[] data, byte[] mapping, boolean pad) {
int outputSize = ((data.length + 2) / 3) * 4; int outputSize = ((data.length + 2) / 3) * 4;
if (!pad) { if (!pad) {
int rem = data.length % 3; int rem = data.length % 3;
@ -107,38 +125,42 @@ public final class Base64Impl {
} }
} }
byte[] output = new byte[outputSize]; byte[] output = new byte[outputSize];
encode(data, output, pad); encode(data, output, mapping, pad);
return output; return output;
} }
public static int encode(byte[] data, byte[] output, boolean pad) { public static byte[] encode(byte[] data, boolean pad) {
return encode(data, alphabet, pad);
}
public static int encode(byte[] data, byte[] output, byte[] mapping, boolean pad) {
int triples = (data.length / 3) * 3; int triples = (data.length / 3) * 3;
int i = 0; int i = 0;
int j; int j;
for (j = 0; j < triples;) { for (j = 0; j < triples;) {
output[i++] = encode((byte) (data[j] >>> 2)); output[i++] = encode(mapping, (byte) (data[j] >>> 2));
output[i++] = encode((byte) ((data[j] << 4) | ((data[j + 1] & 0xFF) >>> 4))); output[i++] = encode(mapping, (byte) ((data[j] << 4) | ((data[j + 1] & 0xFF) >>> 4)));
++j; ++j;
output[i++] = encode((byte) ((data[j] << 2) | ((data[j + 1] & 0xFF) >>> 6))); output[i++] = encode(mapping, (byte) ((data[j] << 2) | ((data[j + 1] & 0xFF) >>> 6)));
++j; ++j;
output[i++] = encode(data[j]); output[i++] = encode(mapping, data[j]);
++j; ++j;
} }
int rem = data.length - j; int rem = data.length - j;
if (rem == 1) { if (rem == 1) {
output[i++] = encode((byte) (data[j] >>> 2)); output[i++] = encode(mapping, (byte) (data[j] >>> 2));
output[i++] = encode((byte) (data[j] << 4)); output[i++] = encode(mapping, (byte) (data[j] << 4));
if (pad) { if (pad) {
output[i++] = '='; output[i++] = '=';
output[i++] = '='; output[i++] = '=';
} }
} else if (rem == 2) { } else if (rem == 2) {
output[i++] = encode((byte) (data[j] >>> 2)); output[i++] = encode(mapping, (byte) (data[j] >>> 2));
output[i++] = encode((byte) ((data[j] << 4) | (data[j + 1] >>> 4))); output[i++] = encode(mapping, (byte) ((data[j] << 4) | ((data[j + 1] & 0xFF) >>> 4)));
++j; ++j;
output[i++] = encode((byte) (data[j] << 2)); output[i++] = encode(mapping, (byte) (data[j] << 2));
if (pad) { if (pad) {
output[i++] = '='; output[i++] = '=';
} }
@ -147,7 +169,7 @@ public final class Base64Impl {
return i; return i;
} }
private static byte encode(byte b) { private static byte encode(byte[] mapping, byte b) {
return alphabet[b & 63]; return mapping[b & 63];
} }
} }

View File

@ -0,0 +1,62 @@
/*
* Copyright 2023 ihromant.
*
* 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.util;
import org.teavm.classlib.impl.Base64Impl;
public class TBase64 {
public static Encoder getEncoder() {
return new Encoder(Base64Impl.alphabet, true);
}
public static Encoder getUrlEncoder() {
return new Encoder(Base64Impl.urlAlphabet, false);
}
public static class Encoder {
private final byte[] mapping;
private final boolean padding;
private Encoder(byte[] mapping, boolean padding) {
this.mapping = mapping;
this.padding = padding;
}
public byte[] encode(byte[] src) {
return Base64Impl.encode(src, mapping, padding);
}
}
public Decoder getDecoder() {
return new Decoder(Base64Impl.reverse);
}
public Decoder getUrlDecoder() {
return new Decoder(Base64Impl.urlReverse);
}
public static class Decoder {
private final int[] mapping;
private Decoder(int[] mapping) {
this.mapping = mapping;
}
public byte[] decode(byte[] src) {
return Base64Impl.decode(src, mapping);
}
}
}

View File

@ -33,6 +33,7 @@ public class Base64Test {
assertEquals("qwerty", decode("cXdlcnR5")); assertEquals("qwerty", decode("cXdlcnR5"));
assertEquals("qwertyu", decode("cXdlcnR5dQ==")); assertEquals("qwertyu", decode("cXdlcnR5dQ=="));
assertEquals("qwertyu", decode("cXdlcnR5dQ")); assertEquals("qwertyu", decode("cXdlcnR5dQ"));
assertEquals("юзернейм:пароль", decode("0Y7Qt9C10YDQvdC10LnQvDrQv9Cw0YDQvtC70Yw="));
} }
@Test @Test
@ -44,6 +45,7 @@ public class Base64Test {
assertEquals("cXdlcnQ=", encode("qwert")); assertEquals("cXdlcnQ=", encode("qwert"));
assertEquals("cXdlcnR5", encode("qwerty")); assertEquals("cXdlcnR5", encode("qwerty"));
assertEquals("cXdlcnR5dQ==", encode("qwertyu")); assertEquals("cXdlcnR5dQ==", encode("qwertyu"));
assertEquals("0Y7Qt9C10YDQvdC10LnQvDrQv9Cw0YDQvtC70Yw=", encode("юзернейм:пароль"));
} }
@Test @Test

View File

@ -0,0 +1,99 @@
/*
* Copyright 2023 ihromant.
*
* 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.impl;
import static org.junit.Assert.*;
import java.io.UnsupportedEncodingException;
import org.junit.Test;
@RunWith(TeaVMTestRunner.class)
@WholeClassCompilation
public class Base64Test {
@Test
public void decoderWorks() {
Decoder decoder = Base64.getDecoder();
assertEquals("q", decoder.decode("cQ=="));
assertEquals("qw", decoder.decode("cXc="));
assertEquals("qwe", decoder.decode("cXdl"));
assertEquals("qwer", decoder.decode("cXdlcg=="));
assertEquals("qwert", decoder.decode("cXdlcnQ="));
assertEquals("qwerty", decoder.decode("cXdlcnR5"));
assertEquals("qwertyu", decoder.decode("cXdlcnR5dQ=="));
assertEquals("юзернейм:пароль", decoder.decode("0Y7Qt9C10YDQvdC10LnQvDrQv9Cw0YDQvtC70Yw="));
}
@Test
public void encoderWorks() {
Encoder encoder = Base64.getEncoder();
assertEquals("cQ==", encoder.encode("q"));
assertEquals("cXc=", encoder.encode("qw"));
assertEquals("cXdl", encoder.encode("qwe"));
assertEquals("cXdlcg==", encoder.encode("qwer"));
assertEquals("cXdlcnQ=", encoder.encode("qwert"));
assertEquals("cXdlcnR5", encoder.encode("qwerty"));
assertEquals("cXdlcnR5dQ==", encoder.encode("qwertyu"));
assertEquals("0Y7Qt9C10YDQvdC10LnQvDrQv9Cw0YDQvtC70Yw=", encoder.encode("юзернейм:пароль"));
}
@Test
public void urlDecoderWorks() {
Decoder decoder = Base64.getUrlDecoder();
assertEquals("q", decoder.decode("cQ"));
assertEquals("qw", decoder.decode("cXc"));
assertEquals("qwe", decoder.decode("cXdl"));
assertEquals("qwer", decoder.decode("cXdlcg"));
assertEquals("qwerty", decoder.decode("cXdlcnR5"));
assertEquals("qwertyu", decoder.decode("cXdlcnR5dQ"));
assertEquals("юзернейм:пароль", decoder.decode("0Y7Qt9C10YDQvdC10LnQvDrQv9Cw0YDQvtC70Yw"));
}
@Test
public void urlEncoderWorks() {
Encoder encoder = Base64.getUrlEncoder();
assertEquals("cQ", encoder.encodeNoPad("q"));
assertEquals("cXc", encoder.encodeNoPad("qw"));
assertEquals("cXdl", encoder.encodeNoPad("qwe"));
assertEquals("cXdlcg", encoder.encodeNoPad("qwer"));
assertEquals("cXdlcnQ", encoder.encodeNoPad("qwert"));
assertEquals("cXdlcnR5", encoder.encodeNoPad("qwerty"));
assertEquals("cXdlcnR5dQ", encoder.encodeNoPad("qwertyu"));
assertEquals("0Y7Qt9C10YDQvdC10LnQvDrQv9Cw0YDQvtC70Yw", encoder.encode("юзернейм:пароль"));
}
private String decode(String text) {
try {
return new String(Base64Impl.decode(text.getBytes("UTF-8")), "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}
private String encode(String text) {
try {
return new String(Base64Impl.encode(text.getBytes("UTF-8"), true), "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}
private String encodeNoPad(String text) {
try {
return new String(Base64Impl.encode(text.getBytes("UTF-8"), false), "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}
}