/* * Overchan Android (Meta Imageboard Client) * Copyright (C) 2014-2016 miku-nyan <https://github.com/miku-nyan> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package nya.miku.wishmaster.http.recaptcha; import nya.miku.wishmaster.R; import nya.miku.wishmaster.api.interfaces.CancellableTask; import nya.miku.wishmaster.http.interactive.InteractiveException; import java.util.concurrent.atomic.AtomicBoolean; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.graphics.Bitmap; import android.net.http.SslError; import android.view.ViewGroup; import android.webkit.SslErrorHandler; import android.webkit.WebView; import android.webkit.WebViewClient; /** * Объект рекапчи 2.0, работает в обычном режиме (js через webview).<br> * Работа через прокси не поддерживается.<br> * Решённые капчи (значения, которые нужно будет передавать как "g-recaptcha-response") будут сохраняться в стеке объекта {@link Recaptcha2solved}. * @author miku-nyan * */ public class Recaptcha2js extends InteractiveException { private static final long serialVersionUID = 1L; private static final String INTERCEPT = "_intercept?"; private static final String FALLBACK_INTERCEPT = "_fallback"; private static final String FALLBACK_FILTER = "g-recaptcha-response="; private final String baseUrl, publicKey, sToken; private static final String getRecahtchaHtml(String publicKey, String sToken) { return "<script type=\"text/javascript\">" + "window.globalOnCaptchaEntered = function(res) { " + "location.href = \"" + INTERCEPT + "\" + res; " + "}" + "</script>" + "<script src=\"https://www.google.com/recaptcha/api.js\" async defer></script>" + "<form action=\"" + FALLBACK_INTERCEPT + "\" method=\"GET\" id=\"_overchan_submitform\">" + "<div class=\"g-recaptcha\" data-sitekey=\"" + publicKey + "\" " + (sToken != null && sToken.length() > 0 ? ("data-stoken=\"" + sToken + "\" ") : "") + "data-callback=\"globalOnCaptchaEntered\"></div>" + "</form>" + "<script type=\"text/javascript\">" + "function _overchan_add_fallback_submit() { " + "var element = document.createElement(\"input\"); " + "element.setAttribute(\"type\", \"submit\"); " + "element.setAttribute(\"value\", \"Submit\");" + "var foo = document.getElementById(\"_overchan_submitform\"); " + "foo.appendChild(element); " + "}" + "</script>"; } private volatile boolean done = false; private volatile String pushedHash = null; /** * @param baseUrl URL, с которого должна открываться капча * @param publicKey открытый ключ * @param sToken Secure Token */ public Recaptcha2js(String baseUrl, String publicKey, String sToken) { this.baseUrl = baseUrl; this.publicKey = publicKey; this.sToken = sToken; } @Override public String getServiceName() { return "Recaptcha"; } @Override public void handle(final Activity activity, final CancellableTask task, final Callback callback) { if (task.isCancelled()) return; activity.runOnUiThread(new Runnable() { @SuppressLint("SetJavaScriptEnabled") @Override public void run() { final Dialog dialog = new Dialog(activity); WebView webView = new WebView(activity); webView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); webView.setWebViewClient(new WebViewClient() { AtomicBoolean fallbackButtonAdded = new AtomicBoolean(false); @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { if (url.contains(INTERCEPT) || url.contains(FALLBACK_INTERCEPT)) { String hash = url.contains(INTERCEPT) ? url.substring(url.indexOf(INTERCEPT) + INTERCEPT.length()) : (url.contains(FALLBACK_FILTER) ? url.substring(url.indexOf(FALLBACK_FILTER) + FALLBACK_FILTER.length()) : null); if (hash != null && hash.length() > 0 && !hash.equals(pushedHash)) { Recaptcha2solved.push(publicKey, hash); pushedHash = hash; } if (!done && !task.isCancelled()) activity.runOnUiThread(new Runnable() { @Override public void run() { if (!done && !task.isCancelled()) { done = true; callback.onSuccess(); dialog.dismiss(); } } }); } super.onPageStarted(view, url, favicon); } @Override public void onLoadResource(WebView view, String url) { if (url.contains("/api/fallback?") && fallbackButtonAdded.compareAndSet(false, true)) { view.loadUrl("javascript:_overchan_add_fallback_submit()"); } super.onLoadResource(view, url); } @Override public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) { new AlertDialog.Builder(activity). setTitle(R.string.error_ssl). setMessage(R.string.ssl_connect_anyway). setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { handler.proceed(); } }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { handler.cancel(); } }).show(); } }); //webView.getSettings().setUserAgentString(HttpConstants.USER_AGENT_STRING); webView.getSettings().setJavaScriptEnabled(true); dialog.setTitle("Recaptcha"); dialog.setContentView(webView); dialog.setCanceledOnTouchOutside(false); dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { if (!done && !task.isCancelled()) { done = true; callback.onError("Cancelled"); } } }); dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); dialog.show(); String url = baseUrl != null ? baseUrl : "https://127.0.0.1/"; webView.loadDataWithBaseURL(url, getRecahtchaHtml(publicKey, sToken), "text/html", "UTF-8", null); } }); } }