From 5c2fbec80ae14b0d7c5cef3f64d7750a5d985c00 Mon Sep 17 00:00:00 2001 From: NerdNumber9 Date: Thu, 18 Apr 2019 22:08:09 -0400 Subject: [PATCH] Add universal captcha detection --- .../ui/captcha/AutoSolvingWebViewClient.kt | 22 +---- .../captcha/HeadersInjectingWebViewClient.kt | 80 +++++++++++++++++++ .../exh/ui/captcha/SolveCaptchaActivity.kt | 35 ++++---- 3 files changed, 101 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt diff --git a/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt b/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt index 08aa7cee7..4dcf50279 100644 --- a/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt +++ b/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt @@ -15,8 +15,8 @@ import java.nio.charset.Charset class AutoSolvingWebViewClient(activity: SolveCaptchaActivity, source: CaptchaCompletionVerifier, injectScript: String?, - private val headers: Map) - : BasicWebViewClient(activity, source, injectScript) { + headers: Map) + : HeadersInjectingWebViewClient(activity, source, injectScript, headers) { override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { // Inject our custom script into the recaptcha iframes @@ -32,24 +32,6 @@ class AutoSolvingWebViewClient(activity: SolveCaptchaActivity, doc.toString().byteInputStream(Charset.forName("UTF-8")).buffered() ) } - if(headers.isNotEmpty()) { - val response = activity.httpClient.newCall(request.toOkHttpRequest() - .newBuilder() - .apply { - headers.forEach { (n, v) -> addHeader(n, v) } - } - .build()) - .execute() - - return WebResourceResponse( - response.body()?.contentType()?.let { "${it.type()}/${it.subtype()}" }, - response.body()?.contentType()?.charset()?.toString(), - response.code(), - response.message(), - response.headers().toMultimap().mapValues { it.value.joinToString(",") }, - response.body()?.byteStream() - ) - } return super.shouldInterceptRequest(view, request) } } \ No newline at end of file diff --git a/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt b/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt new file mode 100644 index 000000000..a66f68c8f --- /dev/null +++ b/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt @@ -0,0 +1,80 @@ +package exh.ui.captcha + +import android.os.Build +import android.support.annotation.RequiresApi +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +open class HeadersInjectingWebViewClient(activity: SolveCaptchaActivity, + source: CaptchaCompletionVerifier, + injectScript: String?, + private val headers: Map) + : BasicWebViewClient(activity, source, injectScript) { + + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { + // Temp disabled as it's unreliable + /*if(headers.isNotEmpty()) { + val response = activity.httpClient.newCall(request.toOkHttpRequest() + .newBuilder() + .apply { + headers.forEach { (n, v) -> header(n, v) } + } + .build()) + .execute() + + return WebResourceResponse( + response.body()?.contentType()?.let { "${it.type()}/${it.subtype()}" }, + response.body()?.contentType()?.charset()?.toString(), + response.code(), + response.message().nullIfBlank() ?: FALLBACK_REASON_PHRASES[response.code()] ?: "Unknown status", + response.headers().toMultimap().mapValues { it.value.joinToString(",") }, + response.body()?.byteStream() + ) + }*/ + return super.shouldInterceptRequest(view, request) + } + + companion object { + private val FALLBACK_REASON_PHRASES = mapOf( + 100 to "Continue", + 101 to "Switching Protocols", + 200 to "OK", + 201 to "Created", + 202 to "Accepted", + 203 to "Non-Authoritative Information", + 204 to "No Content", + 205 to "Reset Content", + 206 to "Partial Content", + 300 to "Multiple Choices", + 301 to "Moved Permanently", + 302 to "Moved Temporarily", + 303 to "See Other", + 304 to "Not Modified", + 305 to "Use Proxy", + 400 to "Bad Request", + 401 to "Unauthorized", + 402 to "Payment Required", + 403 to "Forbidden", + 404 to "Not Found", + 405 to "Method Not Allowed", + 406 to "Not Acceptable", + 407 to "Proxy Authentication Required", + 408 to "Request Time-out", + 409 to "Conflict", + 410 to "Gone", + 411 to "Length Required", + 412 to "Precondition Failed", + 413 to "Request Entity Too Large", + 414 to "Request-URI Too Large", + 415 to "Unsupported Media Type", + 500 to "Internal Server Error", + 501 to "Not Implemented", + 502 to "Bad Gateway", + 503 to "Service Unavailable", + 504 to "Gateway Time-out", + 505 to "HTTP Version not supported" + ) + } +} diff --git a/app/src/main/java/exh/ui/captcha/SolveCaptchaActivity.kt b/app/src/main/java/exh/ui/captcha/SolveCaptchaActivity.kt index 59a614153..42f7d30b4 100644 --- a/app/src/main/java/exh/ui/captcha/SolveCaptchaActivity.kt +++ b/app/src/main/java/exh/ui/captcha/SolveCaptchaActivity.kt @@ -124,23 +124,26 @@ class SolveCaptchaActivity : AppCompatActivity() { } } - webview.webViewClient = if (preferencesHelper.eh_autoSolveCaptchas().getOrDefault() - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - // Fetch auto-solve credentials early for speed - credentialsObservable = httpClient.newCall(Request.Builder() - // Rob demo credentials - .url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials") - .build()) - .asObservableSuccess() - .subscribeOn(Schedulers.io()) - .map { - val json = jsonParser.parse(it.body()!!.string()) - it.close() - json["token"].string - }.melt() + webview.webViewClient = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if(preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) { + // Fetch auto-solve credentials early for speed + credentialsObservable = httpClient.newCall(Request.Builder() + // Rob demo credentials + .url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials") + .build()) + .asObservableSuccess() + .subscribeOn(Schedulers.io()) + .map { + val json = jsonParser.parse(it.body()!!.string()) + it.close() + json["token"].string + }.melt() - webview.addJavascriptInterface(this@SolveCaptchaActivity, "exh") - AutoSolvingWebViewClient(this, source, script, headers) + webview.addJavascriptInterface(this@SolveCaptchaActivity, "exh") + AutoSolvingWebViewClient(this, source, script, headers) + } else { + HeadersInjectingWebViewClient(this, source, script, headers) + } } else { BasicWebViewClient(this, source, script) }