/* * SPDX-License-Identifier: (LGPL-3.0-only WITH LGPL-3.0-linking-exception) OR MPL-2.0 */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package mozilla.components.lib.publicsuffixlist.ext import kotlin.experimental.and private const val BITMASK = 0xff.toByte() /** * Performs a binary search for the provided [labels] on the [ByteArray]'s data. * * This algorithm is based on OkHttp's PublicSuffixDatabase class: * https://github.com/square/okhttp/blob/1977136/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixDatabase.kt */ @Suppress("ComplexMethod", "NestedBlockDepth") internal fun ByteArray.binarySearch(labels: List, labelIndex: Int): String? { var low = 0 var high = size var match: String? = null while (low < high) { val mid = (low + high) / 2 val start = findStartOfLineFromIndex(mid) val end = findEndOfLineFromIndex(start) val publicSuffixLength = start + end - start var compareResult: Int var currentLabelIndex = labelIndex var currentLabelByteIndex = 0 var publicSuffixByteIndex = 0 var expectDot = false while (true) { val byte0 = if (expectDot) { expectDot = false '.'.code.toByte() } else { labels[currentLabelIndex][currentLabelByteIndex] and BITMASK } val byte1 = this[start + publicSuffixByteIndex] and BITMASK // Compare the bytes. Note that the file stores UTF-8 encoded bytes, so we must compare // the // unsigned bytes. compareResult = (byte0.toUByte() - byte1.toUByte()).toInt() if (compareResult != 0) { break } publicSuffixByteIndex++ currentLabelByteIndex++ if (publicSuffixByteIndex == publicSuffixLength) { break } if (labels[currentLabelIndex].size == currentLabelByteIndex) { // We've exhausted our current label. Either there are more labels to compare, in // which // case we expect a dot as the next character. Otherwise, we've checked all our // labels. if (currentLabelIndex == labels.size - 1) { break } else { currentLabelIndex++ currentLabelByteIndex = -1 expectDot = true } } } if (compareResult < 0) { high = start - 1 } else if (compareResult > 0) { low = start + end + 1 } else { // We found a match, but are the lengths equal? val publicSuffixBytesLeft = publicSuffixLength - publicSuffixByteIndex var labelBytesLeft = labels[currentLabelIndex].size - currentLabelByteIndex for (i in currentLabelIndex + 1 until labels.size) { labelBytesLeft += labels[i].size } if (labelBytesLeft < publicSuffixBytesLeft) { high = start - 1 } else if (labelBytesLeft > publicSuffixBytesLeft) { low = start + end + 1 } else { // Found a match. match = String(this, start, publicSuffixLength, Charsets.UTF_8) break } } } return match } /** Search for a '\n' that marks the start of a value. Don't go back past the start of the array. */ private fun ByteArray.findStartOfLineFromIndex(start: Int): Int { var index = start while (index > -1 && this[index] != '\n'.code.toByte()) { index-- } index++ return index } /** Search for a '\n' that marks the end of a value. */ private fun ByteArray.findEndOfLineFromIndex(start: Int): Int { var end = 1 while (this[start + end] != '\n'.code.toByte()) { end++ } return end }