From d695d21497e5f54f78316f77a1d637009b3ee633 Mon Sep 17 00:00:00 2001 From: Fabian Henneke Date: Tue, 12 May 2020 11:17:08 +0200 Subject: [PATCH] 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 * Initial workflow configuration for PSL update Signed-off-by: Harsh Shandilya * Initial check-in of PSL data Signed-off-by: Harsh Shandilya Co-authored-by: Harsh Shandilya --- .../workflows/update_publicsuffix_data.yml | 29 ++++ .idea/codeStyles/Project.xml | 5 + app/build.gradle | 3 +- app/src/main/assets/publicsuffixes | Bin 0 -> 104170 bytes .../lib/publicsuffixlist/PublicSuffixList.kt | 138 +++++++++++++++ .../publicsuffixlist/PublicSuffixListData.kt | 161 ++++++++++++++++++ .../PublicSuffixListLoader.kt | 51 ++++++ .../lib/publicsuffixlist/ext/ByteArray.kt | 125 ++++++++++++++ build.gradle | 10 +- dependencies.gradle | 8 +- gradle/wrapper/gradle-wrapper.properties | 1 + 11 files changed, 515 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/update_publicsuffix_data.yml create mode 100644 app/src/main/assets/publicsuffixes create mode 100644 app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt create mode 100644 app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt create mode 100644 app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt create mode 100644 app/src/main/java/mozilla/components/lib/publicsuffixlist/ext/ByteArray.kt diff --git a/.github/workflows/update_publicsuffix_data.yml b/.github/workflows/update_publicsuffix_data.yml new file mode 100644 index 00000000..4929b2da --- /dev/null +++ b/.github/workflows/update_publicsuffix_data.yml @@ -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 diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index d365c987..81283c30 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -5,6 +5,11 @@ diff --git a/app/build.gradle b/app/build.gradle index 2ea2a8e8..92eb301c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,7 +25,7 @@ android { viewBinding.enabled = true defaultConfig { - applicationId versions.packageName + applicationId 'dev.msfjarvis.aps' versionCode 10721 versionName '1.8.0-SNAPSHOT' } @@ -101,7 +101,6 @@ dependencies { } implementation deps.third_party.jsch implementation deps.third_party.openpgp_ktx - implementation deps.third_party.publicsuffixlist implementation deps.third_party.ssh_auth implementation deps.third_party.timber implementation deps.third_party.timberkt diff --git a/app/src/main/assets/publicsuffixes b/app/src/main/assets/publicsuffixes new file mode 100644 index 0000000000000000000000000000000000000000..c47610a89fa1a56fa67c4a96f4f447487f3a60bd GIT binary patch literal 104170 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 z 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 literal 0 HcmV?d00001 diff --git a/app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt b/app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt new file mode 100644 index 00000000..6df3caca --- /dev/null +++ b/app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt @@ -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 = 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 = 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 = 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 + } + } +} diff --git a/app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt b/app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt new file mode 100644 index 00000000..0cbf6945 --- /dev/null +++ b/app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt @@ -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, labelIndex: Int): String? { + return rules.binarySearch(labels, labelIndex) + } + + private fun binarySearchExceptions(labels: List, 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): List { + // 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): 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): 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, 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() + const val EXCEPTION_MARKER = '!' + } +} + +internal sealed class PublicSuffixOffset { + data class Offset(val value: Int) : PublicSuffixOffset() + object PublicSuffix : PublicSuffixOffset() + object PrevailingRule : PublicSuffixOffset() +} diff --git a/app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt b/app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt new file mode 100644 index 00000000..88e82523 --- /dev/null +++ b/app/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt @@ -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 +} diff --git a/app/src/main/java/mozilla/components/lib/publicsuffixlist/ext/ByteArray.kt b/app/src/main/java/mozilla/components/lib/publicsuffixlist/ext/ByteArray.kt new file mode 100644 index 00000000..5abb9154 --- /dev/null +++ b/app/src/main/java/mozilla/components/lib/publicsuffixlist/ext/ByteArray.kt @@ -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, 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 +} diff --git a/build.gradle b/build.gradle index 8de48223..fa9a99e9 100644 --- a/build.gradle +++ b/build.gradle @@ -26,9 +26,6 @@ subprojects { maven { url 'https://jitpack.io' } - maven { - url 'https://maven.mozilla.org/maven2' - } } pluginManager.withPlugin('kotlin-android') { dependencies { @@ -65,8 +62,7 @@ subprojects { } } -tasks { - wrapper { - distributionType = Wrapper.DistributionType.ALL - } +tasks.wrapper { + distributionType = Wrapper.DistributionType.ALL + distributionSha256Sum = "0f316a67b971b7b571dac7215dcf2591a30994b3450e0629925ffcfe2c68cc5c" } diff --git a/dependencies.gradle b/dependencies.gradle index 52ba9f2d..b0b4dd80 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -6,8 +6,7 @@ ext.versions = [ minSdk: 23, targetSdk: 29, compileSdk: 29, - buildTools: '29.0.3', - packageName: 'dev.msfjarvis.aps' + buildTools: '29.0.3' ] ext.deps = [ @@ -52,11 +51,6 @@ ext.deps = [ jgit: 'org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r', leakcanary: 'com.squareup.leakcanary:leakcanary-android:2.2', 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', timber: 'com.jakewharton.timber:timber:4.7.1', timberkt: 'com.github.ajalt:timberkt:1.5.1', diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6623300b..6e06308f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionSha256Sum=0f316a67b971b7b571dac7215dcf2591a30994b3450e0629925ffcfe2c68cc5c distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists