• 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 @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 
decodeBase64ToArraynull32 internal 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 
encodeBase64null116 internal 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