Make APS buildable on F-Droid (#762)
* Include lib-publicsuffixlist in tree with proper license attribution * Exclude lib-publicsuffixlist from code style * Move applicationId to app/build.gradle * build: add distributionSha256Sum to Gradle Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Initial workflow configuration for PSL update Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Initial check-in of PSL data Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
b16620b55c
commit
d695d21497
11 changed files with 515 additions and 16 deletions
29
.github/workflows/update_publicsuffix_data.yml
vendored
Normal file
29
.github/workflows/update_publicsuffix_data.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-publicsuffix-data:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Download new publicsuffix data
|
||||||
|
run: curl -L https://github.com/mozilla-mobile/android-components/raw/master/components/lib/publicsuffixlist/src/main/assets/publicsuffixes -o app/src/main/assets/publicsuffixes
|
||||||
|
- name: Compare list changes
|
||||||
|
run: if [[ $(git diff --binary --stat) != '' ]]; then echo "::set-env name=UPDATED::true"; fi
|
||||||
|
- name: Create update PR
|
||||||
|
uses: peter-evans/create-pull-request@v2
|
||||||
|
if: env.UPDATED == 'true'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_PERSONAL_TOKEN }}
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
commit-message: 'Update Public Suffix List data'
|
||||||
|
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||||
|
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||||
|
title: 'Update Public Suffix List data'
|
||||||
|
body: 'Updates Public Suffix List from https://publicsuffix.org/list/'
|
||||||
|
assignees: msfjarvis
|
||||||
|
labels: PSL
|
||||||
|
branch: bot/update-psl
|
|
@ -5,6 +5,11 @@
|
||||||
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
|
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
|
||||||
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||||
<option name="SOFT_MARGINS" value="100" />
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<option name="DO_NOT_FORMAT">
|
||||||
|
<list>
|
||||||
|
<fileSet type="namedScope" name="publicsuffixlist" pattern="src[app]:mozilla.components.lib.publicsuffixlist.PublicSuffixList||src[app]:mozilla.components.lib.publicsuffixlist.PublicSuffixListData||src[app]:mozilla.components.lib.publicsuffixlist.PublicSuffixListLoader||src[app]:mozilla.components.lib.publicsuffixlist.ext.ByteArray" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
<JavaCodeStyleSettings>
|
<JavaCodeStyleSettings>
|
||||||
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
|
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
|
||||||
</JavaCodeStyleSettings>
|
</JavaCodeStyleSettings>
|
||||||
|
|
|
@ -25,7 +25,7 @@ android {
|
||||||
viewBinding.enabled = true
|
viewBinding.enabled = true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId versions.packageName
|
applicationId 'dev.msfjarvis.aps'
|
||||||
versionCode 10721
|
versionCode 10721
|
||||||
versionName '1.8.0-SNAPSHOT'
|
versionName '1.8.0-SNAPSHOT'
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,6 @@ dependencies {
|
||||||
}
|
}
|
||||||
implementation deps.third_party.jsch
|
implementation deps.third_party.jsch
|
||||||
implementation deps.third_party.openpgp_ktx
|
implementation deps.third_party.openpgp_ktx
|
||||||
implementation deps.third_party.publicsuffixlist
|
|
||||||
implementation deps.third_party.ssh_auth
|
implementation deps.third_party.ssh_auth
|
||||||
implementation deps.third_party.timber
|
implementation deps.third_party.timber
|
||||||
implementation deps.third_party.timberkt
|
implementation deps.third_party.timberkt
|
||||||
|
|
BIN
app/src/main/assets/publicsuffixes
Normal file
BIN
app/src/main/assets/publicsuffixes
Normal file
Binary file not shown.
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for reading and accessing the public suffix list.
|
||||||
|
*
|
||||||
|
* > A "public suffix" is one under which Internet users can (or historically could) directly register names. Some
|
||||||
|
* > examples of public suffixes are .com, .co.uk and pvt.k12.ma.us. The Public Suffix List is a list of all known
|
||||||
|
* > public suffixes.
|
||||||
|
*
|
||||||
|
* Note that this implementation applies the rules of the public suffix list only and does not validate domains.
|
||||||
|
*
|
||||||
|
* https://publicsuffix.org/
|
||||||
|
* https://github.com/publicsuffix/list
|
||||||
|
*/
|
||||||
|
class PublicSuffixList(
|
||||||
|
context: Context,
|
||||||
|
dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
|
private val scope: CoroutineScope = CoroutineScope(dispatcher)
|
||||||
|
) {
|
||||||
|
private val data: PublicSuffixListData by lazy { PublicSuffixListLoader.load(context) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch the public suffix list from disk so that it is available in memory.
|
||||||
|
*/
|
||||||
|
fun prefetch(): Deferred<Unit> = scope.async {
|
||||||
|
data.run { Unit }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given [domain] is a public suffix; false otherwise.
|
||||||
|
*
|
||||||
|
* E.g.:
|
||||||
|
* ```
|
||||||
|
* co.uk -> true
|
||||||
|
* com -> true
|
||||||
|
* mozilla.org -> false
|
||||||
|
* org -> true
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Note that this method ignores the default "prevailing rule" described in the formal public suffix list algorithm:
|
||||||
|
* If no rule matches then the passed [domain] is assumed to *not* be a public suffix.
|
||||||
|
*
|
||||||
|
* @param [domain] _must_ be a valid domain. [PublicSuffixList] performs no validation, and if any unexpected values
|
||||||
|
* are passed (e.g., a full URL, a domain with a trailing '/', etc) this may return an incorrect result.
|
||||||
|
*/
|
||||||
|
fun isPublicSuffix(domain: String): Deferred<Boolean> = scope.async {
|
||||||
|
when (data.getPublicSuffixOffset(domain)) {
|
||||||
|
is PublicSuffixOffset.PublicSuffix -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the public suffix and one more level; known as the registrable domain. Returns `null` if
|
||||||
|
* [domain] is a public suffix itself.
|
||||||
|
*
|
||||||
|
* E.g.:
|
||||||
|
* ```
|
||||||
|
* wwww.mozilla.org -> mozilla.org
|
||||||
|
* www.bcc.co.uk -> bbc.co.uk
|
||||||
|
* a.b.ide.kyoto.jp -> b.ide.kyoto.jp
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param [domain] _must_ be a valid domain. [PublicSuffixList] performs no validation, and if any unexpected values
|
||||||
|
* are passed (e.g., a full URL, a domain with a trailing '/', etc) this may return an incorrect result.
|
||||||
|
*/
|
||||||
|
fun getPublicSuffixPlusOne(domain: String): Deferred<String?> = scope.async {
|
||||||
|
when (val offset = data.getPublicSuffixOffset(domain)) {
|
||||||
|
is PublicSuffixOffset.Offset -> domain
|
||||||
|
.split('.')
|
||||||
|
.drop(offset.value)
|
||||||
|
.joinToString(separator = ".")
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the public suffix of the given [domain]; known as the effective top-level domain (eTLD). Returns `null`
|
||||||
|
* if the [domain] is a public suffix itself.
|
||||||
|
*
|
||||||
|
* E.g.:
|
||||||
|
* ```
|
||||||
|
* wwww.mozilla.org -> org
|
||||||
|
* www.bcc.co.uk -> co.uk
|
||||||
|
* a.b.ide.kyoto.jp -> ide.kyoto.jp
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param [domain] _must_ be a valid domain. [PublicSuffixList] performs no validation, and if any unexpected values
|
||||||
|
* are passed (e.g., a full URL, a domain with a trailing '/', etc) this may return an incorrect result.
|
||||||
|
*/
|
||||||
|
fun getPublicSuffix(domain: String) = scope.async {
|
||||||
|
when (val offset = data.getPublicSuffixOffset(domain)) {
|
||||||
|
is PublicSuffixOffset.Offset -> domain
|
||||||
|
.split('.')
|
||||||
|
.drop(offset.value + 1)
|
||||||
|
.joinToString(separator = ".")
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips the public suffix from the given [domain]. Returns the original domain if no public suffix could be
|
||||||
|
* stripped.
|
||||||
|
*
|
||||||
|
* E.g.:
|
||||||
|
* ```
|
||||||
|
* wwww.mozilla.org -> www.mozilla
|
||||||
|
* www.bcc.co.uk -> www.bbc
|
||||||
|
* a.b.ide.kyoto.jp -> a.b
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param [domain] _must_ be a valid domain. [PublicSuffixList] performs no validation, and if any unexpected values
|
||||||
|
* are passed (e.g., a full URL, a domain with a trailing '/', etc) this may return an incorrect result.
|
||||||
|
*/
|
||||||
|
fun stripPublicSuffix(domain: String) = scope.async {
|
||||||
|
when (val offset = data.getPublicSuffixOffset(domain)) {
|
||||||
|
is PublicSuffixOffset.Offset -> domain
|
||||||
|
.split('.')
|
||||||
|
.joinToString(separator = ".", limit = offset.value + 1, truncated = "")
|
||||||
|
.dropLast(1)
|
||||||
|
else -> domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only 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
|
||||||
|
|
||||||
|
import mozilla.components.lib.publicsuffixlist.ext.binarySearch
|
||||||
|
import java.net.IDN
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class wrapping the public suffix list data and offering methods for accessing rules in it.
|
||||||
|
*/
|
||||||
|
internal class PublicSuffixListData(
|
||||||
|
private val rules: ByteArray,
|
||||||
|
private val exceptions: ByteArray
|
||||||
|
) {
|
||||||
|
private fun binarySearchRules(labels: List<ByteArray>, labelIndex: Int): String? {
|
||||||
|
return rules.binarySearch(labels, labelIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun binarySearchExceptions(labels: List<ByteArray>, labelIndex: Int): String? {
|
||||||
|
return exceptions.binarySearch(labels, labelIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
fun getPublicSuffixOffset(domain: String): PublicSuffixOffset? {
|
||||||
|
if (domain.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val domainLabels = IDN.toUnicode(domain).split('.')
|
||||||
|
if (domainLabels.find { it.isEmpty() } != null) {
|
||||||
|
// At least one of the labels is empty: Bail out.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val rule = findMatchingRule(domainLabels)
|
||||||
|
|
||||||
|
if (domainLabels.size == rule.size && rule[0][0] != PublicSuffixListData.EXCEPTION_MARKER) {
|
||||||
|
// The domain is a public suffix.
|
||||||
|
return if (rule == PublicSuffixListData.PREVAILING_RULE) {
|
||||||
|
PublicSuffixOffset.PrevailingRule
|
||||||
|
} else {
|
||||||
|
PublicSuffixOffset.PublicSuffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (rule[0][0] == PublicSuffixListData.EXCEPTION_MARKER) {
|
||||||
|
// Exception rules hold the effective TLD plus one.
|
||||||
|
PublicSuffixOffset.Offset(domainLabels.size - rule.size)
|
||||||
|
} else {
|
||||||
|
// Otherwise the rule is for a public suffix, so we must take one more label.
|
||||||
|
PublicSuffixOffset.Offset(domainLabels.size - (rule.size + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a matching rule for the given domain labels.
|
||||||
|
*
|
||||||
|
* This algorithm is based on OkHttp's PublicSuffixDatabase class:
|
||||||
|
* https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java
|
||||||
|
*/
|
||||||
|
private fun findMatchingRule(domainLabels: List<String>): List<String> {
|
||||||
|
// Break apart the domain into UTF-8 labels, i.e. foo.bar.com turns into [foo, bar, com].
|
||||||
|
val domainLabelsBytes = domainLabels.map { it.toByteArray(Charsets.UTF_8) }
|
||||||
|
|
||||||
|
val exactMatch = findExactMatch(domainLabelsBytes)
|
||||||
|
val wildcardMatch = findWildcardMatch(domainLabelsBytes)
|
||||||
|
val exceptionMatch = findExceptionMatch(domainLabelsBytes, wildcardMatch)
|
||||||
|
|
||||||
|
if (exceptionMatch != null) {
|
||||||
|
return ("${PublicSuffixListData.EXCEPTION_MARKER}$exceptionMatch").split('.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exactMatch == null && wildcardMatch == null) {
|
||||||
|
return PublicSuffixListData.PREVAILING_RULE
|
||||||
|
}
|
||||||
|
|
||||||
|
val exactRuleLabels = exactMatch?.split('.') ?: PublicSuffixListData.EMPTY_RULE
|
||||||
|
val wildcardRuleLabels = wildcardMatch?.split('.') ?: PublicSuffixListData.EMPTY_RULE
|
||||||
|
|
||||||
|
return if (exactRuleLabels.size > wildcardRuleLabels.size) {
|
||||||
|
exactRuleLabels
|
||||||
|
} else {
|
||||||
|
wildcardRuleLabels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an exact match or null.
|
||||||
|
*/
|
||||||
|
private fun findExactMatch(labels: List<ByteArray>): String? {
|
||||||
|
// Start by looking for exact matches. We start at the leftmost label. For example, foo.bar.com
|
||||||
|
// will look like: [foo, bar, com], [bar, com], [com]. The longest matching rule wins.
|
||||||
|
|
||||||
|
for (i in 0 until labels.size) {
|
||||||
|
val rule = binarySearchRules(labels, i)
|
||||||
|
|
||||||
|
if (rule != null) {
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a wildcard match or null.
|
||||||
|
*/
|
||||||
|
private fun findWildcardMatch(labels: List<ByteArray>): String? {
|
||||||
|
// In theory, wildcard rules are not restricted to having the wildcard in the leftmost position.
|
||||||
|
// In practice, wildcards are always in the leftmost position. For now, this implementation
|
||||||
|
// cheats and does not attempt every possible permutation. Instead, it only considers wildcards
|
||||||
|
// in the leftmost position. We assert this fact when we generate the public suffix file. If
|
||||||
|
// this assertion ever fails we'll need to refactor this implementation.
|
||||||
|
if (labels.size > 1) {
|
||||||
|
val labelsWithWildcard = labels.toMutableList()
|
||||||
|
for (labelIndex in 0 until labelsWithWildcard.size) {
|
||||||
|
labelsWithWildcard[labelIndex] = PublicSuffixListData.WILDCARD_LABEL
|
||||||
|
val rule = binarySearchRules(labelsWithWildcard, labelIndex)
|
||||||
|
if (rule != null) {
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findExceptionMatch(labels: List<ByteArray>, wildcardMatch: String?): String? {
|
||||||
|
// Exception rules only apply to wildcard rules, so only try it if we matched a wildcard.
|
||||||
|
if (wildcardMatch == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
for (labelIndex in 0 until labels.size) {
|
||||||
|
val rule = binarySearchExceptions(labels, labelIndex)
|
||||||
|
if (rule != null) {
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val WILDCARD_LABEL = byteArrayOf('*'.toByte())
|
||||||
|
val PREVAILING_RULE = listOf("*")
|
||||||
|
val EMPTY_RULE = listOf<String>()
|
||||||
|
const val EXCEPTION_MARKER = '!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class PublicSuffixOffset {
|
||||||
|
data class Offset(val value: Int) : PublicSuffixOffset()
|
||||||
|
object PublicSuffix : PublicSuffixOffset()
|
||||||
|
object PrevailingRule : PublicSuffixOffset()
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
private const val PUBLIC_SUFFIX_LIST_FILE = "publicsuffixes"
|
||||||
|
|
||||||
|
internal object PublicSuffixListLoader {
|
||||||
|
fun load(context: Context): PublicSuffixListData = context.assets.open(
|
||||||
|
PUBLIC_SUFFIX_LIST_FILE
|
||||||
|
).buffered().use { stream ->
|
||||||
|
val publicSuffixSize = stream.readInt()
|
||||||
|
val publicSuffixBytes = stream.readFully(publicSuffixSize)
|
||||||
|
|
||||||
|
val exceptionSize = stream.readInt()
|
||||||
|
val exceptionBytes = stream.readFully(exceptionSize)
|
||||||
|
|
||||||
|
PublicSuffixListData(publicSuffixBytes, exceptionBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun BufferedInputStream.readInt(): Int {
|
||||||
|
return (read() and 0xff shl 24
|
||||||
|
or (read() and 0xff shl 16)
|
||||||
|
or (read() and 0xff shl 8)
|
||||||
|
or (read() and 0xff))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BufferedInputStream.readFully(size: Int): ByteArray {
|
||||||
|
val bytes = ByteArray(size)
|
||||||
|
|
||||||
|
var offset = 0
|
||||||
|
while (offset < size) {
|
||||||
|
val read = read(bytes, offset, size - offset)
|
||||||
|
if (read == -1) {
|
||||||
|
throw IOException("Unexpected end of stream")
|
||||||
|
}
|
||||||
|
offset += read
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only 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/master/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java
|
||||||
|
*/
|
||||||
|
@Suppress("ComplexMethod", "NestedBlockDepth")
|
||||||
|
internal fun ByteArray.binarySearch(labels: List<ByteArray>, 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
|
||||||
|
'.'.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.
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||||
|
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'.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'.toByte()) {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
return end
|
||||||
|
}
|
10
build.gradle
10
build.gradle
|
@ -26,9 +26,6 @@ subprojects {
|
||||||
maven {
|
maven {
|
||||||
url 'https://jitpack.io'
|
url 'https://jitpack.io'
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
url 'https://maven.mozilla.org/maven2'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pluginManager.withPlugin('kotlin-android') {
|
pluginManager.withPlugin('kotlin-android') {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -65,8 +62,7 @@ subprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks.wrapper {
|
||||||
wrapper {
|
distributionType = Wrapper.DistributionType.ALL
|
||||||
distributionType = Wrapper.DistributionType.ALL
|
distributionSha256Sum = "0f316a67b971b7b571dac7215dcf2591a30994b3450e0629925ffcfe2c68cc5c"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,7 @@ ext.versions = [
|
||||||
minSdk: 23,
|
minSdk: 23,
|
||||||
targetSdk: 29,
|
targetSdk: 29,
|
||||||
compileSdk: 29,
|
compileSdk: 29,
|
||||||
buildTools: '29.0.3',
|
buildTools: '29.0.3'
|
||||||
packageName: 'dev.msfjarvis.aps'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
ext.deps = [
|
ext.deps = [
|
||||||
|
@ -52,11 +51,6 @@ ext.deps = [
|
||||||
jgit: 'org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r',
|
jgit: 'org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r',
|
||||||
leakcanary: 'com.squareup.leakcanary:leakcanary-android:2.2',
|
leakcanary: 'com.squareup.leakcanary:leakcanary-android:2.2',
|
||||||
openpgp_ktx: 'com.github.android-password-store:openpgp-ktx:2.0.0',
|
openpgp_ktx: 'com.github.android-password-store:openpgp-ktx:2.0.0',
|
||||||
// The library is updated every two weeks to include the most recent version of the Public
|
|
||||||
// suffix list. Its API is expected to remain stable for the foreseeable future, and thus
|
|
||||||
// a reference to the latest version is warranted.
|
|
||||||
// See: https://github.com/mozilla-mobile/android-components/blob/master/components/lib/publicsuffixlist/README.md
|
|
||||||
publicsuffixlist: 'org.mozilla.components:lib-publicsuffixlist:+',
|
|
||||||
ssh_auth: 'org.sufficientlysecure:sshauthentication-api:1.0',
|
ssh_auth: 'org.sufficientlysecure:sshauthentication-api:1.0',
|
||||||
timber: 'com.jakewharton.timber:timber:4.7.1',
|
timber: 'com.jakewharton.timber:timber:4.7.1',
|
||||||
timberkt: 'com.github.ajalt:timberkt:1.5.1',
|
timberkt: 'com.github.ajalt:timberkt:1.5.1',
|
||||||
|
|
1
gradle/wrapper/gradle-wrapper.properties
vendored
1
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionSha256Sum=0f316a67b971b7b571dac7215dcf2591a30994b3450e0629925ffcfe2c68cc5c
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
Loading…
Reference in a new issue