diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt index 33724d1e8..a9a275cfb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt @@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.toShareIntent +import eu.kanade.tachiyomi.util.system.toWebViewHeaders import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.setComposeContent import kotlinx.coroutines.Dispatchers @@ -71,7 +72,7 @@ class WebViewActivity : BaseActivity() { val source = sourceManager.get(intent.extras!!.getLong(SOURCE_KEY)) as? HttpSource value = withContext(Dispatchers.IO) { try { - source?.headers?.toMultimap()?.mapValues { it.value.getOrNull(0) ?: "" }.orEmpty() + source?.headers?.toWebViewHeaders().orEmpty() } catch (e: Exception) { logcat(LogPriority.ERROR, e) { "Failed to build headers" } emptyMap() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewViewModel.kt index d8003480f..2ca4537bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewViewModel.kt @@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.toShareIntent +import eu.kanade.tachiyomi.util.system.toWebViewHeaders import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -47,7 +48,7 @@ class WebViewViewModel( viewModelScope.launch(Dispatchers.IO) { val source = sourceId?.let { sourceManager.get(it) as? HttpSource } headers.value = try { - source?.headers?.toMultimap()?.mapValues { it.value.getOrNull(0) ?: "" }.orEmpty() + source?.headers?.toWebViewHeaders().orEmpty() } catch (e: Exception) { logcat(LogPriority.ERROR, e) { "Failed to build headers" } emptyMap() diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index 4f5576715..cfaebc88f 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -15,6 +15,7 @@ import androidx.webkit.WebViewCompat import androidx.webkit.WebViewFeature import eu.kanade.tachiyomi.network.AndroidCookieJar import eu.kanade.tachiyomi.util.system.isOutdated +import eu.kanade.tachiyomi.util.system.toWebViewHeaders import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.CoroutineScope import logcat.LogPriority @@ -139,7 +140,7 @@ class CloudflareInterceptor( } else { originalRequest.url.newBuilder().encodedPath("/").query(null).fragment(null).build().toString() } - val headers = parseHeaders(originalRequest.headers) + val headers = originalRequest.headers.toWebViewHeaders() executor.execute { webview = createWebView(originalRequest) diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt index 92135aa23..a0d6e59c5 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt @@ -13,12 +13,10 @@ import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import okhttp3.Headers import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response import tachiyomi.i18n.MR -import java.util.Locale import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -78,16 +76,6 @@ abstract class WebViewInterceptor( return true } - fun parseHeaders(headers: Headers): Map { - return headers - // Keeping unsafe header makes webview throw [net::ERR_INVALID_ARGUMENT] - .filter { (name, value) -> - isRequestHeaderSafe(name, value) - } - .groupBy(keySelector = { (name, _) -> name }) { (_, value) -> value } - .mapValues { it.value.getOrNull(0).orEmpty() } - } - fun CountDownLatch.awaitFor30Seconds() { await(30, TimeUnit.SECONDS) } @@ -100,16 +88,3 @@ abstract class WebViewInterceptor( } } } - -// Based on [IsRequestHeaderSafe] in -// https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/header_util.cc -private fun isRequestHeaderSafe(_name: String, _value: String): Boolean { - val name = _name.lowercase(Locale.ENGLISH) - val value = _value.lowercase(Locale.ENGLISH) - if (name in unsafeHeaderNames || name.startsWith("proxy-")) return false - if (name == "connection" && value == "upgrade") return false - return true -} -private val unsafeHeaderNames = listOf( - "content-length", "host", "trailer", "te", "upgrade", "cookie2", "keep-alive", "transfer-encoding", "set-cookie", -) diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt index 00fed0391..0f250e103 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt @@ -11,7 +11,9 @@ import androidx.webkit.WebSettingsCompat import androidx.webkit.WebViewFeature import kotlinx.coroutines.suspendCancellableCoroutine import logcat.LogPriority +import okhttp3.Headers import tachiyomi.core.common.util.system.logcat +import java.util.Locale import kotlin.coroutines.resume object WebViewUtil { @@ -144,6 +146,31 @@ fun WebView.setUserAgent(userAgent: String) { } } +fun Headers.toWebViewHeaders(): Map { + return this + // Keeping unsafe header makes webview throw [net::ERR_INVALID_ARGUMENT] + .filter { (name, value) -> + isRequestHeaderSafe(name, value) + } + .groupBy(keySelector = { (name, _) -> name }) { (_, value) -> value } + .mapValues { it.value.getOrNull(0).orEmpty() } +} + +// Based on [IsRequestHeaderSafe] in +// https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/header_util.cc +private fun isRequestHeaderSafe(_name: String, _value: String): Boolean { + val name = _name.lowercase(Locale.ENGLISH) + val value = _value.lowercase(Locale.ENGLISH) + if (name in unsafeHeaderNames || name.startsWith("proxy-")) return false + if (name == "connection" && value == "upgrade") return false + return true +} +private val unsafeHeaderNames = listOf( + "content-length", "host", "trailer", "te", "upgrade", "cookie2", "keep-alive", "transfer-encoding", "set-cookie", + // Renderer is killed if it doesn't match the request initiator + "origin", +) + private const val WEBVIEW_BRAND = "Android WebView" private const val CHROMIUM_BRAND = "Chromium" private const val CHROME_BRAND = "Google Chrome"