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