From 1e5475114217438bf5783fce0b22163a448d2c5b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 23 Aug 2023 10:53:44 +0200 Subject: [PATCH] [db] handle SerializationException when parsing index for RepoAdder --- .../main/java/org/fdroid/repo/RepoAdder.kt | 5 ++ .../main/java/org/fdroid/repo/RepoFetcher.kt | 8 ++- .../java/org/fdroid/repo/RepoV1Fetcher.kt | 3 +- .../java/org/fdroid/repo/RepoAdderTest.kt | 59 +++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt b/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt index 2e39882e2..53767b373 100644 --- a/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt +++ b/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import kotlinx.serialization.SerializationException import mu.KotlinLogging import org.fdroid.database.AppOverviewItem import org.fdroid.database.FDroidDatabase @@ -166,6 +167,10 @@ internal class RepoAdder( log.error(e) { "Error fetching repo." } addRepoState.value = AddRepoError(IO_ERROR, e) return + } catch (e: SerializationException) { + log.error(e) { "Error fetching repo." } + addRepoState.value = AddRepoError(INVALID_INDEX, e) + return } // set final result val finalRepo = receivedRepo diff --git a/libs/database/src/main/java/org/fdroid/repo/RepoFetcher.kt b/libs/database/src/main/java/org/fdroid/repo/RepoFetcher.kt index a96c815d9..e8c244c02 100644 --- a/libs/database/src/main/java/org/fdroid/repo/RepoFetcher.kt +++ b/libs/database/src/main/java/org/fdroid/repo/RepoFetcher.kt @@ -1,6 +1,7 @@ package org.fdroid.repo import android.net.Uri +import kotlinx.serialization.SerializationException import org.fdroid.database.AppOverviewItem import org.fdroid.database.Repository import org.fdroid.download.NotFoundException @@ -8,7 +9,12 @@ import org.fdroid.index.SigningException import java.io.IOException internal fun interface RepoFetcher { - @Throws(IOException::class, SigningException::class, NotFoundException::class) + @Throws( + IOException::class, + SigningException::class, + NotFoundException::class, + SerializationException::class, + ) suspend fun fetchRepo( uri: Uri, repo: Repository, diff --git a/libs/database/src/main/java/org/fdroid/repo/RepoV1Fetcher.kt b/libs/database/src/main/java/org/fdroid/repo/RepoV1Fetcher.kt index cc0b9507e..0d6e7fafd 100644 --- a/libs/database/src/main/java/org/fdroid/repo/RepoV1Fetcher.kt +++ b/libs/database/src/main/java/org/fdroid/repo/RepoV1Fetcher.kt @@ -4,6 +4,7 @@ import android.content.res.Resources import android.net.Uri import androidx.core.os.ConfigurationCompat.getLocales import androidx.core.os.LocaleListCompat +import kotlinx.serialization.SerializationException import org.fdroid.database.Repository import org.fdroid.download.DownloaderFactory import org.fdroid.index.IndexConverter @@ -23,7 +24,7 @@ internal class RepoV1Fetcher( private val locales: LocaleListCompat = getLocales(Resources.getSystem().configuration) - @Throws(SigningException::class) + @Throws(SigningException::class, SerializationException::class) override suspend fun fetchRepo( uri: Uri, repo: Repository, diff --git a/libs/database/src/test/java/org/fdroid/repo/RepoAdderTest.kt b/libs/database/src/test/java/org/fdroid/repo/RepoAdderTest.kt index f5bc50880..e95dbb6df 100644 --- a/libs/database/src/test/java/org/fdroid/repo/RepoAdderTest.kt +++ b/libs/database/src/test/java/org/fdroid/repo/RepoAdderTest.kt @@ -35,6 +35,7 @@ import org.fdroid.index.IndexFormatVersion import org.fdroid.index.SigningException import org.fdroid.index.TempFileProvider import org.fdroid.repo.AddRepoError.ErrorType.INVALID_FINGERPRINT +import org.fdroid.repo.AddRepoError.ErrorType.INVALID_INDEX import org.fdroid.repo.AddRepoError.ErrorType.IO_ERROR import org.fdroid.repo.AddRepoError.ErrorType.UNKNOWN_SOURCES_DISALLOWED import org.fdroid.test.TestDataMinV2 @@ -375,6 +376,64 @@ internal class RepoAdderTest { } } + @Test + fun testParsingThrowsSerializationException() = runTest { + val url = "https://example.org/repo" + val urlTrimmed = url.trimEnd('/') + val jarFile = folder.newFile() + val index = "{ invalid JSON foo bar,".toByteArray() + val indexStream = DigestInputStream(ByteArrayInputStream(index), digest) + + every { tempFileProvider.createTempFile() } returns jarFile + every { + downloaderFactory.create( + repo = match { + it.address == urlTrimmed && it.formatVersion == IndexFormatVersion.TWO + }, + uri = Uri.parse("$urlTrimmed/entry.jar"), + indexFile = any(), + destFile = jarFile, + ) + } returns downloader + every { downloader.download() } answers { + jarFile.outputStream().use { outputStream -> + assets.open("diff-empty-min/entry.jar").use { inputStream -> + inputStream.copyTo(outputStream) + } + } + } + coEvery { + httpManager.getDigestInputStream(match { + it.indexFile.name == "../index-min-v2.json" && + it.mirrors.size == 1 && it.mirrors[0].baseUrl == urlTrimmed + }) + } returns indexStream + every { + digest.digest() // sha256 from entry.json + } returns "851ecda085ed53adab25f761a9dbf4c09d59e5bff9c9d5530814d56445ae30f2".decodeHex() + + repoAdder.addRepoState.test { + assertIs(awaitItem()) + + repoAdder.fetchRepository( + url = url, + username = null, + password = null, + proxy = null + ) + + val state1 = awaitItem() + assertIs(state1) + assertNull(state1.repo) + assertTrue(state1.apps.isEmpty()) + assertFalse(state1.canAdd) + + val state2 = awaitItem() + assertTrue(state2 is AddRepoError, "$state2") + assertEquals(INVALID_INDEX, state2.errorType) + } + } + @Test fun testWrongFingerprint() = runTest { val url = "https://example.org/repo/"