Base32.java

  1. /**
  2.  *
  3.  * Copyright the original author or authors
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.util.stringencoder;

  18. import java.io.ByteArrayOutputStream;
  19. import java.io.DataOutputStream;
  20. import java.io.IOException;
  21. import java.nio.charset.StandardCharsets;

  22. /**
  23.  * Base32 string encoding is useful for when filenames case-insensitive filesystems are encoded.
  24.  * Base32 representation takes roughly 20% more space then Base64.
  25.  *
  26.  * @author Florian Schmaus
  27.  * Based on code by Brian Wellington (bwelling@xbill.org)
  28.  * @see <a href="http://en.wikipedia.org/wiki/Base32">Base32 Wikipedia entry</a>
  29.  *
  30.  */
  31. public class Base32 {

  32.     private static final StringEncoder<String> base32Stringencoder = new StringEncoder<String>() {

  33.         @Override
  34.         public String encode(String string) {
  35.             return Base32.encode(string);
  36.         }

  37.         @Override
  38.         public String decode(String string) {
  39.             return Base32.decode(string);
  40.         }

  41.     };
  42.     private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

  43.     public static StringEncoder<String> getStringEncoder() {
  44.         return base32Stringencoder;
  45.     }

  46.     public static String decode(String str) {
  47.         ByteArrayOutputStream bs = new ByteArrayOutputStream();
  48.         byte[] raw = str.getBytes(StandardCharsets.UTF_8);

  49.         for (int i = 0; i < raw.length; i++) {
  50.             char c = (char) raw[i];
  51.             if (!Character.isWhitespace(c)) {
  52.                 c = Character.toUpperCase(c);
  53.                 bs.write((byte) c);
  54.             }
  55.         }

  56.         while (bs.size() % 8 != 0)
  57.             bs.write('8');

  58.         byte[] in = bs.toByteArray();

  59.         bs.reset();
  60.         DataOutputStream ds = new DataOutputStream(bs);

  61.         for (int i = 0; i < in.length / 8; i++) {
  62.             short[] s = new short[8];
  63.             int[] t = new int[5];

  64.             int padlen = 8;
  65.             for (int j = 0; j < 8; j++) {
  66.                 char c = (char) in[i * 8 + j];
  67.                 if (c == '8')
  68.                     break;
  69.                 s[j] = (short) ALPHABET.indexOf(in[i * 8 + j]);
  70.                 if (s[j] < 0)
  71.                     return null;
  72.                 padlen--;
  73.             }
  74.             int blocklen = paddingToLen(padlen);
  75.             if (blocklen < 0)
  76.                 return null;

  77.             // all 5 bits of 1st, high 3 (of 5) of 2nd
  78.             t[0] = (s[0] << 3) | s[1] >> 2;
  79.             // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th
  80.             t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4);
  81.             // lower 4 of 4th, high 4 of 5th
  82.             t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F);
  83.             // lower 1 of 5th, all 5 of 6th, high 2 of 7th
  84.             t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3);
  85.             // lower 3 of 7th, all of 8th
  86.             t[4] = ((s[6] & 0x07) << 5) | s[7];

  87.             try {
  88.                 for (int j = 0; j < blocklen; j++)
  89.                     ds.writeByte((byte) (t[j] & 0xFF));
  90.             } catch (IOException e) {
  91.                 // This should not happen.
  92.                 throw new AssertionError(e);
  93.             }
  94.         }

  95.         String res = new String(bs.toByteArray(), StandardCharsets.UTF_8);
  96.         return res;
  97.     }

  98.     public static String encode(String str) {
  99.         byte[] b = str.getBytes(StandardCharsets.UTF_8);
  100.         ByteArrayOutputStream os = new ByteArrayOutputStream();

  101.         for (int i = 0; i < (b.length + 4) / 5; i++) {
  102.             short[] s = new short[5];
  103.             int[] t = new int[8];

  104.             int blocklen = 5;
  105.             for (int j = 0; j < 5; j++) {
  106.                 if ((i * 5 + j) < b.length)
  107.                     s[j] = (short) (b[i * 5 + j] & 0xFF);
  108.                 else {
  109.                     s[j] = 0;
  110.                     blocklen--;
  111.                 }
  112.             }
  113.             int padlen = lenToPadding(blocklen);

  114.             // convert the 5 byte block into 8 characters (values 0-31).

  115.             // upper 5 bits from first byte
  116.             t[0] = (byte) ((s[0] >> 3) & 0x1F);
  117.             // lower 3 bits from 1st byte, upper 2 bits from 2nd.
  118.             t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03));
  119.             // bits 5-1 from 2nd.
  120.             t[2] = (byte) ((s[1] >> 1) & 0x1F);
  121.             // lower 1 bit from 2nd, upper 4 from 3rd
  122.             t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F));
  123.             // lower 4 from 3rd, upper 1 from 4th.
  124.             t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01));
  125.             // bits 6-2 from 4th
  126.             t[5] = (byte) ((s[3] >> 2) & 0x1F);
  127.             // lower 2 from 4th, upper 3 from 5th;
  128.             t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07));
  129.             // lower 5 from 5th;
  130.             t[7] = (byte) (s[4] & 0x1F);

  131.             // write out the actual characters.
  132.             for (int j = 0; j < t.length - padlen; j++) {
  133.                 char c = ALPHABET.charAt(t[j]);
  134.                 os.write(c);
  135.             }
  136.         }
  137.         String res = new String(os.toByteArray(), StandardCharsets.UTF_8);
  138.         return res;
  139.     }

  140.     private static int lenToPadding(int blocklen) {
  141.         switch (blocklen) {
  142.         case 1:
  143.             return 6;
  144.         case 2:
  145.             return 4;
  146.         case 3:
  147.             return 3;
  148.         case 4:
  149.             return 1;
  150.         case 5:
  151.             return 0;
  152.         default:
  153.             return -1;
  154.         }
  155.     }

  156.     private static int paddingToLen(int padlen) {
  157.         switch (padlen) {
  158.         case 6:
  159.             return 1;
  160.         case 4:
  161.             return 2;
  162.         case 3:
  163.             return 3;
  164.         case 1:
  165.             return 4;
  166.         case 0:
  167.             return 5;
  168.         default:
  169.             return -1;
  170.         }
  171.     }

  172. }