/** * * Copyright the original author or authors * * 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.jivesoftware.smack.util.stringencoder; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * Base32 string encoding is useful for when filenames case-insensitive filesystems are encoded. * Base32 representation takes roughly 20% more space then Base64. * * @author Florian Schmaus * Based on code by Brian Wellington (bwelling@xbill.org) * @see Base32 Wikipedia entry * */ public class Base32 { private static final StringEncoder base32Stringencoder = new StringEncoder() { @Override public String encode(String string) { return Base32.encode(string); } @Override public String decode(String string) { return Base32.decode(string); } }; private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; public static StringEncoder getStringEncoder() { return base32Stringencoder; } public static String decode(String str) { ByteArrayOutputStream bs = new ByteArrayOutputStream(); byte[] raw = str.getBytes(StandardCharsets.UTF_8); for (int i = 0; i < raw.length; i++) { char c = (char) raw[i]; if (!Character.isWhitespace(c)) { c = Character.toUpperCase(c); bs.write((byte) c); } } while (bs.size() % 8 != 0) bs.write('8'); byte[] in = bs.toByteArray(); bs.reset(); DataOutputStream ds = new DataOutputStream(bs); for (int i = 0; i < in.length / 8; i++) { short[] s = new short[8]; int[] t = new int[5]; int padlen = 8; for (int j = 0; j < 8; j++) { char c = (char) in[i * 8 + j]; if (c == '8') break; s[j] = (short) ALPHABET.indexOf(in[i * 8 + j]); if (s[j] < 0) return null; padlen--; } int blocklen = paddingToLen(padlen); if (blocklen < 0) return null; // all 5 bits of 1st, high 3 (of 5) of 2nd t[0] = (s[0] << 3) | s[1] >> 2; // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4); // lower 4 of 4th, high 4 of 5th t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F); // lower 1 of 5th, all 5 of 6th, high 2 of 7th t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3); // lower 3 of 7th, all of 8th t[4] = ((s[6] & 0x07) << 5) | s[7]; try { for (int j = 0; j < blocklen; j++) ds.writeByte((byte) (t[j] & 0xFF)); } catch (IOException e) { // This should not happen. throw new AssertionError(e); } } String res = new String(bs.toByteArray(), StandardCharsets.UTF_8); return res; } public static String encode(String str) { byte[] b = str.getBytes(StandardCharsets.UTF_8); ByteArrayOutputStream os = new ByteArrayOutputStream(); for (int i = 0; i < (b.length + 4) / 5; i++) { short[] s = new short[5]; int[] t = new int[8]; int blocklen = 5; for (int j = 0; j < 5; j++) { if ((i * 5 + j) < b.length) s[j] = (short) (b[i * 5 + j] & 0xFF); else { s[j] = 0; blocklen--; } } int padlen = lenToPadding(blocklen); // convert the 5 byte block into 8 characters (values 0-31). // upper 5 bits from first byte t[0] = (byte) ((s[0] >> 3) & 0x1F); // lower 3 bits from 1st byte, upper 2 bits from 2nd. t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03)); // bits 5-1 from 2nd. t[2] = (byte) ((s[1] >> 1) & 0x1F); // lower 1 bit from 2nd, upper 4 from 3rd t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F)); // lower 4 from 3rd, upper 1 from 4th. t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01)); // bits 6-2 from 4th t[5] = (byte) ((s[3] >> 2) & 0x1F); // lower 2 from 4th, upper 3 from 5th; t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07)); // lower 5 from 5th; t[7] = (byte) (s[4] & 0x1F); // write out the actual characters. for (int j = 0; j < t.length - padlen; j++) { char c = ALPHABET.charAt(t[j]); os.write(c); } } String res = new String(os.toByteArray(), StandardCharsets.UTF_8); return res; } private static int lenToPadding(int blocklen) { switch (blocklen) { case 1: return 6; case 2: return 4; case 3: return 3; case 4: return 1; case 5: return 0; default: return -1; } } private static int paddingToLen(int padlen) { switch (padlen) { case 6: return 1; case 4: return 2; case 3: return 3; case 1: return 4; case 0: return 5; default: return -1; } } }