fix: support for POST requests on CloudflareInterceptor (#1909)

* fix: support for POST requests

Works with Flaresolverr. Required for Kagane.

Byparr is not a drop-in replacement, it just ignores the `cmd`  and interprets everything as a GET request.

* Use encodeToString instead

* linting

* Use FormBody for encoding

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>

* Add missing imports

* linting, again

---------

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
This commit is contained in:
David Brochero
2026-02-18 20:51:07 -03:00
committed by GitHub
parent c52457c80e
commit 2249d237dd

View File

@@ -14,9 +14,9 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Cookie import okhttp3.Cookie
import okhttp3.FormBody
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@@ -24,6 +24,7 @@ import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import okio.Buffer
import suwayomi.tachidesk.server.serverConfig import suwayomi.tachidesk.server.serverConfig
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.IOException import java.io.IOException
@@ -70,7 +71,8 @@ class CloudflareInterceptor(
flareResponse.solution.status in 200..299 && flareResponse.solution.status in 200..299 &&
flareResponse.solution.response != null flareResponse.solution.response != null
) { ) {
val isImage = flareResponse.solution.response.contains(CHROME_IMAGE_TEMPLATE_REGEX) val isImage =
flareResponse.solution.response.contains(CHROME_IMAGE_TEMPLATE_REGEX)
if (!isImage) { if (!isImage) {
logger.debug { "Falling back to FlareSolverr response" } logger.debug { "Falling back to FlareSolverr response" }
@@ -87,7 +89,8 @@ class CloudflareInterceptor(
} }
} }
val request = CFClearance.requestWithFlareSolverr(flareResponse, setUserAgent, originalRequest) val request =
CFClearance.requestWithFlareSolverr(flareResponse, setUserAgent, originalRequest)
chain.proceed(request) chain.proceed(request)
} catch (e: Exception) { } catch (e: Exception) {
@@ -187,7 +190,6 @@ object CFClearance {
onlyCookies: Boolean, onlyCookies: Boolean,
): FlareSolverResponse { ): FlareSolverResponse {
val timeout = serverConfig.flareSolverrTimeout.value.seconds val timeout = serverConfig.flareSolverrTimeout.value.seconds
return with(json) { return with(json) {
mutex.withLock { mutex.withLock {
client.value client.value
@@ -198,7 +200,7 @@ object CFClearance {
Json Json
.encodeToString( .encodeToString(
FlareSolverRequest( FlareSolverRequest(
"request.get", "request.${originalRequest.method.lowercase()}",
originalRequest.url.toString(), originalRequest.url.toString(),
session = serverConfig.flareSolverrSessionName.value, session = serverConfig.flareSolverrSessionName.value,
sessionTtlMinutes = serverConfig.flareSolverrSessionTtl.value, sessionTtlMinutes = serverConfig.flareSolverrSessionTtl.value,
@@ -208,6 +210,14 @@ object CFClearance {
}, },
returnOnlyCookies = onlyCookies, returnOnlyCookies = onlyCookies,
maxTimeout = timeout.inWholeMilliseconds.toInt(), maxTimeout = timeout.inWholeMilliseconds.toInt(),
postData =
if (originalRequest.method == "POST" && originalRequest.body is FormBody) {
Buffer()
.also { (originalRequest.body as FormBody).writeTo(it) }
.readUtf8()
} else {
null
},
), ),
).toRequestBody(jsonMediaType), ).toRequestBody(jsonMediaType),
), ),
@@ -238,7 +248,9 @@ object CFClearance {
if (!cookie.path.isNullOrEmpty()) it.path(cookie.path) if (!cookie.path.isNullOrEmpty()) it.path(cookie.path)
// We need to convert the expires time to milliseconds for the persistent cookie store // We need to convert the expires time to milliseconds for the persistent cookie store
if (cookie.expires != null && cookie.expires > 0) it.expiresAt((cookie.expires * 1000).toLong()) if (cookie.expires != null && cookie.expires > 0) it.expiresAt((cookie.expires * 1000).toLong())
if (!cookie.domain.startsWith('.')) it.hostOnlyDomain(cookie.domain.removePrefix(".")) if (!cookie.domain.startsWith('.')) {
it.hostOnlyDomain(cookie.domain.removePrefix("."))
}
}.build() }.build()
}.groupBy { it.domain } }.groupBy { it.domain }
.flatMap { (domain, cookies) -> .flatMap { (domain, cookies) ->