blob: 7a1a488b8c9abfd683501d8dcb8dc965b42b383b [file] [log] [blame]
/*
* Copyright (C) 2018 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio.internal
import okio.BASE64_URL_SAFE
import okio.Buffer
import okio.ByteString
import okio.REPLACEMENT_CODE_POINT
import okio.and
import okio.arrayRangeEquals
import okio.asUtf8ToByteArray
import okio.checkOffsetAndCount
import okio.decodeBase64ToArray
import okio.encodeBase64
import okio.isIsoControl
import okio.processUtf8CodePoints
import okio.shr
import okio.toUtf8String
// TODO Kotlin's expect classes can't have default implementations, so platform implementations
// have to call these functions. Remove all this nonsense when expect class allow actual code.
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonUtf8(): String {
var result = utf8
if (result == null) {
// We don't care if we double-allocate in racy code.
result = internalArray().toUtf8String()
utf8 = result
}
return result
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonBase64(): String = data.encodeBase64()
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonBase64Url() = data.encodeBase64(map = BASE64_URL_SAFE)
internal val HEX_DIGIT_CHARS =
charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonHex(): String {
val result = CharArray(data.size * 2)
var c = 0
for (b in data) {
result[c++] = HEX_DIGIT_CHARS[b shr 4 and 0xf]
result[c++] = HEX_DIGIT_CHARS[b and 0xf] // ktlint-disable no-multi-spaces
}
return String(result)
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonToAsciiLowercase(): ByteString {
// Search for an uppercase character. If we don't find one, return this.
var i = 0
while (i < data.size) {
var c = data[i]
if (c < 'A'.toByte() || c > 'Z'.toByte()) {
i++
continue
}
// This string is needs to be lowercased. Create and return a new byte string.
val lowercase = data.copyOf()
lowercase[i++] = (c - ('A' - 'a')).toByte()
while (i < lowercase.size) {
c = lowercase[i]
if (c < 'A'.toByte() || c > 'Z'.toByte()) {
i++
continue
}
lowercase[i] = (c - ('A' - 'a')).toByte()
i++
}
return ByteString(lowercase)
}
return this
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonToAsciiUppercase(): ByteString {
// Search for an lowercase character. If we don't find one, return this.
var i = 0
while (i < data.size) {
var c = data[i]
if (c < 'a'.toByte() || c > 'z'.toByte()) {
i++
continue
}
// This string is needs to be uppercased. Create and return a new byte string.
val lowercase = data.copyOf()
lowercase[i++] = (c - ('a' - 'A')).toByte()
while (i < lowercase.size) {
c = lowercase[i]
if (c < 'a'.toByte() || c > 'z'.toByte()) {
i++
continue
}
lowercase[i] = (c - ('a' - 'A')).toByte()
i++
}
return ByteString(lowercase)
}
return this
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonSubstring(beginIndex: Int, endIndex: Int): ByteString {
require(beginIndex >= 0) { "beginIndex < 0" }
require(endIndex <= data.size) { "endIndex > length(${data.size})" }
val subLen = endIndex - beginIndex
require(subLen >= 0) { "endIndex < beginIndex" }
if (beginIndex == 0 && endIndex == data.size) {
return this
}
return ByteString(data.copyOfRange(beginIndex, endIndex))
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonGetByte(pos: Int) = data[pos]
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonGetSize() = data.size
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonToByteArray() = data.copyOf()
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonInternalArray() = data
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonRangeEquals(
offset: Int,
other: ByteString,
otherOffset: Int,
byteCount: Int
): Boolean = other.rangeEquals(otherOffset, this.data, offset, byteCount)
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonRangeEquals(
offset: Int,
other: ByteArray,
otherOffset: Int,
byteCount: Int
): Boolean {
return (
offset >= 0 && offset <= data.size - byteCount &&
otherOffset >= 0 && otherOffset <= other.size - byteCount &&
arrayRangeEquals(data, offset, other, otherOffset, byteCount)
)
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonStartsWith(prefix: ByteString) =
rangeEquals(0, prefix, 0, prefix.size)
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonStartsWith(prefix: ByteArray) =
rangeEquals(0, prefix, 0, prefix.size)
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonEndsWith(suffix: ByteString) =
rangeEquals(size - suffix.size, suffix, 0, suffix.size)
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonEndsWith(suffix: ByteArray) =
rangeEquals(size - suffix.size, suffix, 0, suffix.size)
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonIndexOf(other: ByteArray, fromIndex: Int): Int {
val limit = data.size - other.size
for (i in maxOf(fromIndex, 0)..limit) {
if (arrayRangeEquals(data, i, other, 0, other.size)) {
return i
}
}
return -1
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonLastIndexOf(
other: ByteString,
fromIndex: Int
) = lastIndexOf(other.internalArray(), fromIndex)
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonLastIndexOf(other: ByteArray, fromIndex: Int): Int {
val limit = data.size - other.size
for (i in minOf(fromIndex, limit) downTo 0) {
if (arrayRangeEquals(data, i, other, 0, other.size)) {
return i
}
}
return -1
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonEquals(other: Any?): Boolean {
return when {
other === this -> true
other is ByteString -> other.size == data.size && other.rangeEquals(0, data, 0, data.size)
else -> false
}
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonHashCode(): Int {
val result = hashCode
if (result != 0) return result
return data.contentHashCode().also {
hashCode = it
}
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonCompareTo(other: ByteString): Int {
val sizeA = size
val sizeB = other.size
var i = 0
val size = minOf(sizeA, sizeB)
while (i < size) {
val byteA = this[i] and 0xff
val byteB = other[i] and 0xff
if (byteA == byteB) {
i++
continue
}
return if (byteA < byteB) -1 else 1
}
if (sizeA == sizeB) return 0
return if (sizeA < sizeB) -1 else 1
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun commonOf(data: ByteArray) = ByteString(data.copyOf())
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteArray.commonToByteString(offset: Int, byteCount: Int): ByteString {
checkOffsetAndCount(size.toLong(), offset.toLong(), byteCount.toLong())
return ByteString(copyOfRange(offset, offset + byteCount))
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun String.commonEncodeUtf8(): ByteString {
val byteString = ByteString(asUtf8ToByteArray())
byteString.utf8 = this
return byteString
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun String.commonDecodeBase64(): ByteString? {
val decoded = decodeBase64ToArray()
return if (decoded != null) ByteString(decoded) else null
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun String.commonDecodeHex(): ByteString {
require(length % 2 == 0) { "Unexpected hex string: $this" }
val result = ByteArray(length / 2)
for (i in result.indices) {
val d1 = decodeHexDigit(this[i * 2]) shl 4
val d2 = decodeHexDigit(this[i * 2 + 1])
result[i] = (d1 + d2).toByte()
}
return ByteString(result)
}
/** Writes the contents of this byte string to `buffer`. */
internal fun ByteString.commonWrite(buffer: Buffer, offset: Int, byteCount: Int) {
buffer.write(data, offset, byteCount)
}
private fun decodeHexDigit(c: Char): Int {
return when (c) {
in '0'..'9' -> c - '0'
in 'a'..'f' -> c - 'a' + 10
in 'A'..'F' -> c - 'A' + 10
else -> throw IllegalArgumentException("Unexpected hex digit: $c")
}
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonToString(): String {
if (data.isEmpty()) return "[size=0]"
val i = codePointIndexToCharIndex(data, 64)
if (i == -1) {
return if (data.size <= 64) {
"[hex=${hex()}]"
} else {
"[size=${data.size} hex=${commonSubstring(0, 64).hex()}…]"
}
}
val text = utf8()
val safeText = text.substring(0, i)
.replace("\\", "\\\\")
.replace("\n", "\\n")
.replace("\r", "\\r")
return if (i < text.length) {
"[size=${data.size} text=$safeText…]"
} else {
"[text=$safeText]"
}
}
private fun codePointIndexToCharIndex(s: ByteArray, codePointCount: Int): Int {
var charCount = 0
var j = 0
s.processUtf8CodePoints(0, s.size) { c ->
if (j++ == codePointCount) {
return charCount
}
if ((c != '\n'.toInt() && c != '\r'.toInt() && isIsoControl(c)) ||
c == REPLACEMENT_CODE_POINT
) {
return -1
}
charCount += if (c < 0x10000) 1 else 2
}
return charCount
}