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 @file:JvmName("-Base64") 19 package okio 20 21 import okio.ByteString.Companion.encodeUtf8 22 import kotlin.jvm.JvmName 23 24 /** @author Alexander Y. Kleymenov */ 25 26 internal val BASE64 = 27 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeUtf8().data 28 internal val BASE64_URL_SAFE = 29 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".encodeUtf8().data 30 decodeBase64ToArraynull31internal fun String.decodeBase64ToArray(): ByteArray? { 32 // Ignore trailing '=' padding and whitespace from the input. 33 var limit = length 34 while (limit > 0) { 35 val c = this[limit - 1] 36 if (c != '=' && c != '\n' && c != '\r' && c != ' ' && c != '\t') { 37 break 38 } 39 limit-- 40 } 41 42 // If the input includes whitespace, this output array will be longer than necessary. 43 val out = ByteArray((limit * 6L / 8L).toInt()) 44 var outCount = 0 45 var inCount = 0 46 47 var word = 0 48 for (pos in 0 until limit) { 49 val c = this[pos] 50 51 val bits: Int 52 if (c in 'A'..'Z') { 53 // char ASCII value 54 // A 65 0 55 // Z 90 25 (ASCII - 65) 56 bits = c.toInt() - 65 57 } else if (c in 'a'..'z') { 58 // char ASCII value 59 // a 97 26 60 // z 122 51 (ASCII - 71) 61 bits = c.toInt() - 71 62 } else if (c in '0'..'9') { 63 // char ASCII value 64 // 0 48 52 65 // 9 57 61 (ASCII + 4) 66 bits = c.toInt() + 4 67 } else if (c == '+' || c == '-') { 68 bits = 62 69 } else if (c == '/' || c == '_') { 70 bits = 63 71 } else if (c == '\n' || c == '\r' || c == ' ' || c == '\t') { 72 continue 73 } else { 74 return null 75 } 76 77 // Append this char's 6 bits to the word. 78 word = word shl 6 or bits 79 80 // For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes. 81 inCount++ 82 if (inCount % 4 == 0) { 83 out[outCount++] = (word shr 16).toByte() 84 out[outCount++] = (word shr 8).toByte() 85 out[outCount++] = word.toByte() 86 } 87 } 88 89 val lastWordChars = inCount % 4 90 when (lastWordChars) { 91 1 -> { 92 // We read 1 char followed by "===". But 6 bits is a truncated byte! Fail. 93 return null 94 } 95 2 -> { 96 // We read 2 chars followed by "==". Emit 1 byte with 8 of those 12 bits. 97 word = word shl 12 98 out[outCount++] = (word shr 16).toByte() 99 } 100 3 -> { 101 // We read 3 chars, followed by "=". Emit 2 bytes for 16 of those 18 bits. 102 word = word shl 6 103 out[outCount++] = (word shr 16).toByte() 104 out[outCount++] = (word shr 8).toByte() 105 } 106 } 107 108 // If we sized our out array perfectly, we're done. 109 if (outCount == out.size) return out 110 111 // Copy the decoded bytes to a new, right-sized array. 112 return out.copyOf(outCount) 113 } 114 encodeBase64null115internal fun ByteArray.encodeBase64(map: ByteArray = BASE64): String { 116 val length = (size + 2) / 3 * 4 117 val out = ByteArray(length) 118 var index = 0 119 val end = size - size % 3 120 var i = 0 121 while (i < end) { 122 val b0 = this[i++].toInt() 123 val b1 = this[i++].toInt() 124 val b2 = this[i++].toInt() 125 out[index++] = map[(b0 and 0xff shr 2)] 126 out[index++] = map[(b0 and 0x03 shl 4) or (b1 and 0xff shr 4)] 127 out[index++] = map[(b1 and 0x0f shl 2) or (b2 and 0xff shr 6)] 128 out[index++] = map[(b2 and 0x3f)] 129 } 130 when (size - end) { 131 1 -> { 132 val b0 = this[i].toInt() 133 out[index++] = map[b0 and 0xff shr 2] 134 out[index++] = map[b0 and 0x03 shl 4] 135 out[index++] = '='.toByte() 136 out[index] = '='.toByte() 137 } 138 2 -> { 139 val b0 = this[i++].toInt() 140 val b1 = this[i].toInt() 141 out[index++] = map[(b0 and 0xff shr 2)] 142 out[index++] = map[(b0 and 0x03 shl 4) or (b1 and 0xff shr 4)] 143 out[index++] = map[(b1 and 0x0f shl 2)] 144 out[index] = '='.toByte() 145 } 146 } 147 return out.toUtf8String() 148 } 149