mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-21 19:48:29 +00:00
Unify our Base64 utilities.
This commit is contained in:
committed by
Cody Henthorne
parent
e636e38ba1
commit
4fe6d79fff
116
core-util-jvm/src/main/java/org/signal/core/util/Base64.kt
Normal file
116
core-util-jvm/src/main/java/org/signal/core/util/Base64.kt
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.util
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.UnsupportedEncodingException
|
||||
|
||||
object Base64 {
|
||||
|
||||
/**
|
||||
* Encodes the bytes as a normal Base64 string with padding. Not URL safe. For url-safe, use [encodeUrlSafe].
|
||||
*
|
||||
* Note: the [offset] and [length] are there to support a legacy usecase, which is why they're not present on
|
||||
* the other encode* methods.
|
||||
*/
|
||||
@JvmOverloads
|
||||
@JvmStatic
|
||||
fun encodeWithPadding(bytes: ByteArray, offset: Int = 0, length: Int = bytes.size): String {
|
||||
return Base64Tools.encodeBytes(bytes, offset, length)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the bytes as a normal Base64 string without padding. Not URL safe. For url-safe, use [encodeUrlSafe].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun encodeWithoutPadding(bytes: ByteArray): String {
|
||||
return Base64Tools.encodeBytes(bytes).stripPadding()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the bytes as a url-safe Base64 string with padding. It basically replaces the '+' and '/' characters in the
|
||||
* normal encoding scheme with '-' and '_'.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun encodeUrlSafeWithPadding(bytes: ByteArray): String {
|
||||
return Base64Tools.encodeBytes(bytes, Base64Tools.URL_SAFE or Base64Tools.DONT_GUNZIP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the bytes as a url-safe Base64 string without padding. It basically replaces the '+' and '/' characters in the
|
||||
* normal encoding scheme with '-' and '_'.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun encodeUrlSafeWithoutPadding(bytes: ByteArray): String {
|
||||
return Base64Tools.encodeBytes(bytes, Base64Tools.URL_SAFE or Base64Tools.DONT_GUNZIP).stripPadding()
|
||||
}
|
||||
|
||||
/**
|
||||
* A very lenient decoder. Does not care about the presence of padding or whether it's url-safe or not. It'll just decode it.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
@JvmStatic
|
||||
fun decode(value: String): ByteArray {
|
||||
return if (value.contains('-') || value.contains('_')) {
|
||||
Base64Tools.decode(value.withPaddingIfNeeded(), Base64Tools.URL_SAFE or Base64Tools.DONT_GUNZIP)
|
||||
} else {
|
||||
Base64Tools.decode(value.withPaddingIfNeeded())
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun decode(value: ByteArray): ByteArray {
|
||||
// This pattern of trying US_ASCII first mimics how Base64Tools handles strings
|
||||
return try {
|
||||
decode(String(value, Charsets.US_ASCII))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
decode(String(value, Charsets.UTF_8))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as [decode], except that instead of requiring you to handle an exception, this will just crash on invalid base64 strings.
|
||||
* Should only be used if the value is definitely a valid base64 string.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decodeOrThrow(value: String): ByteArray {
|
||||
return try {
|
||||
decode(value)
|
||||
} catch (e: IOException) {
|
||||
throw AssertionError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as [decode], except that instead of requiring you to handle an exception, this will just crash on invalid base64 strings.
|
||||
* It also allows null inputs. If the input is null, the outpul will be null.
|
||||
* Should only be used if the value is definitely a valid base64 string.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decodeNullableOrThrow(value: String?): ByteArray? {
|
||||
if (value == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
decode(value)
|
||||
} catch (e: IOException) {
|
||||
throw AssertionError(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.withPaddingIfNeeded(): String {
|
||||
return when (this.length % 4) {
|
||||
2 -> "$this=="
|
||||
3 -> "$this="
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.stripPadding(): String {
|
||||
return this.replace("=", "")
|
||||
}
|
||||
}
|
||||
2098
core-util-jvm/src/main/java/org/signal/core/util/Base64Tools.java
Normal file
2098
core-util-jvm/src/main/java/org/signal/core/util/Base64Tools.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.util
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Test
|
||||
import kotlin.random.Random
|
||||
|
||||
class Base64Test {
|
||||
|
||||
@Test
|
||||
fun `decode - correctly decode all strings regardless of url safety or padding`() {
|
||||
val stopwatch = Stopwatch("time", 2)
|
||||
|
||||
for (len in 0 until 256) {
|
||||
for (i in 0..2_000) {
|
||||
val bytes = Random.nextBytes(len)
|
||||
|
||||
val padded = Base64.encodeWithPadding(bytes)
|
||||
val unpadded = Base64.encodeWithoutPadding(bytes)
|
||||
val urlSafePadded = Base64.encodeUrlSafeWithPadding(bytes)
|
||||
val urlSafeUnpadded = Base64.encodeUrlSafeWithoutPadding(bytes)
|
||||
|
||||
assertArrayEquals(bytes, Base64.decode(padded))
|
||||
assertArrayEquals(bytes, Base64.decode(unpadded))
|
||||
assertArrayEquals(bytes, Base64.decode(urlSafePadded))
|
||||
assertArrayEquals(bytes, Base64.decode(urlSafeUnpadded))
|
||||
}
|
||||
}
|
||||
|
||||
println(stopwatch.stopAndGetLogString())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user