From 8488d511a5a4578ee4c4ebd1ca0cc147fecac5fc Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 7 Mar 2023 13:36:07 -0300 Subject: [PATCH] [db] escape search query to prevent sqlite FTS search features from messing up query Fixes acra-crash-reports#217 --- .../org/fdroid/database/AppListItemsTest.kt | 27 +++++++++++++++++++ .../main/java/org/fdroid/database/AppDao.kt | 19 ++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/libs/database/src/dbTest/java/org/fdroid/database/AppListItemsTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/AppListItemsTest.kt index aa5d9c329..3f8a29bcc 100644 --- a/libs/database/src/dbTest/java/org/fdroid/database/AppListItemsTest.kt +++ b/libs/database/src/dbTest/java/org/fdroid/database/AppListItemsTest.kt @@ -62,6 +62,12 @@ internal class AppListItemsTest : AppTest() { assertEquals(app1, apps[0]) } + // get first app by partial search, sort by name + appDao.getAppListItems(pm, "On", NAME).getOrFail().let { apps -> + assertEquals(1, apps.size) + assertEquals(app1, apps[0]) + } + // get second app by search, sort order doesn't matter appDao.getAppListItems(pm, "Two", NAME).getOrFail().let { apps -> assertEquals(1, apps.size) @@ -155,6 +161,27 @@ internal class AppListItemsTest : AppTest() { } } + @Test + fun testMalformedSearchQuery() { + every { pm.getInstalledPackages(0) } returns emptyList() + + // without category + appDao.getAppListItems(pm, "\"", LAST_UPDATED).getOrFail().let { apps -> + assertTrue(apps.isEmpty()) + } + appDao.getAppListItems(pm, "*simple\"*", NAME).getOrFail().let { apps -> + assertTrue(apps.isEmpty()) + } + + // with category + appDao.getAppListItems(pm, "Category", "\"", LAST_UPDATED).getOrFail().let { apps -> + assertTrue(apps.isEmpty()) + } + appDao.getAppListItems(pm, "Category", "*simple\"*", NAME).getOrFail().let { apps -> + assertTrue(apps.isEmpty()) + } + } + @Test fun testSortOrderByLastUpdated() { // insert three apps in a random order diff --git a/libs/database/src/main/java/org/fdroid/database/AppDao.kt b/libs/database/src/main/java/org/fdroid/database/AppDao.kt index c62c324a0..eea1b0517 100644 --- a/libs/database/src/main/java/org/fdroid/database/AppDao.kt +++ b/libs/database/src/main/java/org/fdroid/database/AppDao.kt @@ -379,7 +379,7 @@ internal interface AppDaoInt : AppDao { return if (searchQuery.isNullOrEmpty()) when (sortOrder) { LAST_UPDATED -> getAppListItemsByLastUpdated().map(packageManager) NAME -> getAppListItemsByName().map(packageManager) - } else getAppListItems(searchQuery).map(packageManager) + } else getAppListItems(escapeQuery(searchQuery)).map(packageManager) } override fun getAppListItems( @@ -391,7 +391,12 @@ internal interface AppDaoInt : AppDao { return if (searchQuery.isNullOrEmpty()) when (sortOrder) { LAST_UPDATED -> getAppListItemsByLastUpdated(category).map(packageManager) NAME -> getAppListItemsByName(category).map(packageManager) - } else getAppListItems(category, searchQuery).map(packageManager) + } else getAppListItems(category, escapeQuery(searchQuery)).map(packageManager) + } + + private fun escapeQuery(searchQuery: String): String { + val sanitized = searchQuery.replace(Regex.fromLiteral("\""), "\"\"") + return "\"*$sanitized*\"" } private fun LiveData>.map( @@ -408,6 +413,9 @@ internal interface AppDaoInt : AppDao { } } + /** + * Warning: Run [escapeQuery] on the given [searchQuery] before. + */ @Transaction @Query(""" SELECT repoId, packageName, app.localizedName, app.localizedSummary, app.lastUpdated, @@ -416,10 +424,13 @@ internal interface AppDaoInt : AppDao { JOIN ${AppMetadataFts.TABLE} USING (repoId, packageName) LEFT JOIN ${HighestVersion.TABLE} AS version USING (repoId, packageName) JOIN ${RepositoryPreferences.TABLE} AS pref USING (repoId) - WHERE pref.enabled = 1 AND ${AppMetadataFts.TABLE} MATCH '"*' || :searchQuery || '*"' + WHERE pref.enabled = 1 AND ${AppMetadataFts.TABLE} MATCH :searchQuery GROUP BY packageName HAVING MAX(pref.weight)""") fun getAppListItems(searchQuery: String): LiveData> + /** + * Warning: Run [escapeQuery] on the given [searchQuery] before. + */ @Transaction @Query(""" SELECT repoId, packageName, app.localizedName, app.localizedSummary, app.lastUpdated, @@ -429,7 +440,7 @@ internal interface AppDaoInt : AppDao { LEFT JOIN ${HighestVersion.TABLE} AS version USING (repoId, packageName) JOIN ${RepositoryPreferences.TABLE} AS pref USING (repoId) WHERE pref.enabled = 1 AND categories LIKE '%,' || :category || ',%' AND - ${AppMetadataFts.TABLE} MATCH '"*' || :searchQuery || '*"' + ${AppMetadataFts.TABLE} MATCH :searchQuery GROUP BY packageName HAVING MAX(pref.weight)""") fun getAppListItems(category: String, searchQuery: String): LiveData>