/* * 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 java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import nya.miku.wishmaster.api.HttpChanModule; import nya.miku.wishmaster.api.interfaces.CancellableTask; import nya.miku.wishmaster.api.util.RegexUtils; import nya.miku.wishmaster.common.Async; import nya.miku.wishmaster.common.Logger; import nya.miku.wishmaster.common.MainApplication; import nya.miku.wishmaster.http.interactive.InteractiveException; import nya.miku.wishmaster.http.streamer.HttpRequestModel; import nya.miku.wishmaster.http.streamer.HttpResponseModel; import nya.miku.wishmaster.http.streamer.HttpStreamer; import nya.miku.wishmaster.lib.base64.Base64; import nya.miku.wishmaster.ui.AppearanceUtils; import nya.miku.wishmaster.ui.CompatibilityUtils; import org.apache.commons.lang3.tuple.Pair; import cz.msebera.android.httpclient.Header; import cz.msebera.android.httpclient.HttpHeaders; import cz.msebera.android.httpclient.NameValuePair; import cz.msebera.android.httpclient.client.HttpClient; import cz.msebera.android.httpclient.client.entity.UrlEncodedFormEntity; import cz.msebera.android.httpclient.message.BasicHeader; import cz.msebera.android.httpclient.message.BasicNameValuePair; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; import android.content.DialogInterface; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; public class Recaptcha2fallback extends InteractiveException { private static final long serialVersionUID = 1L; private static final String TAG = "Recaptcha2fallback"; private static Pair<String, String> lastChallenge = null; private static final String RECAPTCHA_FALLBACK_URL = "://www.google.com/recaptcha/api/fallback?k="; private static final String RECAPTCHA_IMAGE_URL = "://www.google.com/recaptcha/api2/payload?c="; private String chanName; private String scheme; private String baseUrl; private String publicKey; private String sToken; @Override public String getServiceName() { return "Recaptcha (fallback)"; } /** * @param baseUrl URL, с которого должна открываться капча * @param publicKey открытый ключ * @param sToken Secure Token * @param chanName название модуля чана (модуль должен имплементировать {@link HttpChanModule}) */ public Recaptcha2fallback(String baseUrl, String publicKey, String sToken, String chanName) { this.chanName = chanName; this.scheme = "https"; this.baseUrl = baseUrl; this.publicKey = publicKey; this.sToken = sToken; } @Override public void handle(final Activity activity, final CancellableTask task, final Callback callback) { try { final HttpClient httpClient = ((HttpChanModule) MainApplication.getInstance().getChanModule(chanName)).getHttpClient(); final String usingURL = scheme + RECAPTCHA_FALLBACK_URL + publicKey + (sToken != null && sToken.length() > 0 ? ("&stoken=" + sToken) : ""); String refererURL = baseUrl != null && baseUrl.length() > 0 ? baseUrl : usingURL; Header[] customHeaders = new Header[] { new BasicHeader(HttpHeaders.REFERER, refererURL) }; String htmlChallenge; if (lastChallenge != null && lastChallenge.getLeft().equals(usingURL)) { htmlChallenge = lastChallenge.getRight(); } else { htmlChallenge = HttpStreamer.getInstance().getStringFromUrl(usingURL, HttpRequestModel.builder().setGET().setCustomHeaders(customHeaders).build(), httpClient, null, task, false); } lastChallenge = null; Matcher challengeMatcher = Pattern.compile("name=\"c\" value=\"([\\w-]+)").matcher(htmlChallenge); if (challengeMatcher.find()) { final String challenge = challengeMatcher.group(1); HttpResponseModel responseModel = HttpStreamer.getInstance().getFromUrl(scheme + RECAPTCHA_IMAGE_URL + challenge + "&k=" + publicKey, HttpRequestModel.builder().setGET().setCustomHeaders(customHeaders).build(), httpClient, null, task); try { InputStream imageStream = responseModel.stream; final Bitmap challengeBitmap = BitmapFactory.decodeStream(imageStream); final String message; Matcher messageMatcher = Pattern.compile("imageselect-message(?:.*?)>(.*?)</div>").matcher(htmlChallenge); if (messageMatcher.find()) message = RegexUtils.removeHtmlTags(messageMatcher.group(1)); else message = null; final Bitmap candidateBitmap; Matcher candidateMatcher = Pattern.compile("fbc-imageselect-candidates(?:.*?)src=\"data:image/(?:.*?);base64,([^\"]*)\""). matcher(htmlChallenge); if (candidateMatcher.find()) { Bitmap bmp = null; try { byte[] imgData = Base64.decode(candidateMatcher.group(1), Base64.DEFAULT); bmp = BitmapFactory.decodeByteArray(imgData, 0, imgData.length); } catch (Exception e) {} candidateBitmap = bmp; } else candidateBitmap = null; activity.runOnUiThread(new Runnable() { final int maxX = 3; final int maxY = 3; final boolean[] isSelected = new boolean[maxX * maxY]; @SuppressLint("InlinedApi") @Override public void run() { LinearLayout rootLayout = new LinearLayout(activity); rootLayout.setOrientation(LinearLayout.VERTICAL); if (candidateBitmap != null) { ImageView candidateView = new ImageView(activity); candidateView.setImageBitmap(candidateBitmap); int picSize = (int) (activity.getResources().getDisplayMetrics().density * 50 + 0.5f); candidateView.setLayoutParams(new LinearLayout.LayoutParams(picSize, picSize)); candidateView.setScaleType(ImageView.ScaleType.FIT_XY); rootLayout.addView(candidateView); } if (message != null) { TextView textView = new TextView(activity); textView.setText(message); CompatibilityUtils.setTextAppearance(textView, android.R.style.TextAppearance); textView.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); rootLayout.addView(textView); } FrameLayout frame = new FrameLayout(activity); frame.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); final ImageView imageView = new ImageView(activity); imageView.setLayoutParams(new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); imageView.setScaleType(ImageView.ScaleType.FIT_XY); imageView.setImageBitmap(challengeBitmap); frame.addView(imageView); final LinearLayout selector = new LinearLayout(activity); selector.setLayoutParams(new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); AppearanceUtils.callWhenLoaded(imageView, new Runnable() { @Override public void run() { selector.setLayoutParams(new FrameLayout.LayoutParams(imageView.getWidth(), imageView.getHeight())); } }); selector.setOrientation(LinearLayout.VERTICAL); selector.setWeightSum(maxY); for (int y=0; y<maxY; ++y) { LinearLayout subSelector = new LinearLayout(activity); subSelector.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)); subSelector.setOrientation(LinearLayout.HORIZONTAL); subSelector.setWeightSum(maxX); for (int x=0; x<maxX; ++x) { FrameLayout switcher = new FrameLayout(activity); switcher.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)); switcher.setTag(new int[] { x, y }); switcher.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int[] coord = (int[]) v.getTag(); int index = coord[1] * maxX + coord[0]; isSelected[index] = !isSelected[index]; v.setBackgroundColor(isSelected[index] ? Color.argb(128, 0, 255, 0) : Color.TRANSPARENT); } }); subSelector.addView(switcher); } selector.addView(subSelector); } frame.addView(selector); rootLayout.addView(frame); Button checkButton = new Button(activity); checkButton.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); checkButton.setText(android.R.string.ok); rootLayout.addView(checkButton); ScrollView dlgView = new ScrollView(activity); dlgView.addView(rootLayout); final Dialog dialog = new Dialog(activity); dialog.setTitle("Recaptcha"); dialog.setContentView(dlgView); dialog.setCanceledOnTouchOutside(false); dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { if (!task.isCancelled()) { callback.onError("Cancelled"); } } }); dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); dialog.show(); checkButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); if (task.isCancelled()) return; Async.runAsync(new Runnable() { @Override public void run() { try { List<NameValuePair> pairs = new ArrayList<NameValuePair>(); pairs.add(new BasicNameValuePair("c", challenge)); for (int i=0; i<isSelected.length; ++i) if (isSelected[i]) pairs.add(new BasicNameValuePair("response", Integer.toString(i))); HttpRequestModel request = HttpRequestModel.builder(). setPOST(new UrlEncodedFormEntity(pairs, "UTF-8")). setCustomHeaders(new Header[] { new BasicHeader(HttpHeaders.REFERER, usingURL) }).build(); String response = HttpStreamer.getInstance(). getStringFromUrl(usingURL, request, httpClient, null, task, false); String hash = ""; Matcher matcher = Pattern.compile("fbc-verification-token(?:.*?)<textarea[^>]*>([^<]*)<", Pattern.DOTALL).matcher(response); if (matcher.find()) hash = matcher.group(1); if (hash.length() > 0) { Recaptcha2solved.push(publicKey, hash); activity.runOnUiThread(new Runnable() { @Override public void run() { callback.onSuccess(); } }); } else { lastChallenge = Pair.of(usingURL, response); throw new RecaptchaException("incorrect answer (hash is empty)"); } } catch (final Exception e){ Logger.e(TAG, e); if (task.isCancelled()) return; handle(activity, task, callback); } } }); } }); } }); } finally { responseModel.release(); } } else throw new Exception("can't parse recaptcha challenge answer"); } catch (final Exception e) { Logger.e(TAG, e); if (!task.isCancelled()) { activity.runOnUiThread(new Runnable() { @Override public void run() { callback.onError(e.getMessage() != null ? e.getMessage() : e.toString()); } }); } } } }