• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
decodeBase64ToArraynull31 internal 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 
encodeBase64null115 internal 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