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