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}