fix(tor): wire Coil image loading through Tor via custom Fetcher.Factory

Port Android's OkHttpFactory pattern to desktop — creates a custom
Fetcher.Factory<Uri> that calls DesktopHttpClient.currentClient() per
image request, routing all image loads through the SOCKS proxy when
Tor is active.

Avoids the OkHttpNetworkFetcher.factory() import issue by directly
constructing NetworkFetcher with the Tor-aware Call.Factory, matching
the proven Android implementation.

This closes the last network leak — ALL egress paths now route through
Tor when enabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-04-01 14:03:13 +03:00
parent 11d0a657ac
commit 566a4bf452
@@ -23,11 +23,22 @@ package com.vitorpamplona.amethyst.desktop.service.images
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
import coil3.Uri
import coil3.annotation.DelicateCoilApi
import coil3.annotation.ExperimentalCoilApi
import coil3.disk.DiskCache
import coil3.fetch.Fetcher
import coil3.memory.MemoryCache
import coil3.network.CacheStrategy
import coil3.network.ConcurrentRequestStrategy
import coil3.network.ConnectivityChecker
import coil3.network.NetworkFetcher
import coil3.network.okhttp.asNetworkClient
import coil3.request.Options
import coil3.size.Precision
import coil3.svg.SvgDecoder
import com.vitorpamplona.amethyst.desktop.network.DesktopHttpClient
import okhttp3.Call
import okio.Path.Companion.toOkioPath
import java.io.File
@@ -44,8 +55,7 @@ object DesktopImageLoaderSetup {
.diskCache { newDiskCache() }
.precision(Precision.INEXACT)
.components {
// TODO: Wire Coil through Tor — OkHttpNetworkFetcher.factory() not resolving in JVM module
// add(coil3.network.okhttp.OkHttpNetworkFetcher.factory { DesktopHttpClient.currentClient() })
add(TorAwareOkHttpFactory { DesktopHttpClient.currentClient() })
add(SvgDecoder.Factory())
add(SkiaGifDecoder.Factory())
add(DesktopBase64Fetcher.Factory)
@@ -94,3 +104,35 @@ object DesktopImageLoaderSetup {
}
}
}
/**
* Custom Coil Fetcher.Factory that routes all image requests through the Tor-aware client.
* Pattern ported from Android's OkHttpFactory in ImageLoaderSetup.kt.
*
* Each image request calls [clientProvider] to get the current OkHttpClient,
* which returns the SOCKS proxy client when Tor is active or the direct client when off.
*/
@OptIn(ExperimentalCoilApi::class)
private class TorAwareOkHttpFactory(
val clientProvider: () -> Call.Factory,
) : Fetcher.Factory<Uri> {
private val cacheStrategyLazy = lazy { CacheStrategy.DEFAULT }
override fun create(
data: Uri,
options: Options,
imageLoader: ImageLoader,
): Fetcher? {
if (data.scheme != "http" && data.scheme != "https") return null
return NetworkFetcher(
url = data.toString(),
options = options,
networkClient = lazy { clientProvider().asNetworkClient() },
diskCache = lazy { imageLoader.diskCache },
cacheStrategy = cacheStrategyLazy,
connectivityChecker = lazy { ConnectivityChecker(options.context) },
concurrentRequestStrategy = lazy { ConcurrentRequestStrategy.UNCOORDINATED },
)
}
}