Match any path component in StrictDomain FilterMode
This commit is contained in:
parent
b633cc1f3d
commit
d6db10e089
5 changed files with 86 additions and 16 deletions
|
@ -121,6 +121,7 @@ dependencies {
|
|||
|
||||
// Testing-only dependencies
|
||||
androidTestImplementation deps.testing.junit
|
||||
androidTestImplementation deps.testing.kotlin_test_junit
|
||||
androidTestImplementation deps.testing.androidx.runner
|
||||
androidTestImplementation deps.testing.androidx.rules
|
||||
androidTestImplementation deps.testing.androidx.junit
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore
|
||||
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
import org.junit.Test as test
|
||||
|
||||
private infix fun String.matchedForDomain(domain: String) =
|
||||
SearchableRepositoryViewModel.generateStrictDomainRegex(domain)?.containsMatchIn(this) == true
|
||||
|
||||
class StrictDomainRegexTest {
|
||||
@test fun acceptsLiteralDomain() {
|
||||
assertTrue("work/example.org/john.doe@example.org.gpg" matchedForDomain "example.org")
|
||||
assertTrue("example.org/john.doe@example.org.gpg" matchedForDomain "example.org")
|
||||
assertTrue("example.org.gpg" matchedForDomain "example.org")
|
||||
}
|
||||
|
||||
@test fun acceptsSubdomains() {
|
||||
assertTrue("work/www.example.org/john.doe@example.org.gpg" matchedForDomain "example.org")
|
||||
assertTrue("www2.example.org/john.doe@example.org.gpg" matchedForDomain "example.org")
|
||||
assertTrue("www.login.example.org.gpg" matchedForDomain "example.org")
|
||||
}
|
||||
|
||||
@test fun rejectsPhishingAttempts() {
|
||||
assertFalse("example.org.gpg" matchedForDomain "xample.org")
|
||||
assertFalse("login.example.org.gpg" matchedForDomain "xample.org")
|
||||
assertFalse("example.org/john.doe@exmple.org.gpg" matchedForDomain "xample.org")
|
||||
assertFalse("example.org.gpg" matchedForDomain "e/xample.org")
|
||||
}
|
||||
|
||||
@test fun rejectNonGpgComponentMatches() {
|
||||
assertFalse("work/example.org" matchedForDomain "example.org")
|
||||
}
|
||||
|
||||
@test fun rejectsEmailAddresses() {
|
||||
assertFalse("work/notexample.org/john.doe@example.org.gpg" matchedForDomain "example.org")
|
||||
assertFalse("work/notexample.org/john.doe@www.example.org.gpg" matchedForDomain "example.org")
|
||||
assertFalse("work/john.doe@www.example.org/foo.org" matchedForDomain "example.org")
|
||||
}
|
||||
|
||||
@test fun rejectsPathSeparators() {
|
||||
assertNull(SearchableRepositoryViewModel.generateStrictDomainRegex("ex/ample.org"))
|
||||
}
|
||||
}
|
|
@ -126,6 +126,28 @@ enum class ListMode {
|
|||
@FlowPreview
|
||||
class SearchableRepositoryViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun generateStrictDomainRegex(domain: String): Regex? {
|
||||
// Valid domains do not contain path separators.
|
||||
if (domain.contains('/'))
|
||||
return null
|
||||
// Matches the start of a path component, which is either the start of the
|
||||
// string or a path separator.
|
||||
val prefix = """(?:^|/)"""
|
||||
val escapedFilter = Regex.escape(domain.replace("/", ""))
|
||||
// Matches either the filter literally or a strict subdomain of the filter term.
|
||||
// We allow a lot of freedom in what a subdomain is, as long as it is not an
|
||||
// email address.
|
||||
val subdomain = """(?:(?:[^/@]+\.)?$escapedFilter)"""
|
||||
// Matches the end of a path component, which is either the literal ".gpg" or a
|
||||
// path separator.
|
||||
val suffix = """(?:\.gpg|/)"""
|
||||
// Match any relative path with a component that is a subdomain of the filter.
|
||||
return Regex(prefix + subdomain + suffix)
|
||||
}
|
||||
}
|
||||
|
||||
private var _updateCounter = 0
|
||||
private val updateCounter: Int
|
||||
get() = _updateCounter
|
||||
|
@ -219,22 +241,18 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel
|
|||
}
|
||||
FilterMode.StrictDomain -> {
|
||||
check(searchAction.listMode == ListMode.FilesOnly) { "Searches with StrictDomain search mode can only list files" }
|
||||
val regex = generateStrictDomainRegex(searchAction.filter)
|
||||
if (regex != null) {
|
||||
prefilteredResultFlow
|
||||
.filter { absoluteFile ->
|
||||
val file = absoluteFile.relativeTo(root)
|
||||
val toMatch =
|
||||
directoryStructure.getIdentifierFor(file) ?: return@filter false
|
||||
// In strict domain mode, we match
|
||||
// * the search term exactly,
|
||||
// * subdomains of the search term,
|
||||
// * or the search term plus an arbitrary protocol.
|
||||
toMatch == searchAction.filter ||
|
||||
toMatch.endsWith(".${searchAction.filter}") ||
|
||||
toMatch.endsWith("://${searchAction.filter}")
|
||||
regex.containsMatchIn(absoluteFile.relativeTo(root).path)
|
||||
}
|
||||
.map { it.toPasswordItem(root) }
|
||||
.toList()
|
||||
.sortedWith(itemComparator)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
FilterMode.Fuzzy -> {
|
||||
prefilteredResultFlow
|
||||
|
|
|
@ -50,6 +50,8 @@ subprojects {
|
|||
targetSdkVersion versions.targetSdk
|
||||
versionCode versions.versionCode
|
||||
versionName versions.versionName
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
|
|
|
@ -67,6 +67,7 @@ ext.deps = [
|
|||
|
||||
testing: [
|
||||
junit: 'junit:junit:4.13',
|
||||
kotlin_test_junit: 'org.jetbrains.kotlin:kotlin-test-junit:1.3.71',
|
||||
androidx: [
|
||||
runner: 'androidx.test:runner:1.3.0-alpha05',
|
||||
rules: 'androidx.test:rules:1.3.0-alpha05',
|
||||
|
|
Loading…
Reference in a new issue