Skip to content

4277332

Choose a tag to compare

@github-actions github-actions released this 04 Oct 07:26
· 6 commits to master since this release
LXManga: fetch through the WebView when Cloudflare rejects OkHttp

The device log settled it:

    [LxManga] GET https://lxmanga.space/danh-sach?... | jar=AndroidCookieJar cookies=[_ga,_ga_XZQ22BMRNK,cf_clearance,cf_chl_rc_ni]

and then nothing — no "-> HTTP <code>" line, i.e. the request never came
back because the app's CloudFlareInterceptor turned the response into a
CloudFlareProtectedException. So: the jar is the WebView-backed
AndroidCookieJar, the HttpOnly cf_clearance *is* in it, OkHttp *does*
send it — and Cloudflare challenges the request anyway.

That is the known behaviour for a clearance bound to the client it was
issued to (Cloudflare ties cf_clearance to the TLS/JA4 fingerprint and
UA as well as the cookie when the site is in "Under Attack" mode). A
Java/OkHttp client is not the Chromium that solved the Turnstile, so
presenting the cookie cannot be enough — which is why the whole
"CloudFlareActivity + shared cookie jar" loop never converges on this
site and kept asking the user to solve the challenge again.

fetchDocument now tries two transports:

  1. plain OkHttp, as before — used whenever Cloudflare accepts it. A
     Cloudflare rejection (CloudFlareProtectedException /
     CloudFlareBlockedException — both `IOException` subclasses from the
     app, detected by class name so the parser keeps compiling against
     the plain tsuki library) is caught instead of propagated, and a
     challenge body that no interceptor caught is detected explicitly.

  2. the app's WebView via `evaluateJs` + `fetch`: `evaluateJs(url, …)`
     loads a stub document whose origin is the page URL, so the fetch is
     same-origin and goes through the WebView network stack — the
     clearance *and* the fingerprint it was issued to. Same HTML,
     parsed with Jsoup, so every selector is untouched. If the WebView
     is challenged too, AuthRequiredException points the user at the
     existing "Đăng nhập" row, whose WebView is the one that can clear
     it.

Result handling got fixed along the way:

  * `WebView.evaluateJavascript` hands string results back JSON-encoded
    (`"[{\"url\":…}]"`), while the parser assumed raw text — so
    getPages could never have parsed a chapter's URL array. Results are
    now unwrapped via JSONTokener.
  * Older WebView builds do not resolve the promise an async IIFE
    returns and hand back `{}`; fetchDocument then retries with a
    synchronous XHR script that cannot be mishandled.
  * getListPage returned all 120 `div.relative` nodes for the 60 mangas
    the site renders (every card exists twice, mobile + desktop), which
    skews the paginator. Now collapsed by URL.
  * Diagnostics kept (jar class, cookie names, clearance *age* parsed
    from the token, which transport served the request).

Tests: 40/40 in the JVM harness, incl. the new fallback paths (rejection
→ WebView HTML, quoted vs unquoted results, sync-XHR retry, challenge
shell, AuthRequired message).