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
019
020import java.io.ByteArrayOutputStream;
021import java.io.DataOutputStream;
022import java.io.IOException;
023import java.io.UnsupportedEncodingException;
024
025import org.jivesoftware.smack.util.StringUtils;
026
027/**
028 * Base32 string encoding is useful for when filenames case-insensitive filesystems are encoded.
029 * Base32 representation takes roughly 20% more space then Base64.
030 * 
031 * @author Florian Schmaus
032 * Based on code by Brian Wellington (bwelling@xbill.org)
033 * @see <a href="http://en.wikipedia.org/wiki/Base32">Base32 Wikipedia entry</a>
034 *
035 */
036public class Base32 {
037
038    private static final StringEncoder base32Stringencoder = new StringEncoder() {
039
040        @Override
041        public String encode(String string) {
042            return Base32.encode(string);
043        }
044
045        @Override
046        public String decode(String string) {
047            return Base32.decode(string);
048        }
049
050    };
051    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ2345678";
052
053    public static StringEncoder getStringEncoder() {
054        return base32Stringencoder;
055    }
056
057    public static String decode(String str) {
058        ByteArrayOutputStream bs = new ByteArrayOutputStream();
059        byte[] raw;
060        try {
061            raw = str.getBytes(StringUtils.UTF8);
062        }
063        catch (UnsupportedEncodingException e) {
064            throw new AssertionError(e);
065        }
066        for (int i = 0; i < raw.length; i++) {
067            char c = (char) raw[i];
068            if (!Character.isWhitespace(c)) {
069                c = Character.toUpperCase(c);
070                bs.write((byte) c);
071            }
072        }
073
074        while (bs.size() % 8 != 0)
075            bs.write('8');
076
077        byte[] in = bs.toByteArray();
078
079        bs.reset();
080        DataOutputStream ds = new DataOutputStream(bs);
081
082        for (int i = 0; i < in.length / 8; i++) {
083            short[] s = new short[8];
084            int[] t = new int[5];
085
086            int padlen = 8;
087            for (int j = 0; j < 8; j++) {
088                char c = (char) in[i * 8 + j];
089                if (c == '8')
090                    break;
091                s[j] = (short) ALPHABET.indexOf(in[i * 8 + j]);
092                if (s[j] < 0)
093                    return null;
094                padlen--;
095            }
096            int blocklen = paddingToLen(padlen);
097            if (blocklen < 0)
098                return null;
099
100            // all 5 bits of 1st, high 3 (of 5) of 2nd
101            t[0] = (s[0] << 3) | s[1] >> 2;
102            // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th
103            t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4);
104            // lower 4 of 4th, high 4 of 5th
105            t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F);
106            // lower 1 of 5th, all 5 of 6th, high 2 of 7th
107            t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3);
108            // lower 3 of 7th, all of 8th
109            t[4] = ((s[6] & 0x07) << 5) | s[7];
110
111            try {
112                for (int j = 0; j < blocklen; j++)
113                    ds.writeByte((byte) (t[j] & 0xFF));
114            } catch (IOException e) {
115            }
116        }
117
118        String res;
119        try {
120            res = new String(bs.toByteArray(), StringUtils.UTF8);
121        }
122        catch (UnsupportedEncodingException e) {
123            throw new AssertionError(e);
124        }
125        return res;
126    }
127
128    public static String encode(String str) {
129        byte[] b;
130        try {
131            b = str.getBytes(StringUtils.UTF8);
132        }
133        catch (UnsupportedEncodingException e) {
134            throw new AssertionError(e);
135        }
136        ByteArrayOutputStream os = new ByteArrayOutputStream();
137
138        for (int i = 0; i < (b.length + 4) / 5; i++) {
139            short[] s = new short[5];
140            int[] t = new int[8];
141
142            int blocklen = 5;
143            for (int j = 0; j < 5; j++) {
144                if ((i * 5 + j) < b.length)
145                    s[j] = (short) (b[i * 5 + j] & 0xFF);
146                else {
147                    s[j] = 0;
148                    blocklen--;
149                }
150            }
151            int padlen = lenToPadding(blocklen);
152
153            // convert the 5 byte block into 8 characters (values 0-31).
154
155            // upper 5 bits from first byte
156            t[0] = (byte) ((s[0] >> 3) & 0x1F);
157            // lower 3 bits from 1st byte, upper 2 bits from 2nd.
158            t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03));
159            // bits 5-1 from 2nd.
160            t[2] = (byte) ((s[1] >> 1) & 0x1F);
161            // lower 1 bit from 2nd, upper 4 from 3rd
162            t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F));
163            // lower 4 from 3rd, upper 1 from 4th.
164            t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01));
165            // bits 6-2 from 4th
166            t[5] = (byte) ((s[3] >> 2) & 0x1F);
167            // lower 2 from 4th, upper 3 from 5th;
168            t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07));
169            // lower 5 from 5th;
170            t[7] = (byte) (s[4] & 0x1F);
171
172            // write out the actual characters.
173            for (int j = 0; j < t.length - padlen; j++) {
174                char c = ALPHABET.charAt(t[j]);
175                os.write(c);
176            }
177        }
178        String res;
179        try {
180            res = new String(os.toByteArray(), StringUtils.UTF8);
181        }
182        catch (UnsupportedEncodingException e) {
183            throw new AssertionError(e);
184        }
185        return res;
186    }
187
188    private static int lenToPadding(int blocklen) {
189        switch (blocklen) {
190        case 1:
191            return 6;
192        case 2:
193            return 4;
194        case 3:
195            return 3;
196        case 4:
197            return 1;
198        case 5:
199            return 0;
200        default:
201            return -1;
202        }
203    }
204
205    private static int paddingToLen(int padlen) {
206        switch (padlen) {
207        case 6:
208            return 1;
209        case 4:
210            return 2;
211        case 3:
212            return 3;
213        case 1:
214            return 4;
215        case 0:
216            return 5;
217        default:
218            return -1;
219        }
220    }
221
222}