001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.util.stringencoder;
018
019import java.io.ByteArrayOutputStream;
020import java.io.DataOutputStream;
021import java.io.IOException;
022import java.nio.charset.StandardCharsets;
023
024/**
025 * Base32 string encoding is useful for when filenames case-insensitive filesystems are encoded.
026 * Base32 representation takes roughly 20% more space then Base64.
027 *
028 * @author Florian Schmaus
029 * Based on code by Brian Wellington (bwelling@xbill.org)
030 * @see <a href="http://en.wikipedia.org/wiki/Base32">Base32 Wikipedia entry</a>
031 *
032 */
033public class Base32 {
034
035    private static final StringEncoder<String> base32Stringencoder = new StringEncoder<String>() {
036
037        @Override
038        public String encode(String string) {
039            return Base32.encode(string);
040        }
041
042        @Override
043        public String decode(String string) {
044            return Base32.decode(string);
045        }
046
047    };
048    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
049
050    public static StringEncoder<String> getStringEncoder() {
051        return base32Stringencoder;
052    }
053
054    public static String decode(String str) {
055        ByteArrayOutputStream bs = new ByteArrayOutputStream();
056        byte[] raw = str.getBytes(StandardCharsets.UTF_8);
057
058        for (int i = 0; i < raw.length; i++) {
059            char c = (char) raw[i];
060            if (!Character.isWhitespace(c)) {
061                c = Character.toUpperCase(c);
062                bs.write((byte) c);
063            }
064        }
065
066        while (bs.size() % 8 != 0)
067            bs.write('8');
068
069        byte[] in = bs.toByteArray();
070
071        bs.reset();
072        DataOutputStream ds = new DataOutputStream(bs);
073
074        for (int i = 0; i < in.length / 8; i++) {
075            short[] s = new short[8];
076            int[] t = new int[5];
077
078            int padlen = 8;
079            for (int j = 0; j < 8; j++) {
080                char c = (char) in[i * 8 + j];
081                if (c == '8')
082                    break;
083                s[j] = (short) ALPHABET.indexOf(in[i * 8 + j]);
084                if (s[j] < 0)
085                    return null;
086                padlen--;
087            }
088            int blocklen = paddingToLen(padlen);
089            if (blocklen < 0)
090                return null;
091
092            // all 5 bits of 1st, high 3 (of 5) of 2nd
093            t[0] = (s[0] << 3) | s[1] >> 2;
094            // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th
095            t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4);
096            // lower 4 of 4th, high 4 of 5th
097            t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F);
098            // lower 1 of 5th, all 5 of 6th, high 2 of 7th
099            t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3);
100            // lower 3 of 7th, all of 8th
101            t[4] = ((s[6] & 0x07) << 5) | s[7];
102
103            try {
104                for (int j = 0; j < blocklen; j++)
105                    ds.writeByte((byte) (t[j] & 0xFF));
106            } catch (IOException e) {
107                // This should not happen.
108                throw new AssertionError(e);
109            }
110        }
111
112        String res = new String(bs.toByteArray(), StandardCharsets.UTF_8);
113        return res;
114    }
115
116    public static String encode(String str) {
117        byte[] b = str.getBytes(StandardCharsets.UTF_8);
118        ByteArrayOutputStream os = new ByteArrayOutputStream();
119
120        for (int i = 0; i < (b.length + 4) / 5; i++) {
121            short[] s = new short[5];
122            int[] t = new int[8];
123
124            int blocklen = 5;
125            for (int j = 0; j < 5; j++) {
126                if ((i * 5 + j) < b.length)
127                    s[j] = (short) (b[i * 5 + j] & 0xFF);
128                else {
129                    s[j] = 0;
130                    blocklen--;
131                }
132            }
133            int padlen = lenToPadding(blocklen);
134
135            // convert the 5 byte block into 8 characters (values 0-31).
136
137            // upper 5 bits from first byte
138            t[0] = (byte) ((s[0] >> 3) & 0x1F);
139            // lower 3 bits from 1st byte, upper 2 bits from 2nd.
140            t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03));
141            // bits 5-1 from 2nd.
142            t[2] = (byte) ((s[1] >> 1) & 0x1F);
143            // lower 1 bit from 2nd, upper 4 from 3rd
144            t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F));
145            // lower 4 from 3rd, upper 1 from 4th.
146            t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01));
147            // bits 6-2 from 4th
148            t[5] = (byte) ((s[3] >> 2) & 0x1F);
149            // lower 2 from 4th, upper 3 from 5th;
150            t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07));
151            // lower 5 from 5th;
152            t[7] = (byte) (s[4] & 0x1F);
153
154            // write out the actual characters.
155            for (int j = 0; j < t.length - padlen; j++) {
156                char c = ALPHABET.charAt(t[j]);
157                os.write(c);
158            }
159        }
160        String res = new String(os.toByteArray(), StandardCharsets.UTF_8);
161        return res;
162    }
163
164    private static int lenToPadding(int blocklen) {
165        switch (blocklen) {
166        case 1:
167            return 6;
168        case 2:
169            return 4;
170        case 3:
171            return 3;
172        case 4:
173            return 1;
174        case 5:
175            return 0;
176        default:
177            return -1;
178        }
179    }
180
181    private static int paddingToLen(int padlen) {
182        switch (padlen) {
183        case 6:
184            return 1;
185        case 4:
186            return 2;
187        case 3:
188            return 3;
189        case 1:
190            return 4;
191        case 0:
192            return 5;
193        default:
194            return -1;
195        }
196    }
197
198}