1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. 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 18/** 19 * @author Alexander Y. Kleymenov 20 */ 21 22package okio; 23 24import java.io.UnsupportedEncodingException; 25 26final class Base64 { 27 private Base64() { 28 } 29 30 public static byte[] decode(String in) { 31 // Ignore trailing '=' padding and whitespace from the input. 32 int limit = in.length(); 33 for (; limit > 0; limit--) { 34 char c = in.charAt(limit - 1); 35 if (c != '=' && c != '\n' && c != '\r' && c != ' ' && c != '\t') { 36 break; 37 } 38 } 39 40 // If the input includes whitespace, this output array will be longer than necessary. 41 byte[] out = new byte[(int) (limit * 6L / 8L)]; 42 int outCount = 0; 43 int inCount = 0; 44 45 int word = 0; 46 for (int pos = 0; pos < limit; pos++) { 47 char c = in.charAt(pos); 48 49 int bits; 50 if (c >= 'A' && c <= 'Z') { 51 // char ASCII value 52 // A 65 0 53 // Z 90 25 (ASCII - 65) 54 bits = c - 65; 55 } else if (c >= 'a' && c <= 'z') { 56 // char ASCII value 57 // a 97 26 58 // z 122 51 (ASCII - 71) 59 bits = c - 71; 60 } else if (c >= '0' && c <= '9') { 61 // char ASCII value 62 // 0 48 52 63 // 9 57 61 (ASCII + 4) 64 bits = c + 4; 65 } else if (c == '+') { 66 bits = 62; 67 } else if (c == '/') { 68 bits = 63; 69 } else if (c == '\n' || c == '\r' || c == ' ' || c == '\t') { 70 continue; 71 } else { 72 return null; 73 } 74 75 // Append this char's 6 bits to the word. 76 word = (word << 6) | (byte) bits; 77 78 // For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes. 79 inCount++; 80 if (inCount % 4 == 0) { 81 out[outCount++] = (byte) (word >> 16); 82 out[outCount++] = (byte) (word >> 8); 83 out[outCount++] = (byte) word; 84 } 85 } 86 87 int lastWordChars = inCount % 4; 88 if (lastWordChars == 1) { 89 // We read 1 char followed by "===". But 6 bits is a truncated byte! Fail. 90 return null; 91 } else if (lastWordChars == 2) { 92 // We read 2 chars followed by "==". Emit 1 byte with 8 of those 12 bits. 93 word = word << 12; 94 out[outCount++] = (byte) (word >> 16); 95 } else if (lastWordChars == 3) { 96 // We read 3 chars, followed by "=". Emit 2 bytes for 16 of those 18 bits. 97 word = word << 6; 98 out[outCount++] = (byte) (word >> 16); 99 out[outCount++] = (byte) (word >> 8); 100 } 101 102 // If we sized our out array perfectly, we're done. 103 if (outCount == out.length) return out; 104 105 // Copy the decoded bytes to a new, right-sized array. 106 byte[] prefix = new byte[outCount]; 107 System.arraycopy(out, 0, prefix, 0, outCount); 108 return prefix; 109 } 110 111 private static final byte[] MAP = new byte[] { 112 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 113 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 114 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', 115 '5', '6', '7', '8', '9', '+', '/' 116 }; 117 118 public static String encode(byte[] in) { 119 int length = (in.length + 2) * 4 / 3; 120 byte[] out = new byte[length]; 121 int index = 0, end = in.length - in.length % 3; 122 for (int i = 0; i < end; i += 3) { 123 out[index++] = MAP[(in[i] & 0xff) >> 2]; 124 out[index++] = MAP[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)]; 125 out[index++] = MAP[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)]; 126 out[index++] = MAP[(in[i + 2] & 0x3f)]; 127 } 128 switch (in.length % 3) { 129 case 1: 130 out[index++] = MAP[(in[end] & 0xff) >> 2]; 131 out[index++] = MAP[(in[end] & 0x03) << 4]; 132 out[index++] = '='; 133 out[index++] = '='; 134 break; 135 case 2: 136 out[index++] = MAP[(in[end] & 0xff) >> 2]; 137 out[index++] = MAP[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)]; 138 out[index++] = MAP[((in[end + 1] & 0x0f) << 2)]; 139 out[index++] = '='; 140 break; 141 } 142 try { 143 return new String(out, 0, index, "US-ASCII"); 144 } catch (UnsupportedEncodingException e) { 145 throw new AssertionError(e); 146 } 147 } 148} 149