mirror of https://github.com/kurisufriend/Clover
(#696) New captcha windowdev
commit
0c58c619ad
@ -0,0 +1,139 @@ |
|||||||
|
/* |
||||||
|
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||||
|
* Copyright (C) 2014 Floens |
||||||
|
* |
||||||
|
* 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 org.floens.chan.ui.captcha.v2; |
||||||
|
|
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.annotation.Nullable; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class CaptchaInfo { |
||||||
|
CaptchaType captchaType; |
||||||
|
@NonNull |
||||||
|
List<Integer> checkboxes; |
||||||
|
@Nullable |
||||||
|
String cParameter; |
||||||
|
@NonNull |
||||||
|
List<Bitmap> challengeImages; |
||||||
|
@Nullable |
||||||
|
CaptchaTitle captchaTitle; |
||||||
|
|
||||||
|
public CaptchaInfo() { |
||||||
|
captchaType = CaptchaType.Unknown; |
||||||
|
checkboxes = new ArrayList<>(); |
||||||
|
cParameter = null; |
||||||
|
challengeImages = null; |
||||||
|
captchaTitle = null; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCaptchaType(CaptchaType captchaType) { |
||||||
|
this.captchaType = captchaType; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCheckboxes(@NonNull List<Integer> checkboxes) { |
||||||
|
this.checkboxes = checkboxes; |
||||||
|
} |
||||||
|
|
||||||
|
public void setcParameter(@Nullable String cParameter) { |
||||||
|
this.cParameter = cParameter; |
||||||
|
} |
||||||
|
|
||||||
|
public void setChallengeImages(@NonNull List<Bitmap> challengeImages) { |
||||||
|
this.challengeImages = challengeImages; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCaptchaTitle(@Nullable CaptchaTitle captchaTitle) { |
||||||
|
this.captchaTitle = captchaTitle; |
||||||
|
} |
||||||
|
|
||||||
|
public CaptchaType getCaptchaType() { |
||||||
|
return captchaType; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
public List<Bitmap> getChallengeImages() { |
||||||
|
return challengeImages; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
public List<Integer> getCheckboxes() { |
||||||
|
return checkboxes; |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
public String getcParameter() { |
||||||
|
return cParameter; |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
public CaptchaTitle getCaptchaTitle() { |
||||||
|
return captchaTitle; |
||||||
|
} |
||||||
|
|
||||||
|
public enum CaptchaType { |
||||||
|
Unknown, |
||||||
|
// 3x3
|
||||||
|
Canonical, |
||||||
|
// 2x4
|
||||||
|
NoCanonical; |
||||||
|
|
||||||
|
public static CaptchaType fromCheckboxesCount(int count) { |
||||||
|
if (count == 8) { |
||||||
|
return NoCanonical; |
||||||
|
} else if (count == 9) { |
||||||
|
return Canonical; |
||||||
|
} |
||||||
|
|
||||||
|
return Unknown; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static class CaptchaTitle { |
||||||
|
private String title; |
||||||
|
private int boldStart = -1; |
||||||
|
private int boldEnd = -1; |
||||||
|
|
||||||
|
public CaptchaTitle(String title, int boldStart, int boldEnd) { |
||||||
|
this.title = title; |
||||||
|
this.boldStart = boldStart; |
||||||
|
this.boldEnd = boldEnd; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isEmpty() { |
||||||
|
return title.isEmpty() && boldStart == -1 && boldEnd == -1; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean hasBold() { |
||||||
|
return boldStart != -1 && boldEnd != -1; |
||||||
|
} |
||||||
|
|
||||||
|
public String getTitle() { |
||||||
|
return title; |
||||||
|
} |
||||||
|
|
||||||
|
public int getBoldStart() { |
||||||
|
return boldStart; |
||||||
|
} |
||||||
|
|
||||||
|
public int getBoldEnd() { |
||||||
|
return boldEnd; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,400 @@ |
|||||||
|
/* |
||||||
|
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||||
|
* Copyright (C) 2014 Floens |
||||||
|
* |
||||||
|
* 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 org.floens.chan.ui.captcha.v2; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.graphics.BitmapFactory; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.v4.util.Pair; |
||||||
|
|
||||||
|
import org.floens.chan.utils.BackgroundUtils; |
||||||
|
import org.floens.chan.utils.IOUtils; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.OutputStream; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import okhttp3.OkHttpClient; |
||||||
|
import okhttp3.Request; |
||||||
|
import okhttp3.Response; |
||||||
|
|
||||||
|
public class CaptchaNoJsHtmlParser { |
||||||
|
private static final String TAG = "CaptchaNoJsHtmlParser"; |
||||||
|
private static final String googleBaseUrl = "https://www.google.com"; |
||||||
|
private static final Pattern checkboxesPattern = |
||||||
|
Pattern.compile("<input class=\"fbc-imageselect-checkbox-\\d+\" type=\"checkbox\" name=\"response\" value=\"(\\d+)\">"); |
||||||
|
|
||||||
|
// FIXME: this pattern captures the C parameter as many times as it is in the HTML.
|
||||||
|
// Should match only the first occurrence instead.
|
||||||
|
private static final Pattern cParameterPattern = |
||||||
|
Pattern.compile("<input type=\"hidden\" name=\"c\" value=\"(.*?)\"/>"); |
||||||
|
private static final Pattern challengeTitlePattern = |
||||||
|
Pattern.compile("<div class=\"(rc-imageselect-desc-no-canonical|rc-imageselect-desc)\">(.*?)</div>"); |
||||||
|
private static final Pattern challengeImageUrlPattern = |
||||||
|
Pattern.compile("<img class=\"fbc-imageselect-payload\" src=\"(.*?)&"); |
||||||
|
private static final Pattern challengeTitleBoldPartPattern = |
||||||
|
Pattern.compile("<strong>(.*?)</strong>"); |
||||||
|
private static final Pattern verificationTokenPattern = |
||||||
|
Pattern.compile("<div class=\"fbc-verification-token\"><textarea dir=\"ltr\" readonly>(.*?)</textarea></div>"); |
||||||
|
private static final String CHALLENGE_IMAGE_FILE_NAME = "challenge_image_file"; |
||||||
|
private static final int SUCCESS_STATUS_CODE = 200; |
||||||
|
|
||||||
|
private OkHttpClient okHttpClient; |
||||||
|
private Context context; |
||||||
|
|
||||||
|
public CaptchaNoJsHtmlParser(Context context, OkHttpClient okHttpClient) { |
||||||
|
this.context = context; |
||||||
|
this.okHttpClient = okHttpClient; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
public CaptchaInfo parseHtml(String responseHtml, String siteKey) |
||||||
|
throws CaptchaNoJsV2ParsingError, IOException { |
||||||
|
if (BackgroundUtils.isMainThread()) { |
||||||
|
throw new RuntimeException("Must not be executed on the main thread!"); |
||||||
|
} |
||||||
|
|
||||||
|
CaptchaInfo captchaInfo = new CaptchaInfo(); |
||||||
|
|
||||||
|
// parse challenge checkboxes' ids
|
||||||
|
parseCheckboxes(responseHtml, captchaInfo); |
||||||
|
|
||||||
|
// parse captcha random key
|
||||||
|
parseCParameter(responseHtml, captchaInfo); |
||||||
|
|
||||||
|
// parse title
|
||||||
|
parseChallengeTitle(responseHtml, captchaInfo); |
||||||
|
|
||||||
|
// parse image url, download image and split it into list of separate images
|
||||||
|
parseAndDownloadChallengeImage(responseHtml, captchaInfo, siteKey); |
||||||
|
|
||||||
|
return captchaInfo; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
String parseVerificationToken(String responseHtml) throws CaptchaNoJsV2ParsingError { |
||||||
|
if (BackgroundUtils.isMainThread()) { |
||||||
|
throw new RuntimeException("Must not be executed on the main thread!"); |
||||||
|
} |
||||||
|
|
||||||
|
Matcher matcher = verificationTokenPattern.matcher(responseHtml); |
||||||
|
if (!matcher.find()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Could not parse verification token"); |
||||||
|
} |
||||||
|
|
||||||
|
String token; |
||||||
|
|
||||||
|
try { |
||||||
|
token = matcher.group(1); |
||||||
|
} catch (Exception error) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Could not parse verification token", error); |
||||||
|
} |
||||||
|
|
||||||
|
if (token == null || token.isEmpty()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Verification token is null or empty"); |
||||||
|
} |
||||||
|
|
||||||
|
return token; |
||||||
|
} |
||||||
|
|
||||||
|
public void parseChallengeTitle( |
||||||
|
String responseHtml, |
||||||
|
CaptchaInfo captchaInfo |
||||||
|
) throws CaptchaNoJsV2ParsingError { |
||||||
|
Matcher matcher = challengeTitlePattern.matcher(responseHtml); |
||||||
|
if (!matcher.find()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Could not parse challenge title " + responseHtml); |
||||||
|
} |
||||||
|
|
||||||
|
CaptchaInfo.CaptchaTitle captchaTitle; |
||||||
|
|
||||||
|
try { |
||||||
|
String title = matcher.group(2); |
||||||
|
Matcher titleMatcher = challengeTitleBoldPartPattern.matcher(title); |
||||||
|
|
||||||
|
if (titleMatcher.find()) { |
||||||
|
// find the part of the title that should be bold
|
||||||
|
int start = title.indexOf("<strong>"); |
||||||
|
|
||||||
|
String firstPart = title.substring(0, start); |
||||||
|
String boldPart = titleMatcher.group(1); |
||||||
|
String resultTitle = firstPart + boldPart; |
||||||
|
|
||||||
|
captchaTitle = new CaptchaInfo.CaptchaTitle( |
||||||
|
resultTitle, |
||||||
|
firstPart.length(), |
||||||
|
firstPart.length() + boldPart.length()); |
||||||
|
} else { |
||||||
|
// could not find it
|
||||||
|
captchaTitle = new CaptchaInfo.CaptchaTitle(title, -1, -1); |
||||||
|
} |
||||||
|
} catch (Exception error) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Error while trying to parse challenge title", error); |
||||||
|
} |
||||||
|
|
||||||
|
if (captchaTitle.isEmpty()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("challengeTitle is empty"); |
||||||
|
} |
||||||
|
|
||||||
|
captchaInfo.setCaptchaTitle(captchaTitle); |
||||||
|
} |
||||||
|
|
||||||
|
private void parseAndDownloadChallengeImage( |
||||||
|
String responseHtml, |
||||||
|
CaptchaInfo captchaInfo, |
||||||
|
String siteKey |
||||||
|
) throws CaptchaNoJsV2ParsingError, IOException { |
||||||
|
Matcher matcher = challengeImageUrlPattern.matcher(responseHtml); |
||||||
|
if (!matcher.find()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Could not parse challenge image url"); |
||||||
|
} |
||||||
|
|
||||||
|
String challengeImageUrl; |
||||||
|
|
||||||
|
try { |
||||||
|
challengeImageUrl = matcher.group(1); |
||||||
|
} catch (Exception error) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Error while trying to parse challenge image url", error); |
||||||
|
} |
||||||
|
|
||||||
|
if (challengeImageUrl == null) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("challengeImageUrl is null"); |
||||||
|
} |
||||||
|
|
||||||
|
if (challengeImageUrl.isEmpty()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("challengeImageUrl is empty"); |
||||||
|
} |
||||||
|
|
||||||
|
String fullUrl = googleBaseUrl + challengeImageUrl + "&k=" + siteKey; |
||||||
|
File challengeImageFile = downloadAndStoreImage(fullUrl); |
||||||
|
|
||||||
|
Pair<Integer, Integer> columnsAndRows = calculateColumnsAndRows( |
||||||
|
captchaInfo.captchaType |
||||||
|
); |
||||||
|
|
||||||
|
Integer columns = columnsAndRows.first; |
||||||
|
Integer rows = columnsAndRows.second; |
||||||
|
|
||||||
|
if (columns == null) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Could not calculate columns count"); |
||||||
|
} |
||||||
|
|
||||||
|
if (rows == null) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Could not calculate rows count"); |
||||||
|
} |
||||||
|
|
||||||
|
List<Bitmap> challengeImages = decodeImagesFromFile(challengeImageFile, columns, rows); |
||||||
|
captchaInfo.setChallengeImages(challengeImages); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private Pair<Integer, Integer> calculateColumnsAndRows( |
||||||
|
CaptchaInfo.CaptchaType captchaType |
||||||
|
) throws CaptchaNoJsV2ParsingError { |
||||||
|
switch (captchaType) { |
||||||
|
case Canonical: { |
||||||
|
// 3x3 captcha with square images
|
||||||
|
return new Pair<>(3, 3); |
||||||
|
} |
||||||
|
|
||||||
|
case NoCanonical: { |
||||||
|
// 2x4 captcha with rectangle images (store fronts)
|
||||||
|
return new Pair<>(2, 4); |
||||||
|
} |
||||||
|
|
||||||
|
default: { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Unknown captcha type"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void parseCParameter( |
||||||
|
String responseHtml, |
||||||
|
CaptchaInfo captchaInfo |
||||||
|
) throws CaptchaNoJsV2ParsingError { |
||||||
|
Matcher matcher = cParameterPattern.matcher(responseHtml); |
||||||
|
if (!matcher.find()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Could not parse c parameter"); |
||||||
|
} |
||||||
|
|
||||||
|
String cParameter; |
||||||
|
|
||||||
|
try { |
||||||
|
cParameter = matcher.group(1); |
||||||
|
} catch (Exception error) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Error while trying to parse c parameter", error); |
||||||
|
} |
||||||
|
|
||||||
|
if (cParameter == null) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("cParameter is null"); |
||||||
|
} |
||||||
|
|
||||||
|
if (cParameter.isEmpty()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("cParameter is empty"); |
||||||
|
} |
||||||
|
|
||||||
|
captchaInfo.setcParameter(cParameter); |
||||||
|
} |
||||||
|
|
||||||
|
public void parseCheckboxes( |
||||||
|
String responseHtml, |
||||||
|
CaptchaInfo captchaInfo |
||||||
|
) throws CaptchaNoJsV2ParsingError { |
||||||
|
Matcher matcher = checkboxesPattern.matcher(responseHtml); |
||||||
|
Set<Integer> checkboxesSet = new HashSet<>(matcher.groupCount()); |
||||||
|
int index = 0; |
||||||
|
|
||||||
|
while (matcher.find()) { |
||||||
|
try { |
||||||
|
Integer checkboxId = Integer.parseInt(matcher.group(1)); |
||||||
|
checkboxesSet.add(checkboxId); |
||||||
|
} catch (Exception error) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Error while trying to parse checkbox with id (" + index + ")", error); |
||||||
|
} |
||||||
|
|
||||||
|
++index; |
||||||
|
} |
||||||
|
|
||||||
|
if (checkboxesSet.isEmpty()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Could not parse any checkboxes!"); |
||||||
|
} |
||||||
|
|
||||||
|
CaptchaInfo.CaptchaType captchaType; |
||||||
|
|
||||||
|
try { |
||||||
|
captchaType = CaptchaInfo.CaptchaType.fromCheckboxesCount(checkboxesSet.size()); |
||||||
|
} catch (Exception error) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Error while trying to parse captcha type", error); |
||||||
|
} |
||||||
|
|
||||||
|
if (captchaType == CaptchaInfo.CaptchaType.Unknown) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Unknown captcha type"); |
||||||
|
} |
||||||
|
|
||||||
|
captchaInfo.setCaptchaType(captchaType); |
||||||
|
captchaInfo.setCheckboxes(new ArrayList<>(checkboxesSet)); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private File downloadAndStoreImage(String fullUrl) throws IOException, CaptchaNoJsV2ParsingError { |
||||||
|
Request request = new Request.Builder() |
||||||
|
.url(fullUrl) |
||||||
|
.build(); |
||||||
|
|
||||||
|
try (Response response = okHttpClient.newCall(request).execute()) { |
||||||
|
if (response.code() != SUCCESS_STATUS_CODE) { |
||||||
|
throw new CaptchaNoJsV2ParsingError( |
||||||
|
"Could not download challenge image, status code = " + response.code()); |
||||||
|
} |
||||||
|
|
||||||
|
if (response.body() == null) { |
||||||
|
throw new CaptchaNoJsV2ParsingError("Captcha challenge image body is null"); |
||||||
|
} |
||||||
|
|
||||||
|
try (InputStream is = response.body().byteStream()) { |
||||||
|
File imageFile = getChallengeImageFile(); |
||||||
|
|
||||||
|
try (OutputStream os = new FileOutputStream(imageFile)) { |
||||||
|
IOUtils.copy(is, os); |
||||||
|
} |
||||||
|
|
||||||
|
return imageFile; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private File getChallengeImageFile() throws CaptchaNoJsV2ParsingError, IOException { |
||||||
|
File imageFile = new File(context.getCacheDir(), CHALLENGE_IMAGE_FILE_NAME); |
||||||
|
|
||||||
|
if (imageFile.exists()) { |
||||||
|
if (!imageFile.delete()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError( |
||||||
|
"Could not delete old challenge image file \"" + imageFile.getAbsolutePath() + "\""); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!imageFile.createNewFile()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError( |
||||||
|
"Could not create challenge image file \"" + imageFile.getAbsolutePath() + "\""); |
||||||
|
} |
||||||
|
|
||||||
|
if (!imageFile.canWrite()) { |
||||||
|
throw new CaptchaNoJsV2ParsingError( |
||||||
|
"Cannot write to the challenge image file \"" + imageFile.getAbsolutePath() + "\""); |
||||||
|
} |
||||||
|
|
||||||
|
return imageFile; |
||||||
|
} |
||||||
|
|
||||||
|
private List<Bitmap> decodeImagesFromFile(File challengeImageFile, int columns, int rows) { |
||||||
|
Bitmap originalBitmap = BitmapFactory.decodeFile(challengeImageFile.getAbsolutePath()); |
||||||
|
List<Bitmap> resultImages = new ArrayList<>(columns * rows); |
||||||
|
|
||||||
|
try { |
||||||
|
int imageWidth = originalBitmap.getWidth() / columns; |
||||||
|
int imageHeight = originalBitmap.getHeight() / rows; |
||||||
|
|
||||||
|
for (int column = 0; column < columns; ++column) { |
||||||
|
for (int row = 0; row < rows; ++row) { |
||||||
|
Bitmap imagePiece = Bitmap.createBitmap( |
||||||
|
originalBitmap, |
||||||
|
row * imageWidth, |
||||||
|
column * imageHeight, |
||||||
|
imageWidth, |
||||||
|
imageHeight); |
||||||
|
|
||||||
|
resultImages.add(imagePiece); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return resultImages; |
||||||
|
} catch (Exception error) { |
||||||
|
for (Bitmap bitmap : resultImages) { |
||||||
|
if (!bitmap.isRecycled()) { |
||||||
|
bitmap.recycle(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
resultImages.clear(); |
||||||
|
throw error; |
||||||
|
} finally { |
||||||
|
if (originalBitmap != null) { |
||||||
|
originalBitmap.recycle(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static class CaptchaNoJsV2ParsingError extends Exception { |
||||||
|
public CaptchaNoJsV2ParsingError(String message) { |
||||||
|
super(message); |
||||||
|
} |
||||||
|
|
||||||
|
public CaptchaNoJsV2ParsingError(String message, Throwable cause) { |
||||||
|
super(message, cause); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,303 @@ |
|||||||
|
/* |
||||||
|
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||||
|
* Copyright (C) 2014 Floens |
||||||
|
* |
||||||
|
* 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 org.floens.chan.ui.captcha.v2; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.content.res.Configuration; |
||||||
|
import android.graphics.Typeface; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.annotation.Nullable; |
||||||
|
import android.support.constraint.ConstraintLayout; |
||||||
|
import android.support.v7.widget.AppCompatButton; |
||||||
|
import android.support.v7.widget.AppCompatTextView; |
||||||
|
import android.text.Spannable; |
||||||
|
import android.text.SpannableString; |
||||||
|
import android.text.style.StyleSpan; |
||||||
|
import android.util.AttributeSet; |
||||||
|
import android.view.View; |
||||||
|
import android.widget.FrameLayout; |
||||||
|
import android.widget.GridView; |
||||||
|
import android.widget.ScrollView; |
||||||
|
import android.widget.Toast; |
||||||
|
|
||||||
|
import org.floens.chan.R; |
||||||
|
import org.floens.chan.core.site.Site; |
||||||
|
import org.floens.chan.core.site.SiteAuthentication; |
||||||
|
import org.floens.chan.ui.captcha.AuthenticationLayoutCallback; |
||||||
|
import org.floens.chan.ui.captcha.AuthenticationLayoutInterface; |
||||||
|
import org.floens.chan.utils.AndroidUtils; |
||||||
|
import org.floens.chan.utils.Logger; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class CaptchaNoJsLayoutV2 extends FrameLayout |
||||||
|
implements AuthenticationLayoutInterface, |
||||||
|
CaptchaNoJsPresenterV2.AuthenticationCallbacks, View.OnClickListener { |
||||||
|
private static final String TAG = "CaptchaNoJsLayoutV2"; |
||||||
|
|
||||||
|
private AppCompatTextView captchaChallengeTitle; |
||||||
|
private GridView captchaImagesGrid; |
||||||
|
private AppCompatButton captchaVerifyButton; |
||||||
|
private AppCompatButton useOldCaptchaButton; |
||||||
|
private AppCompatButton reloadCaptchaButton; |
||||||
|
|
||||||
|
private CaptchaNoJsV2Adapter adapter; |
||||||
|
private CaptchaNoJsPresenterV2 presenter; |
||||||
|
private Context context; |
||||||
|
private AuthenticationLayoutCallback callback; |
||||||
|
|
||||||
|
public CaptchaNoJsLayoutV2(@NonNull Context context) { |
||||||
|
super(context); |
||||||
|
init(context); |
||||||
|
} |
||||||
|
|
||||||
|
public CaptchaNoJsLayoutV2(@NonNull Context context, @Nullable AttributeSet attrs) { |
||||||
|
super(context, attrs); |
||||||
|
init(context); |
||||||
|
} |
||||||
|
|
||||||
|
public CaptchaNoJsLayoutV2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { |
||||||
|
super(context, attrs, defStyleAttr); |
||||||
|
init(context); |
||||||
|
} |
||||||
|
|
||||||
|
private void init(Context context) { |
||||||
|
this.context = context; |
||||||
|
this.presenter = new CaptchaNoJsPresenterV2(this, context); |
||||||
|
this.adapter = new CaptchaNoJsV2Adapter(context); |
||||||
|
|
||||||
|
View view = inflate(context, R.layout.layout_captcha_nojs_v2, this); |
||||||
|
|
||||||
|
captchaChallengeTitle = view.findViewById(R.id.captcha_layout_v2_title); |
||||||
|
captchaImagesGrid = view.findViewById(R.id.captcha_layout_v2_images_grid); |
||||||
|
captchaVerifyButton = view.findViewById(R.id.captcha_layout_v2_verify_button); |
||||||
|
useOldCaptchaButton = view.findViewById(R.id.captcha_layout_v2_use_old_captcha_button); |
||||||
|
reloadCaptchaButton = view.findViewById(R.id.captcha_layout_v2_reload_button); |
||||||
|
ConstraintLayout buttonsHolder = view.findViewById(R.id.captcha_layout_v2_buttons_holder); |
||||||
|
ScrollView background = view.findViewById(R.id.captcha_layout_v2_background); |
||||||
|
|
||||||
|
background.setBackgroundColor(AndroidUtils.getAttrColor(getContext(), R.attr.backcolor)); |
||||||
|
buttonsHolder.setBackgroundColor(AndroidUtils.getAttrColor(getContext(), R.attr.backcolor_secondary)); |
||||||
|
captchaChallengeTitle.setBackgroundColor(AndroidUtils.getAttrColor(getContext(), R.attr.backcolor_secondary)); |
||||||
|
captchaChallengeTitle.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary)); |
||||||
|
captchaVerifyButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary)); |
||||||
|
useOldCaptchaButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary)); |
||||||
|
reloadCaptchaButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary)); |
||||||
|
|
||||||
|
captchaVerifyButton.setOnClickListener(this); |
||||||
|
useOldCaptchaButton.setOnClickListener(this); |
||||||
|
reloadCaptchaButton.setOnClickListener(this); |
||||||
|
|
||||||
|
captchaVerifyButton.setEnabled(false); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onConfigurationChanged(Configuration newConfig) { |
||||||
|
super.onConfigurationChanged(newConfig); |
||||||
|
|
||||||
|
reset(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void initialize(Site site, AuthenticationLayoutCallback callback) { |
||||||
|
this.callback = callback; |
||||||
|
|
||||||
|
SiteAuthentication authentication = site.actions().postAuthenticate(); |
||||||
|
if (authentication.type != SiteAuthentication.Type.CAPTCHA2_NOJS) { |
||||||
|
callback.onFallbackToV1CaptchaView(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
presenter.init(authentication.siteKey, authentication.baseUrl); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void reset() { |
||||||
|
hardReset(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void hardReset() { |
||||||
|
CaptchaNoJsPresenterV2.RequestCaptchaInfoError captchaInfoError = presenter.requestCaptchaInfo(); |
||||||
|
|
||||||
|
switch (captchaInfoError) { |
||||||
|
case Ok: |
||||||
|
break; |
||||||
|
case HoldYourHorses: |
||||||
|
showToast(getContext().getString( |
||||||
|
R.string.captcha_layout_v2_you_are_requesting_captcha_too_fast)); |
||||||
|
break; |
||||||
|
case AlreadyInProgress: |
||||||
|
showToast(getContext().getString( |
||||||
|
R.string.captcha_layout_v2_captcha_request_is_already_in_progress)); |
||||||
|
break; |
||||||
|
case AlreadyShutdown: |
||||||
|
// do nothing
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean requireResetAfterComplete() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onDestroy() { |
||||||
|
adapter.onDestroy(); |
||||||
|
presenter.onDestroy(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCaptchaInfoParsed(CaptchaInfo captchaInfo) { |
||||||
|
// called on a background thread
|
||||||
|
|
||||||
|
AndroidUtils.runOnUiThread(() -> { |
||||||
|
captchaVerifyButton.setEnabled(true); |
||||||
|
renderCaptchaWindow(captchaInfo); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onVerificationDone(String verificationToken) { |
||||||
|
// called on a background thread
|
||||||
|
|
||||||
|
AndroidUtils.runOnUiThread(() -> { |
||||||
|
captchaVerifyButton.setEnabled(true); |
||||||
|
callback.onAuthenticationComplete(this, null, verificationToken); |
||||||
|
}); |
||||||
|
} |
||||||
|
// Called when we got response from re-captcha but could not parse some part of it
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCaptchaInfoParseError(Throwable error) { |
||||||
|
// called on a background thread
|
||||||
|
|
||||||
|
handleError(true, error); |
||||||
|
} |
||||||
|
|
||||||
|
private void handleError(boolean shouldFallback, Throwable error) { |
||||||
|
AndroidUtils.runOnUiThread(() -> { |
||||||
|
Logger.e(TAG, "CaptchaV2 error", error); |
||||||
|
|
||||||
|
String message = error.getMessage(); |
||||||
|
showToast(message); |
||||||
|
|
||||||
|
captchaVerifyButton.setEnabled(true); |
||||||
|
|
||||||
|
if (shouldFallback) { |
||||||
|
callback.onFallbackToV1CaptchaView(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void showToast(String message) { |
||||||
|
AndroidUtils.runOnUiThread(() -> Toast.makeText(context, message, Toast.LENGTH_LONG).show()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
if (v == captchaVerifyButton) { |
||||||
|
sendVerificationResponse(); |
||||||
|
} else if (v == useOldCaptchaButton) { |
||||||
|
callback.onFallbackToV1CaptchaView(); |
||||||
|
} else if (v == reloadCaptchaButton) { |
||||||
|
reset(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void renderCaptchaWindow(CaptchaInfo captchaInfo) { |
||||||
|
try { |
||||||
|
setCaptchaTitle(captchaInfo); |
||||||
|
|
||||||
|
captchaImagesGrid.setAdapter(null); |
||||||
|
captchaImagesGrid.setAdapter(adapter); |
||||||
|
|
||||||
|
int columnsCount; |
||||||
|
int imageSize = captchaImagesGrid.getWidth(); |
||||||
|
|
||||||
|
switch (captchaInfo.getCaptchaType()) { |
||||||
|
case Canonical: |
||||||
|
columnsCount = 3; |
||||||
|
imageSize /= columnsCount; |
||||||
|
break; |
||||||
|
case NoCanonical: |
||||||
|
columnsCount = 2; |
||||||
|
imageSize /= columnsCount; |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new IllegalStateException("Unknown captcha type"); |
||||||
|
} |
||||||
|
|
||||||
|
captchaImagesGrid.setNumColumns(columnsCount); |
||||||
|
|
||||||
|
adapter.setImageSize(imageSize); |
||||||
|
adapter.setImages(captchaInfo.challengeImages); |
||||||
|
|
||||||
|
captchaVerifyButton.setEnabled(true); |
||||||
|
} catch (Throwable error) { |
||||||
|
Logger.e(TAG, "renderCaptchaWindow", error); |
||||||
|
if (callback != null) { |
||||||
|
callback.onFallbackToV1CaptchaView(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void setCaptchaTitle(CaptchaInfo captchaInfo) { |
||||||
|
if (captchaInfo.getCaptchaTitle() == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (captchaInfo.getCaptchaTitle().hasBold()) { |
||||||
|
SpannableString spannableString = new SpannableString(captchaInfo.getCaptchaTitle().getTitle()); |
||||||
|
spannableString.setSpan( |
||||||
|
new StyleSpan(Typeface.BOLD), |
||||||
|
captchaInfo.getCaptchaTitle().getBoldStart(), |
||||||
|
captchaInfo.getCaptchaTitle().getBoldEnd(), |
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
||||||
|
|
||||||
|
captchaChallengeTitle.setText(spannableString); |
||||||
|
} else { |
||||||
|
captchaChallengeTitle.setText(captchaInfo.getCaptchaTitle().getTitle()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void sendVerificationResponse() { |
||||||
|
List<Integer> selectedIds = adapter.getCheckedImageIds(); |
||||||
|
|
||||||
|
try { |
||||||
|
CaptchaNoJsPresenterV2.VerifyError verifyError = presenter.verify(selectedIds); |
||||||
|
switch (verifyError) { |
||||||
|
case Ok: |
||||||
|
captchaVerifyButton.setEnabled(false); |
||||||
|
break; |
||||||
|
case NoImagesSelected: |
||||||
|
showToast(getContext().getString(R.string.captcha_layout_v2_you_have_to_select_at_least_one_image)); |
||||||
|
break; |
||||||
|
case AlreadyInProgress: |
||||||
|
showToast(getContext().getString(R.string.captcha_layout_v2_verification_already_in_progress)); |
||||||
|
break; |
||||||
|
case AlreadyShutdown: |
||||||
|
// do nothing
|
||||||
|
break; |
||||||
|
} |
||||||
|
} catch (Throwable error) { |
||||||
|
onCaptchaInfoParseError(error); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,370 @@ |
|||||||
|
/* |
||||||
|
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||||
|
* Copyright (C) 2014 Floens |
||||||
|
* |
||||||
|
* 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 org.floens.chan.ui.captcha.v2; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.support.annotation.Nullable; |
||||||
|
|
||||||
|
import org.floens.chan.utils.BackgroundUtils; |
||||||
|
import org.floens.chan.utils.Logger; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.io.UnsupportedEncodingException; |
||||||
|
import java.net.URLEncoder; |
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.ExecutorService; |
||||||
|
import java.util.concurrent.Executors; |
||||||
|
import java.util.concurrent.atomic.AtomicBoolean; |
||||||
|
|
||||||
|
import okhttp3.MediaType; |
||||||
|
import okhttp3.MultipartBody; |
||||||
|
import okhttp3.OkHttpClient; |
||||||
|
import okhttp3.Request; |
||||||
|
import okhttp3.RequestBody; |
||||||
|
import okhttp3.Response; |
||||||
|
import okhttp3.ResponseBody; |
||||||
|
|
||||||
|
public class CaptchaNoJsPresenterV2 { |
||||||
|
private static final String TAG = "CaptchaNoJsPresenterV2"; |
||||||
|
private static final String userAgentHeader = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"; |
||||||
|
private static final String acceptHeader = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"; |
||||||
|
private static final String acceptEncodingHeader = "deflate, br"; |
||||||
|
private static final String acceptLanguageHeader = "en-US"; |
||||||
|
private static final String recaptchaUrlBase = "https://www.google.com/recaptcha/api/fallback?k="; |
||||||
|
private static final String encoding = "UTF-8"; |
||||||
|
private static final String mediaType = "application/x-www-form-urlencoded"; |
||||||
|
private static final String recaptchaChallengeString = "reCAPTCHA challenge"; |
||||||
|
private static final String verificationTokenString = "fbc-verification-token"; |
||||||
|
private static final int SUCCESS_STATUS_CODE = 200; |
||||||
|
private static final long CAPTCHA_REQUEST_THROTTLE_MS = 3000L; |
||||||
|
|
||||||
|
// this cookie is taken from dashchan
|
||||||
|
private static final String defaultGoogleCookies = "NID=87=gkOAkg09AKnvJosKq82kgnDnHj8Om2pLskKhdna02msog8HkdHDlasDf"; |
||||||
|
|
||||||
|
// TODO: inject this in the future when https://github.com/Floens/Clover/pull/678 is merged
|
||||||
|
private final OkHttpClient okHttpClient = new OkHttpClient(); |
||||||
|
|
||||||
|
private final ExecutorService executor = Executors.newSingleThreadExecutor(); |
||||||
|
private final CaptchaNoJsHtmlParser parser; |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private AuthenticationCallbacks callbacks; |
||||||
|
@Nullable |
||||||
|
private CaptchaInfo prevCaptchaInfo = null; |
||||||
|
|
||||||
|
private AtomicBoolean verificationInProgress = new AtomicBoolean(false); |
||||||
|
private AtomicBoolean captchaRequestInProgress = new AtomicBoolean(false); |
||||||
|
private String siteKey; |
||||||
|
private String baseUrl; |
||||||
|
private long lastTimeCaptchaRequest = 0L; |
||||||
|
|
||||||
|
public CaptchaNoJsPresenterV2(@Nullable AuthenticationCallbacks callbacks, Context context) { |
||||||
|
this.callbacks = callbacks; |
||||||
|
this.parser = new CaptchaNoJsHtmlParser(context, okHttpClient); |
||||||
|
} |
||||||
|
|
||||||
|
public void init(String siteKey, String baseUrl) { |
||||||
|
this.siteKey = siteKey; |
||||||
|
this.baseUrl = baseUrl; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Send challenge solution back to the recaptcha |
||||||
|
*/ |
||||||
|
public VerifyError verify( |
||||||
|
List<Integer> selectedIds |
||||||
|
) throws CaptchaNoJsV2Error { |
||||||
|
if (!verificationInProgress.compareAndSet(false, true)) { |
||||||
|
Logger.d(TAG, "Verify captcha request is already in progress"); |
||||||
|
return VerifyError.AlreadyInProgress; |
||||||
|
} |
||||||
|
|
||||||
|
if (executor.isShutdown()) { |
||||||
|
verificationInProgress.set(false); |
||||||
|
Logger.d(TAG, "Cannot verify, executor has been shut down"); |
||||||
|
return VerifyError.AlreadyShutdown; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
if (selectedIds.isEmpty()) { |
||||||
|
verificationInProgress.set(false); |
||||||
|
return VerifyError.NoImagesSelected; |
||||||
|
} |
||||||
|
|
||||||
|
if (prevCaptchaInfo == null) { |
||||||
|
throw new CaptchaNoJsV2Error("prevCaptchaInfo is null"); |
||||||
|
} |
||||||
|
|
||||||
|
if (prevCaptchaInfo.getcParameter() == null) { |
||||||
|
throw new CaptchaNoJsV2Error("C parameter is null"); |
||||||
|
} |
||||||
|
|
||||||
|
executor.submit(() -> { |
||||||
|
try { |
||||||
|
String recaptchaUrl = recaptchaUrlBase + siteKey; |
||||||
|
RequestBody body = createResponseBody(prevCaptchaInfo, selectedIds); |
||||||
|
|
||||||
|
Logger.d(TAG, "Verify called"); |
||||||
|
|
||||||
|
Request request = new Request.Builder() |
||||||
|
.url(recaptchaUrl) |
||||||
|
.post(body) |
||||||
|
.header("Referer", recaptchaUrl) |
||||||
|
.header("User-Agent", userAgentHeader) |
||||||
|
.header("Accept", acceptHeader) |
||||||
|
.header("Accept-Encoding", acceptEncodingHeader) |
||||||
|
.header("Accept-Language", acceptLanguageHeader) |
||||||
|
.header("Cookie", defaultGoogleCookies) |
||||||
|
.build(); |
||||||
|
|
||||||
|
try (Response response = okHttpClient.newCall(request).execute()) { |
||||||
|
prevCaptchaInfo = handleGetRecaptchaResponse(response); |
||||||
|
} finally { |
||||||
|
verificationInProgress.set(false); |
||||||
|
} |
||||||
|
} catch (Throwable error) { |
||||||
|
if (callbacks != null) { |
||||||
|
try { |
||||||
|
prevCaptchaInfo = null; |
||||||
|
callbacks.onCaptchaInfoParseError(error); |
||||||
|
} finally { |
||||||
|
verificationInProgress.set(false); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return VerifyError.Ok; |
||||||
|
} catch (Throwable error) { |
||||||
|
verificationInProgress.set(false); |
||||||
|
throw error; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Requests captcha data, parses it and then passes it to the render function |
||||||
|
*/ |
||||||
|
public RequestCaptchaInfoError requestCaptchaInfo() { |
||||||
|
if (!captchaRequestInProgress.compareAndSet(false, true)) { |
||||||
|
Logger.d(TAG, "Request captcha request is already in progress"); |
||||||
|
return RequestCaptchaInfoError.AlreadyInProgress; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
// recaptcha may become very angry at you if your are fetching it too fast
|
||||||
|
if (System.currentTimeMillis() - lastTimeCaptchaRequest < CAPTCHA_REQUEST_THROTTLE_MS) { |
||||||
|
captchaRequestInProgress.set(false); |
||||||
|
Logger.d(TAG, "Requesting captcha info too fast"); |
||||||
|
return RequestCaptchaInfoError.HoldYourHorses; |
||||||
|
} |
||||||
|
|
||||||
|
if (executor.isShutdown()) { |
||||||
|
captchaRequestInProgress.set(false); |
||||||
|
Logger.d(TAG, "Cannot request captcha info, executor has been shut down"); |
||||||
|
return RequestCaptchaInfoError.AlreadyShutdown; |
||||||
|
} |
||||||
|
|
||||||
|
lastTimeCaptchaRequest = System.currentTimeMillis(); |
||||||
|
|
||||||
|
executor.submit(() -> { |
||||||
|
try { |
||||||
|
try { |
||||||
|
prevCaptchaInfo = getCaptchaInfo(); |
||||||
|
} catch (Throwable error) { |
||||||
|
if (callbacks != null) { |
||||||
|
callbacks.onCaptchaInfoParseError(error); |
||||||
|
} |
||||||
|
|
||||||
|
throw error; |
||||||
|
} |
||||||
|
} catch (Throwable error) { |
||||||
|
Logger.e(TAG, "Error while executing captcha requests", error); |
||||||
|
|
||||||
|
prevCaptchaInfo = null; |
||||||
|
} finally { |
||||||
|
captchaRequestInProgress.set(false); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return RequestCaptchaInfoError.Ok; |
||||||
|
} catch (Throwable error) { |
||||||
|
captchaRequestInProgress.set(false); |
||||||
|
|
||||||
|
if (callbacks != null) { |
||||||
|
callbacks.onCaptchaInfoParseError(error); |
||||||
|
} |
||||||
|
|
||||||
|
// return ok here too because we already handled this exception in the callback
|
||||||
|
return RequestCaptchaInfoError.Ok; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private CaptchaInfo getCaptchaInfo() throws IOException { |
||||||
|
if (BackgroundUtils.isMainThread()) { |
||||||
|
throw new RuntimeException("Must not be executed on the main thread"); |
||||||
|
} |
||||||
|
|
||||||
|
String recaptchaUrl = recaptchaUrlBase + siteKey; |
||||||
|
|
||||||
|
Request request = new Request.Builder() |
||||||
|
.url(recaptchaUrl) |
||||||
|
.header("Referer", baseUrl) |
||||||
|
.header("User-Agent", userAgentHeader) |
||||||
|
.header("Accept", acceptHeader) |
||||||
|
.header("Accept-Encoding", acceptEncodingHeader) |
||||||
|
.header("Accept-Language", acceptLanguageHeader) |
||||||
|
.header("Cookie", defaultGoogleCookies) |
||||||
|
.build(); |
||||||
|
|
||||||
|
try (Response response = okHttpClient.newCall(request).execute()) { |
||||||
|
return handleGetRecaptchaResponse(response); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private RequestBody createResponseBody( |
||||||
|
CaptchaInfo prevCaptchaInfo, |
||||||
|
List<Integer> selectedIds |
||||||
|
) throws UnsupportedEncodingException { |
||||||
|
StringBuilder sb = new StringBuilder(); |
||||||
|
|
||||||
|
sb.append(URLEncoder.encode("c", encoding)); |
||||||
|
sb.append("="); |
||||||
|
sb.append(URLEncoder.encode(prevCaptchaInfo.getcParameter(), encoding)); |
||||||
|
sb.append("&"); |
||||||
|
|
||||||
|
for (Integer selectedImageId : selectedIds) { |
||||||
|
sb.append(URLEncoder.encode("response", encoding)); |
||||||
|
sb.append("="); |
||||||
|
sb.append(URLEncoder.encode(String.valueOf(selectedImageId), encoding)); |
||||||
|
sb.append("&"); |
||||||
|
} |
||||||
|
|
||||||
|
String resultBody; |
||||||
|
|
||||||
|
if (selectedIds.size() > 0) { |
||||||
|
resultBody = sb.deleteCharAt(sb.length() - 1).toString(); |
||||||
|
} else { |
||||||
|
resultBody = sb.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
return MultipartBody.create( |
||||||
|
MediaType.parse(mediaType), |
||||||
|
resultBody); |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private CaptchaInfo handleGetRecaptchaResponse(Response response) { |
||||||
|
try { |
||||||
|
if (response.code() != SUCCESS_STATUS_CODE) { |
||||||
|
if (callbacks != null) { |
||||||
|
callbacks.onCaptchaInfoParseError( |
||||||
|
new IOException("Bad status code for captcha request = " + response.code())); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
ResponseBody body = response.body(); |
||||||
|
if (body == null) { |
||||||
|
if (callbacks != null) { |
||||||
|
callbacks.onCaptchaInfoParseError( |
||||||
|
new IOException("Captcha response body is empty (null)")); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
String bodyString = body.string(); |
||||||
|
if (!bodyString.contains(recaptchaChallengeString)) { |
||||||
|
throw new IllegalStateException("Response body does not contain \"reCAPTCHA challenge\" string"); |
||||||
|
} |
||||||
|
|
||||||
|
if (bodyString.contains(verificationTokenString)) { |
||||||
|
// got the token
|
||||||
|
String verificationToken = parser.parseVerificationToken(bodyString); |
||||||
|
Logger.d(TAG, "Got the verification token"); |
||||||
|
|
||||||
|
if (callbacks != null) { |
||||||
|
callbacks.onVerificationDone(verificationToken); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} else { |
||||||
|
// got the challenge
|
||||||
|
CaptchaInfo captchaInfo = parser.parseHtml(bodyString, siteKey); |
||||||
|
Logger.d(TAG, "Got new challenge"); |
||||||
|
|
||||||
|
if (callbacks != null) { |
||||||
|
callbacks.onCaptchaInfoParsed(captchaInfo); |
||||||
|
} else { |
||||||
|
// Return null when callbacks are null to reset prevCaptchaInfo so that we won't
|
||||||
|
// get stuck without captchaInfo and disabled buttons forever
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return captchaInfo; |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
Logger.e(TAG, "Error while trying to parse captcha html data", e); |
||||||
|
|
||||||
|
if (callbacks != null) { |
||||||
|
callbacks.onCaptchaInfoParseError(e); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void onDestroy() { |
||||||
|
this.callbacks = null; |
||||||
|
this.prevCaptchaInfo = null; |
||||||
|
this.verificationInProgress.set(false); |
||||||
|
this.captchaRequestInProgress.set(false); |
||||||
|
|
||||||
|
executor.shutdown(); |
||||||
|
} |
||||||
|
|
||||||
|
public enum VerifyError { |
||||||
|
Ok, |
||||||
|
NoImagesSelected, |
||||||
|
AlreadyInProgress, |
||||||
|
AlreadyShutdown |
||||||
|
} |
||||||
|
|
||||||
|
public enum RequestCaptchaInfoError { |
||||||
|
Ok, |
||||||
|
AlreadyInProgress, |
||||||
|
HoldYourHorses, |
||||||
|
AlreadyShutdown |
||||||
|
} |
||||||
|
|
||||||
|
public interface AuthenticationCallbacks { |
||||||
|
void onCaptchaInfoParsed(CaptchaInfo captchaInfo); |
||||||
|
|
||||||
|
void onCaptchaInfoParseError(Throwable error); |
||||||
|
|
||||||
|
void onVerificationDone(String verificationToken); |
||||||
|
} |
||||||
|
|
||||||
|
public static class CaptchaNoJsV2Error extends Exception { |
||||||
|
public CaptchaNoJsV2Error(String message) { |
||||||
|
super(message); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,152 @@ |
|||||||
|
package org.floens.chan.ui.captcha.v2; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.support.constraint.ConstraintLayout; |
||||||
|
import android.support.v7.widget.AppCompatImageView; |
||||||
|
import android.view.HapticFeedbackConstants; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.view.animation.DecelerateInterpolator; |
||||||
|
import android.widget.BaseAdapter; |
||||||
|
|
||||||
|
import org.floens.chan.R; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class CaptchaNoJsV2Adapter extends BaseAdapter { |
||||||
|
private LayoutInflater inflater; |
||||||
|
private int imageSize = 0; |
||||||
|
|
||||||
|
private List<ImageChallengeInfo> imageList = new ArrayList<>(); |
||||||
|
|
||||||
|
public CaptchaNoJsV2Adapter(Context context) { |
||||||
|
this.inflater = LayoutInflater.from(context); |
||||||
|
} |
||||||
|
|
||||||
|
public void setImages(List<Bitmap> imageList) { |
||||||
|
cleanUpImages(); |
||||||
|
|
||||||
|
for (Bitmap bitmap : imageList) { |
||||||
|
this.imageList.add(new ImageChallengeInfo(bitmap, false)); |
||||||
|
} |
||||||
|
|
||||||
|
notifyDataSetChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getCount() { |
||||||
|
return imageList.size(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object getItem(int position) { |
||||||
|
return position; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long getItemId(int position) { |
||||||
|
return position; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public View getView(final int position, View convertView, ViewGroup parent) { |
||||||
|
if (convertView == null) { |
||||||
|
convertView = inflater.inflate(R.layout.layout_captcha_challenge_image, parent, false); |
||||||
|
} |
||||||
|
|
||||||
|
AppCompatImageView imageView = convertView.findViewById(R.id.captcha_challenge_image); |
||||||
|
imageView.setScaleX(1.0f); |
||||||
|
imageView.setScaleY(1.0f); |
||||||
|
ConstraintLayout blueCheckmarkHolder = convertView.findViewById(R.id.captcha_challenge_blue_checkmark_holder); |
||||||
|
blueCheckmarkHolder.setAlpha(0.0f); |
||||||
|
|
||||||
|
ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(imageSize, imageSize); |
||||||
|
imageView.setLayoutParams(layoutParams); |
||||||
|
|
||||||
|
imageView.setOnClickListener((view) -> { |
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); |
||||||
|
|
||||||
|
imageList.get(position).toggleChecked(); |
||||||
|
boolean isChecked = imageList.get(position).isChecked; |
||||||
|
|
||||||
|
imageView.animate() |
||||||
|
.scaleX(isChecked ? 0.8f : 1.0f) |
||||||
|
.scaleY(isChecked ? 0.8f : 1.0f) |
||||||
|
.setInterpolator(new DecelerateInterpolator(2.0f)) |
||||||
|
.setDuration(200) |
||||||
|
.start(); |
||||||
|
blueCheckmarkHolder.animate() |
||||||
|
.alpha(isChecked ? 1.0f : 0.0f) |
||||||
|
.setInterpolator(new DecelerateInterpolator(2.0f)) |
||||||
|
.setDuration(200) |
||||||
|
.start(); |
||||||
|
}); |
||||||
|
|
||||||
|
if (position >= 0 && position <= imageList.size()) { |
||||||
|
imageView.setImageBitmap(imageList.get(position).getBitmap()); |
||||||
|
} |
||||||
|
|
||||||
|
return convertView; |
||||||
|
} |
||||||
|
|
||||||
|
public List<Integer> getCheckedImageIds() { |
||||||
|
List<Integer> selectedList = new ArrayList<>(); |
||||||
|
|
||||||
|
for (int i = 0; i < imageList.size(); i++) { |
||||||
|
ImageChallengeInfo info = imageList.get(i); |
||||||
|
|
||||||
|
if (info.isChecked()) { |
||||||
|
selectedList.add(i); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return selectedList; |
||||||
|
} |
||||||
|
|
||||||
|
public void setImageSize(int imageSize) { |
||||||
|
this.imageSize = imageSize; |
||||||
|
} |
||||||
|
|
||||||
|
public void onDestroy() { |
||||||
|
cleanUpImages(); |
||||||
|
} |
||||||
|
|
||||||
|
private void cleanUpImages() { |
||||||
|
for (ImageChallengeInfo imageChallengeInfo : imageList) { |
||||||
|
imageChallengeInfo.recycle(); |
||||||
|
} |
||||||
|
|
||||||
|
imageList.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
public static class ImageChallengeInfo { |
||||||
|
private Bitmap bitmap; |
||||||
|
private boolean isChecked; |
||||||
|
|
||||||
|
public ImageChallengeInfo(Bitmap bitmap, boolean isChecked) { |
||||||
|
this.bitmap = bitmap; |
||||||
|
this.isChecked = isChecked; |
||||||
|
} |
||||||
|
|
||||||
|
public Bitmap getBitmap() { |
||||||
|
return bitmap; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isChecked() { |
||||||
|
return isChecked; |
||||||
|
} |
||||||
|
|
||||||
|
public void toggleChecked() { |
||||||
|
this.isChecked = !this.isChecked; |
||||||
|
} |
||||||
|
|
||||||
|
public void recycle() { |
||||||
|
if (bitmap != null && !bitmap.isRecycled()) { |
||||||
|
bitmap.recycle(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
/* |
||||||
|
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||||
|
* Copyright (C) 2014 Floens |
||||||
|
* |
||||||
|
* 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 org.floens.chan.ui.view; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.util.AttributeSet; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.GridView; |
||||||
|
|
||||||
|
public class WrappingGridView extends GridView { |
||||||
|
|
||||||
|
public WrappingGridView(Context context) { |
||||||
|
super(context); |
||||||
|
} |
||||||
|
|
||||||
|
public WrappingGridView(Context context, AttributeSet attrs) { |
||||||
|
super(context, attrs); |
||||||
|
} |
||||||
|
|
||||||
|
public WrappingGridView(Context context, AttributeSet attrs, int defStyle) { |
||||||
|
super(context, attrs, defStyle); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||||
|
int heightSpec = heightMeasureSpec; |
||||||
|
if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) { |
||||||
|
heightSpec = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST); |
||||||
|
} |
||||||
|
|
||||||
|
super.onMeasure(widthMeasureSpec, heightSpec); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:width="24dp" |
||||||
|
android:height="24dp" |
||||||
|
android:viewportWidth="24.0" |
||||||
|
android:viewportHeight="24.0"> |
||||||
|
<path |
||||||
|
android:fillColor="#FF20A8C6" |
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z" /> |
||||||
|
<path |
||||||
|
android:fillColor="#FFFFFFFF" |
||||||
|
android:pathData="M10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" /> |
||||||
|
</vector> |
@ -0,0 +1,41 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:padding="1dp"> |
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatImageView |
||||||
|
android:id="@+id/captcha_challenge_image" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:scaleType="fitXY" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
app:layout_constraintTop_toTopOf="parent" /> |
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout |
||||||
|
android:id="@+id/captcha_challenge_blue_checkmark_holder" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:alpha="0" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
app:layout_constraintTop_toTopOf="parent"> |
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatImageView |
||||||
|
android:id="@+id/captcha_challenge_blue_checkmark" |
||||||
|
android:layout_width="32dp" |
||||||
|
android:layout_height="32dp" |
||||||
|
android:src="@drawable/ic_blue_checkmark_24dp" |
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/captcha_challenge_blue_checkmark_holder" |
||||||
|
app:layout_constraintEnd_toEndOf="@+id/captcha_challenge_blue_checkmark_holder" |
||||||
|
app:layout_constraintStart_toStartOf="@+id/captcha_challenge_blue_checkmark_holder" |
||||||
|
app:layout_constraintTop_toTopOf="@+id/captcha_challenge_blue_checkmark_holder" /> |
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout> |
||||||
|
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout> |
@ -0,0 +1,113 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:id="@+id/captcha_layout_v2_background" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatTextView |
||||||
|
android:id="@+id/captcha_layout_v2_title" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="40dp" |
||||||
|
android:background="@color/accent" |
||||||
|
android:ellipsize="middle" |
||||||
|
android:gravity="center" |
||||||
|
android:maxLines="2" |
||||||
|
android:padding="4dp" |
||||||
|
android:text="" |
||||||
|
android:textColor="#ffffff" |
||||||
|
android:textSize="18sp" |
||||||
|
tools:text="Select all images with something" /> |
||||||
|
|
||||||
|
<android.support.v4.widget.NestedScrollView |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content"> |
||||||
|
|
||||||
|
<org.floens.chan.ui.view.WrappingGridView |
||||||
|
android:id="@+id/captcha_layout_v2_images_grid" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:gravity="center" |
||||||
|
android:stretchMode="columnWidth" /> |
||||||
|
|
||||||
|
</android.support.v4.widget.NestedScrollView> |
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout |
||||||
|
android:id="@+id/captcha_layout_v2_buttons_holder" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="64dp" |
||||||
|
android:background="@color/accent" |
||||||
|
tools:layout_editor_absoluteY="40dp"> |
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatButton |
||||||
|
android:id="@+id/captcha_layout_v2_use_old_captcha_button" |
||||||
|
android:layout_width="112dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:background="?android:attr/selectableItemBackground" |
||||||
|
android:clickable="true" |
||||||
|
android:focusable="true" |
||||||
|
android:text="@string/captcha_layout_v2_use_old_captcha" |
||||||
|
android:textColor="#ffffff" |
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/captcha_layout_v2_verify_button" |
||||||
|
app:layout_constraintEnd_toStartOf="@+id/captcha_layout_v2_refresh_cookies" |
||||||
|
app:layout_constraintHorizontal_bias="0.5" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
app:layout_constraintTop_toTopOf="@+id/captcha_layout_v2_verify_button" /> |
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatButton |
||||||
|
android:id="@+id/captcha_layout_v2_refresh_cookies" |
||||||
|
android:layout_width="80dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:background="?android:attr/selectableItemBackground" |
||||||
|
android:clickable="true" |
||||||
|
android:focusable="true" |
||||||
|
android:text="@string/captcha_layout_v2_refresh_cookies" |
||||||
|
android:textColor="#ffffff" |
||||||
|
android:visibility="gone" |
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/captcha_layout_v2_verify_button" |
||||||
|
app:layout_constraintEnd_toStartOf="@+id/captcha_layout_v2_reload_button" |
||||||
|
app:layout_constraintHorizontal_bias="0.5" |
||||||
|
app:layout_constraintStart_toEndOf="@+id/captcha_layout_v2_use_old_captcha_button" |
||||||
|
app:layout_constraintTop_toTopOf="@+id/captcha_layout_v2_verify_button" /> |
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatButton |
||||||
|
android:id="@+id/captcha_layout_v2_reload_button" |
||||||
|
android:layout_width="64dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:background="?android:attr/selectableItemBackground" |
||||||
|
android:clickable="true" |
||||||
|
android:focusable="true" |
||||||
|
android:text="@string/captcha_layout_v2_reload" |
||||||
|
android:textColor="#ffffff" |
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/captcha_layout_v2_verify_button" |
||||||
|
app:layout_constraintEnd_toStartOf="@+id/captcha_layout_v2_verify_button" |
||||||
|
app:layout_constraintHorizontal_bias="0.5" |
||||||
|
app:layout_constraintStart_toEndOf="@+id/captcha_layout_v2_refresh_cookies" |
||||||
|
app:layout_constraintTop_toTopOf="@+id/captcha_layout_v2_verify_button" /> |
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatButton |
||||||
|
android:id="@+id/captcha_layout_v2_verify_button" |
||||||
|
android:layout_width="80dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:background="?android:attr/selectableItemBackground" |
||||||
|
android:clickable="true" |
||||||
|
android:focusable="true" |
||||||
|
android:text="@string/captcha_layout_v2_verify_button_text" |
||||||
|
android:textColor="#ffffff" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
app:layout_constraintHorizontal_bias="0.5" |
||||||
|
app:layout_constraintStart_toEndOf="@+id/captcha_layout_v2_reload_button" |
||||||
|
app:layout_constraintTop_toTopOf="parent" /> |
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout> |
||||||
|
|
||||||
|
</LinearLayout> |
||||||
|
|
||||||
|
</ScrollView> |
@ -0,0 +1,106 @@ |
|||||||
|
package org.floens.chan.ui.captcha.v2; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
|
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.mockito.Mockito; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import okhttp3.OkHttpClient; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
|
||||||
|
|
||||||
|
public class CaptchaNoJsHtmlParserTest { |
||||||
|
private List<String> inputDataList = new ArrayList<>(); |
||||||
|
private List<List<Integer>> resultCheckboxList = new ArrayList<>(); |
||||||
|
private List<String> resultTitleList = new ArrayList<>(); |
||||||
|
private List<String> resultCParameterList = new ArrayList<>(); |
||||||
|
|
||||||
|
private Context contextMock = Mockito.mock(Context.class); |
||||||
|
private OkHttpClient okHttpClient = Mockito.mock(OkHttpClient.class); |
||||||
|
private CaptchaNoJsHtmlParser parser = new CaptchaNoJsHtmlParser(contextMock, okHttpClient); |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
inputDataList.add("<!DOCTYPE HTML><html dir=\"ltr\"><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>reCAPTCHA challenge</title><link rel=\"stylesheet\" href=\"https://www.gstatic.com/recaptcha/api2/v1557729121476/fallback__ltr.css\" type=\"text/css\" charset=\"utf-8\"></head><body><div class=\"fbc\"><div class=\"fbc-alert\"></div><div class=\"fbc-header\"><div class=\"fbc-logo\"><div class=\"fbc-logo-img\"></div><div class=\"fbc-logo-text\">reCAPTCHA</div></div><div class=\"fbc-imageselect-candidates\"></div><div class=\"fbc-imageselect-message-without-candidate-image\"><label for=\"response\" class=\"fbc-imageselect-message-text\"><div class=\"rc-imageselect-candidates\"><div class=\"rc-canonical-car\"></div></div><div class=\"rc-imageselect-desc\">Select all images with <strong>cars</strong></div></label></div></div><div><div class=\"fbc-imageselect-challenge\"><form method=\"POST\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLSABiot1uRaxXzd4_PRTTGbxW4AV8NOI947nBNVIBwdfOC2DwB0gdrPnfe8YYLI3kkW_tzwTJbQwZk0_nKaIsfXLaGWnnrEhvRMstWDzRin27w_Yalo6OS8Yr5sIqYBUfI496UtsoDGSL8NVQAbxjXocz_E7DTNXVBMfdLPx093-9vUHKH4yt4Mo6HuKSXmBPC2KNL4j_yl78YQ_VG1mprDwUkQeW-MQZxBOQb4MyhWZMOmNp_aRgHKGpJYHpcVMljDUvwZRk_xLw-NoJAWsgCIAt6pfrUE9jvHxjE4fep8OCH2T-6g-_Lla_UovuyEVIfUi1MQa6KWQMypsavA2mVUjTvIe_NkkO8fJ6gteHqUc7a9FTIHHs1OR5uV7fD0SCn9G5ii\"/><div class=\"fbc-payload-imageselect\"><input class=\"fbc-imageselect-checkbox-1\" type=\"checkbox\" name=\"response\" value=\"0\"><input class=\"fbc-imageselect-checkbox-2\" type=\"checkbox\" name=\"response\" value=\"1\"><input class=\"fbc-imageselect-checkbox-3\" type=\"checkbox\" name=\"response\" value=\"2\"><input class=\"fbc-imageselect-checkbox-4\" type=\"checkbox\" name=\"response\" value=\"3\"><input class=\"fbc-imageselect-checkbox-5\" type=\"checkbox\" name=\"response\" value=\"4\"><input class=\"fbc-imageselect-checkbox-6\" type=\"checkbox\" name=\"response\" value=\"5\"><input class=\"fbc-imageselect-checkbox-7\" type=\"checkbox\" name=\"response\" value=\"6\"><input class=\"fbc-imageselect-checkbox-8\" type=\"checkbox\" name=\"response\" value=\"7\"><input class=\"fbc-imageselect-checkbox-9\" type=\"checkbox\" name=\"response\" value=\"8\"><img class=\"fbc-imageselect-payload\" src=\"/recaptcha/api2/payload?c=03AOLTBLSABiot1uRaxXzd4_PRTTGbxW4AV8NOI947nBNVIBwdfOC2DwB0gdrPnfe8YYLI3kkW_tzwTJbQwZk0_nKaIsfXLaGWnnrEhvRMstWDzRin27w_Yalo6OS8Yr5sIqYBUfI496UtsoDGSL8NVQAbxjXocz_E7DTNXVBMfdLPx093-9vUHKH4yt4Mo6HuKSXmBPC2KNL4j_yl78YQ_VG1mprDwUkQeW-MQZxBOQb4MyhWZMOmNp_aRgHKGpJYHpcVMljDUvwZRk_xLw-NoJAWsgCIAt6pfrUE9jvHxjE4fep8OCH2T-6g-_Lla_UovuyEVIfUi1MQa6KWQMypsavA2mVUjTvIe_NkkO8fJ6gteHqUc7a9FTIHHs1OR5uV7fD0SCn9G5ii&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\" alt=\"reCAPTCHA challenge image\"/></div><div class=\"fbc-button-verify\"><input type=\"submit\" value=\"Verify\"/></div></form></div></div><div class=\"fbc-buttons\"><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLSABiot1uRaxXzd4_PRTTGbxW4AV8NOI947nBNVIBwdfOC2DwB0gdrPnfe8YYLI3kkW_tzwTJbQwZk0_nKaIsfXLaGWnnrEhvRMstWDzRin27w_Yalo6OS8Yr5sIqYBUfI496UtsoDGSL8NVQAbxjXocz_E7DTNXVBMfdLPx093-9vUHKH4yt4Mo6HuKSXmBPC2KNL4j_yl78YQ_VG1mprDwUkQeW-MQZxBOQb4MyhWZMOmNp_aRgHKGpJYHpcVMljDUvwZRk_xLw-NoJAWsgCIAt6pfrUE9jvHxjE4fep8OCH2T-6g-_Lla_UovuyEVIfUi1MQa6KWQMypsavA2mVUjTvIe_NkkO8fJ6gteHqUc7a9FTIHHs1OR5uV7fD0SCn9G5ii\"/><input type=\"hidden\" name=\"reason\" value=\"r\"/><input class=\"fbc-button-reload fbc-button\" type=\"submit\" value=\"Get a new challenge\"/></form></div><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLSABiot1uRaxXzd4_PRTTGbxW4AV8NOI947nBNVIBwdfOC2DwB0gdrPnfe8YYLI3kkW_tzwTJbQwZk0_nKaIsfXLaGWnnrEhvRMstWDzRin27w_Yalo6OS8Yr5sIqYBUfI496UtsoDGSL8NVQAbxjXocz_E7DTNXVBMfdLPx093-9vUHKH4yt4Mo6HuKSXmBPC2KNL4j_yl78YQ_VG1mprDwUkQeW-MQZxBOQb4MyhWZMOmNp_aRgHKGpJYHpcVMljDUvwZRk_xLw-NoJAWsgCIAt6pfrUE9jvHxjE4fep8OCH2T-6g-_Lla_UovuyEVIfUi1MQa6KWQMypsavA2mVUjTvIe_NkkO8fJ6gteHqUc7a9FTIHHs1OR5uV7fD0SCn9G5ii\"/><input type=\"hidden\" name=\"reason\" value=\"a\"/><input class=\"fbc-button-audio fbc-button\" type=\"submit\" value=\"Get an audio challenge\"/></form></div></div></div><div class=\"fbc-why-fallback\"><a href=\"https://support.google.com/recaptcha#6262736\" target=\"_blank\">Want an easier challenge?</a></div><div class=\"fbc-separator\"></div><div class=\"fbc-privacy\"><a href=\"https://www.google.com/intl/en/policies/privacy/\" target=\"_blank\">Privacy</a> - <a href=\"https://www.google.com/intl/en/policies/terms/\" target=\"_blank\">Terms</a></div></div><script nonce=\"qnaxKyOmAYxqk1TC5w5H7Q\">document.body.className += \" js-enabled\";</script></body></html>"); |
||||||
|
inputDataList.add("<!DOCTYPE HTML><html dir=\"ltr\"><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>reCAPTCHA challenge</title><link rel=\"stylesheet\" href=\"https://www.gstatic.com/recaptcha/api2/v1557729121476/fallback__ltr.css\" type=\"text/css\" charset=\"utf-8\"></head><body><div class=\"fbc\"><div class=\"fbc-alert\"></div><div class=\"fbc-header\"><div class=\"fbc-logo\"><div class=\"fbc-logo-img\"></div><div class=\"fbc-logo-text\">reCAPTCHA</div></div><div class=\"fbc-imageselect-candidates\"></div><div class=\"fbc-imageselect-message-without-candidate-image\"><label for=\"response\" class=\"fbc-imageselect-message-text\"><div class=\"rc-imageselect-desc-no-canonical\">Select all images with <strong>mountains or hills</strong>.</div></label></div></div><div><div class=\"fbc-imageselect-challenge\"><form method=\"POST\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLRv_ZKMPWAc0bBYTnC9wPIQElGB6H6SrifMnrj1yDeFnUPNpOPgGZHdaGPVzfieFHzQOh4GIKWUBPQ_aMs3gp0EJmgdP-LAGFBZIreVB-boqP1DyIblN-3ws1bxCyVvw4kSanc08rPVr7G2nwy7b0kkBV82x8ypU1QP1fQR1HxC8qUddQZSLvgmkFiRatbaOqMdsMPDwl1ENJVrzTkX0sQGmfqnf2aisqRbSsiH0BrW1_wTLPjiGv1SW-FrBYWKJZBWP_FanbTftUuhKLaYTjABDD2mcBg-7-QEpFbaFqi7cwSA4Y-SsCiJg7y0DBL8PjsDSxOqoY_Upc5I-0GI3X21KJLJMLxq9tRkWZbsyhHXNyFzzM_4PfOIiGErbTMzICa596q7\"/><div class=\"fbc-payload-imageselect\"><input class=\"fbc-imageselect-checkbox-1\" type=\"checkbox\" name=\"response\" value=\"0\"><input class=\"fbc-imageselect-checkbox-2\" type=\"checkbox\" name=\"response\" value=\"1\"><input class=\"fbc-imageselect-checkbox-3\" type=\"checkbox\" name=\"response\" value=\"2\"><input class=\"fbc-imageselect-checkbox-4\" type=\"checkbox\" name=\"response\" value=\"3\"><input class=\"fbc-imageselect-checkbox-5\" type=\"checkbox\" name=\"response\" value=\"4\"><input class=\"fbc-imageselect-checkbox-6\" type=\"checkbox\" name=\"response\" value=\"5\"><input class=\"fbc-imageselect-checkbox-7\" type=\"checkbox\" name=\"response\" value=\"6\"><input class=\"fbc-imageselect-checkbox-8\" type=\"checkbox\" name=\"response\" value=\"7\"><input class=\"fbc-imageselect-checkbox-9\" type=\"checkbox\" name=\"response\" value=\"8\"><img class=\"fbc-imageselect-payload\" src=\"/recaptcha/api2/payload?c=03AOLTBLRv_ZKMPWAc0bBYTnC9wPIQElGB6H6SrifMnrj1yDeFnUPNpOPgGZHdaGPVzfieFHzQOh4GIKWUBPQ_aMs3gp0EJmgdP-LAGFBZIreVB-boqP1DyIblN-3ws1bxCyVvw4kSanc08rPVr7G2nwy7b0kkBV82x8ypU1QP1fQR1HxC8qUddQZSLvgmkFiRatbaOqMdsMPDwl1ENJVrzTkX0sQGmfqnf2aisqRbSsiH0BrW1_wTLPjiGv1SW-FrBYWKJZBWP_FanbTftUuhKLaYTjABDD2mcBg-7-QEpFbaFqi7cwSA4Y-SsCiJg7y0DBL8PjsDSxOqoY_Upc5I-0GI3X21KJLJMLxq9tRkWZbsyhHXNyFzzM_4PfOIiGErbTMzICa596q7&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\" alt=\"reCAPTCHA challenge image\"/></div><div class=\"fbc-button-verify\"><input type=\"submit\" value=\"Verify\"/></div></form></div></div><div class=\"fbc-buttons\"><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLRv_ZKMPWAc0bBYTnC9wPIQElGB6H6SrifMnrj1yDeFnUPNpOPgGZHdaGPVzfieFHzQOh4GIKWUBPQ_aMs3gp0EJmgdP-LAGFBZIreVB-boqP1DyIblN-3ws1bxCyVvw4kSanc08rPVr7G2nwy7b0kkBV82x8ypU1QP1fQR1HxC8qUddQZSLvgmkFiRatbaOqMdsMPDwl1ENJVrzTkX0sQGmfqnf2aisqRbSsiH0BrW1_wTLPjiGv1SW-FrBYWKJZBWP_FanbTftUuhKLaYTjABDD2mcBg-7-QEpFbaFqi7cwSA4Y-SsCiJg7y0DBL8PjsDSxOqoY_Upc5I-0GI3X21KJLJMLxq9tRkWZbsyhHXNyFzzM_4PfOIiGErbTMzICa596q7\"/><input type=\"hidden\" name=\"reason\" value=\"r\"/><input class=\"fbc-button-reload fbc-button\" type=\"submit\" value=\"Get a new challenge\"/></form></div><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLRv_ZKMPWAc0bBYTnC9wPIQElGB6H6SrifMnrj1yDeFnUPNpOPgGZHdaGPVzfieFHzQOh4GIKWUBPQ_aMs3gp0EJmgdP-LAGFBZIreVB-boqP1DyIblN-3ws1bxCyVvw4kSanc08rPVr7G2nwy7b0kkBV82x8ypU1QP1fQR1HxC8qUddQZSLvgmkFiRatbaOqMdsMPDwl1ENJVrzTkX0sQGmfqnf2aisqRbSsiH0BrW1_wTLPjiGv1SW-FrBYWKJZBWP_FanbTftUuhKLaYTjABDD2mcBg-7-QEpFbaFqi7cwSA4Y-SsCiJg7y0DBL8PjsDSxOqoY_Upc5I-0GI3X21KJLJMLxq9tRkWZbsyhHXNyFzzM_4PfOIiGErbTMzICa596q7\"/><input type=\"hidden\" name=\"reason\" value=\"a\"/><input class=\"fbc-button-audio fbc-button\" type=\"submit\" value=\"Get an audio challenge\"/></form></div></div></div><div class=\"fbc-why-fallback\"><a href=\"https://support.google.com/recaptcha#6262736\" target=\"_blank\">Want an easier challenge?</a></div><div class=\"fbc-separator\"></div><div class=\"fbc-privacy\"><a href=\"https://www.google.com/intl/en/policies/privacy/\" target=\"_blank\">Privacy</a> - <a href=\"https://www.google.com/intl/en/policies/terms/\" target=\"_blank\">Terms</a></div></div><script nonce=\"9mPanFXMj8uH+hihgCN2rQ\">document.body.className += \" js-enabled\";</script></body></html>"); |
||||||
|
inputDataList.add("<!DOCTYPE HTML><html dir=\"ltr\"><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>reCAPTCHA challenge</title><link rel=\"stylesheet\" href=\"https://www.gstatic.com/recaptcha/api2/v1557729121476/fallback__ltr.css\" type=\"text/css\" charset=\"utf-8\"></head><body><div class=\"fbc\"><div class=\"fbc-alert\"></div><div class=\"fbc-header\"><div class=\"fbc-logo\"><div class=\"fbc-logo-img\"></div><div class=\"fbc-logo-text\">reCAPTCHA</div></div><div class=\"fbc-imageselect-candidates\"></div><div class=\"fbc-imageselect-message-without-candidate-image\"><label for=\"response\" class=\"fbc-imageselect-message-text\"><div class=\"rc-imageselect-desc-no-canonical\">Select all images with <strong>crosswalks</strong>.</div></label></div></div><div><div class=\"fbc-imageselect-challenge\"><form method=\"POST\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLQdQO0rHKgYInXnNyPho7cEllKtboAokZsqQPHyE5VkAEXwvkvin540F_CAPIA872WSFqdlShtJvERZPgOP_DmuO4lAF7CxIRDeRzyMd2kKF-ivS3XADiISevh50q1qtolKOW6nhdktgo4JxW3nej539yRZxJ4X6SRde_29QsGgqtTUUB82CYWZ2vOa9yclvuMMLPTFfIIRQHy4DNQXqVOEKV0PfvBatjLhq4ekuYgEoHbni97ePTmjvfsL0gy6CR2SAmOf6m7oJm2snVyBBgxWfMnPue6wypmHco7JRLcEsRHOvUDBLa6rMT5p7fiRP3sxPQNcv_H6prK_EAtSPOB29SELUlf7G72UlcxBNOaKxsOY_xzyFHnyFPRTERh2xc7FEmDF\"/><div class=\"fbc-payload-imageselect\"><input class=\"fbc-imageselect-checkbox-1\" type=\"checkbox\" name=\"response\" value=\"0\"><input class=\"fbc-imageselect-checkbox-2\" type=\"checkbox\" name=\"response\" value=\"1\"><input class=\"fbc-imageselect-checkbox-3\" type=\"checkbox\" name=\"response\" value=\"2\"><input class=\"fbc-imageselect-checkbox-4\" type=\"checkbox\" name=\"response\" value=\"3\"><input class=\"fbc-imageselect-checkbox-5\" type=\"checkbox\" name=\"response\" value=\"4\"><input class=\"fbc-imageselect-checkbox-6\" type=\"checkbox\" name=\"response\" value=\"5\"><input class=\"fbc-imageselect-checkbox-7\" type=\"checkbox\" name=\"response\" value=\"6\"><input class=\"fbc-imageselect-checkbox-8\" type=\"checkbox\" name=\"response\" value=\"7\"><input class=\"fbc-imageselect-checkbox-9\" type=\"checkbox\" name=\"response\" value=\"8\"><img class=\"fbc-imageselect-payload\" src=\"/recaptcha/api2/payload?c=03AOLTBLQdQO0rHKgYInXnNyPho7cEllKtboAokZsqQPHyE5VkAEXwvkvin540F_CAPIA872WSFqdlShtJvERZPgOP_DmuO4lAF7CxIRDeRzyMd2kKF-ivS3XADiISevh50q1qtolKOW6nhdktgo4JxW3nej539yRZxJ4X6SRde_29QsGgqtTUUB82CYWZ2vOa9yclvuMMLPTFfIIRQHy4DNQXqVOEKV0PfvBatjLhq4ekuYgEoHbni97ePTmjvfsL0gy6CR2SAmOf6m7oJm2snVyBBgxWfMnPue6wypmHco7JRLcEsRHOvUDBLa6rMT5p7fiRP3sxPQNcv_H6prK_EAtSPOB29SELUlf7G72UlcxBNOaKxsOY_xzyFHnyFPRTERh2xc7FEmDF&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\" alt=\"reCAPTCHA challenge image\"/></div><div class=\"fbc-button-verify\"><input type=\"submit\" value=\"Verify\"/></div></form></div></div><div class=\"fbc-buttons\"><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLQdQO0rHKgYInXnNyPho7cEllKtboAokZsqQPHyE5VkAEXwvkvin540F_CAPIA872WSFqdlShtJvERZPgOP_DmuO4lAF7CxIRDeRzyMd2kKF-ivS3XADiISevh50q1qtolKOW6nhdktgo4JxW3nej539yRZxJ4X6SRde_29QsGgqtTUUB82CYWZ2vOa9yclvuMMLPTFfIIRQHy4DNQXqVOEKV0PfvBatjLhq4ekuYgEoHbni97ePTmjvfsL0gy6CR2SAmOf6m7oJm2snVyBBgxWfMnPue6wypmHco7JRLcEsRHOvUDBLa6rMT5p7fiRP3sxPQNcv_H6prK_EAtSPOB29SELUlf7G72UlcxBNOaKxsOY_xzyFHnyFPRTERh2xc7FEmDF\"/><input type=\"hidden\" name=\"reason\" value=\"r\"/><input class=\"fbc-button-reload fbc-button\" type=\"submit\" value=\"Get a new challenge\"/></form></div><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLQdQO0rHKgYInXnNyPho7cEllKtboAokZsqQPHyE5VkAEXwvkvin540F_CAPIA872WSFqdlShtJvERZPgOP_DmuO4lAF7CxIRDeRzyMd2kKF-ivS3XADiISevh50q1qtolKOW6nhdktgo4JxW3nej539yRZxJ4X6SRde_29QsGgqtTUUB82CYWZ2vOa9yclvuMMLPTFfIIRQHy4DNQXqVOEKV0PfvBatjLhq4ekuYgEoHbni97ePTmjvfsL0gy6CR2SAmOf6m7oJm2snVyBBgxWfMnPue6wypmHco7JRLcEsRHOvUDBLa6rMT5p7fiRP3sxPQNcv_H6prK_EAtSPOB29SELUlf7G72UlcxBNOaKxsOY_xzyFHnyFPRTERh2xc7FEmDF\"/><input type=\"hidden\" name=\"reason\" value=\"a\"/><input class=\"fbc-button-audio fbc-button\" type=\"submit\" value=\"Get an audio challenge\"/></form></div></div></div><div class=\"fbc-why-fallback\"><a href=\"https://support.google.com/recaptcha#6262736\" target=\"_blank\">Want an easier challenge?</a></div><div class=\"fbc-separator\"></div><div class=\"fbc-privacy\"><a href=\"https://www.google.com/intl/en/policies/privacy/\" target=\"_blank\">Privacy</a> - <a href=\"https://www.google.com/intl/en/policies/terms/\" target=\"_blank\">Terms</a></div></div><script nonce=\"FBv2o/5cAreDSNeyDhdNxQ\">document.body.className += \" js-enabled\";</script></body></html>"); |
||||||
|
inputDataList.add("<!DOCTYPE HTML><html dir=\"ltr\"><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>reCAPTCHA challenge</title><link rel=\"stylesheet\" href=\"https://www.gstatic.com/recaptcha/api2/v1557729121476/fallback__ltr.css\" type=\"text/css\" charset=\"utf-8\"></head><body><div class=\"fbc\"><div class=\"fbc-alert\"></div><div class=\"fbc-header\"><div class=\"fbc-logo\"><div class=\"fbc-logo-img\"></div><div class=\"fbc-logo-text\">reCAPTCHA</div></div><div class=\"fbc-imageselect-candidates\"></div><div class=\"fbc-imageselect-message-without-candidate-image\"><label for=\"response\" class=\"fbc-imageselect-message-text\"><div class=\"rc-imageselect-candidates\"><div class=\"rc-canonical-bridge\"></div></div><div class=\"rc-imageselect-desc\">Select all images with <strong>bridges</strong>.</div></label></div></div><div><div class=\"fbc-imageselect-challenge\"><form method=\"POST\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLTkdQd0zGmSjFw9OCiKz18VT-h4lleJaBwtNMSDPFsUSR_9qepA1uCUW_FNAju-tgYzTOTjmjf0wWmvfLlwKCxEsyMKfYobnJXXYcOHyAwaOJRtEOAwn2SSmVEzlE1DYZ89lFH7XB5rw6tGpSJRJQkvitoVK-aLg_gzmYxbYJPH_VK9ofkfeGfhnZ6bsXoz1vnnhHYOXm7apmKjDEk6NIxKqB_FHDFclemX8rr7liJBaTcJ98I20a9wXGinBUEEyupoQ5gdnoYqLMu_XIQaRYtdmRsCgtm9RorpkJZxaqEpqfusfpFZUntIXkXF31dmx0hL-B5DpA4uFOBI6UmR2oPLAA2LhStMgKtlnl1IYwyrmaeYUybV0rPl0F5jMAr3GZxEa7Ld\"/><div class=\"fbc-payload-imageselect\"><input class=\"fbc-imageselect-checkbox-1\" type=\"checkbox\" name=\"response\" value=\"0\"><input class=\"fbc-imageselect-checkbox-2\" type=\"checkbox\" name=\"response\" value=\"1\"><input class=\"fbc-imageselect-checkbox-3\" type=\"checkbox\" name=\"response\" value=\"2\"><input class=\"fbc-imageselect-checkbox-4\" type=\"checkbox\" name=\"response\" value=\"3\"><input class=\"fbc-imageselect-checkbox-5\" type=\"checkbox\" name=\"response\" value=\"4\"><input class=\"fbc-imageselect-checkbox-6\" type=\"checkbox\" name=\"response\" value=\"5\"><input class=\"fbc-imageselect-checkbox-7\" type=\"checkbox\" name=\"response\" value=\"6\"><input class=\"fbc-imageselect-checkbox-8\" type=\"checkbox\" name=\"response\" value=\"7\"><input class=\"fbc-imageselect-checkbox-9\" type=\"checkbox\" name=\"response\" value=\"8\"><img class=\"fbc-imageselect-payload\" src=\"/recaptcha/api2/payload?c=03AOLTBLTkdQd0zGmSjFw9OCiKz18VT-h4lleJaBwtNMSDPFsUSR_9qepA1uCUW_FNAju-tgYzTOTjmjf0wWmvfLlwKCxEsyMKfYobnJXXYcOHyAwaOJRtEOAwn2SSmVEzlE1DYZ89lFH7XB5rw6tGpSJRJQkvitoVK-aLg_gzmYxbYJPH_VK9ofkfeGfhnZ6bsXoz1vnnhHYOXm7apmKjDEk6NIxKqB_FHDFclemX8rr7liJBaTcJ98I20a9wXGinBUEEyupoQ5gdnoYqLMu_XIQaRYtdmRsCgtm9RorpkJZxaqEpqfusfpFZUntIXkXF31dmx0hL-B5DpA4uFOBI6UmR2oPLAA2LhStMgKtlnl1IYwyrmaeYUybV0rPl0F5jMAr3GZxEa7Ld&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\" alt=\"reCAPTCHA challenge image\"/></div><div class=\"fbc-button-verify\"><input type=\"submit\" value=\"Verify\"/></div></form></div></div><div class=\"fbc-buttons\"><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLTkdQd0zGmSjFw9OCiKz18VT-h4lleJaBwtNMSDPFsUSR_9qepA1uCUW_FNAju-tgYzTOTjmjf0wWmvfLlwKCxEsyMKfYobnJXXYcOHyAwaOJRtEOAwn2SSmVEzlE1DYZ89lFH7XB5rw6tGpSJRJQkvitoVK-aLg_gzmYxbYJPH_VK9ofkfeGfhnZ6bsXoz1vnnhHYOXm7apmKjDEk6NIxKqB_FHDFclemX8rr7liJBaTcJ98I20a9wXGinBUEEyupoQ5gdnoYqLMu_XIQaRYtdmRsCgtm9RorpkJZxaqEpqfusfpFZUntIXkXF31dmx0hL-B5DpA4uFOBI6UmR2oPLAA2LhStMgKtlnl1IYwyrmaeYUybV0rPl0F5jMAr3GZxEa7Ld\"/><input type=\"hidden\" name=\"reason\" value=\"r\"/><input class=\"fbc-button-reload fbc-button\" type=\"submit\" value=\"Get a new challenge\"/></form></div><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLTkdQd0zGmSjFw9OCiKz18VT-h4lleJaBwtNMSDPFsUSR_9qepA1uCUW_FNAju-tgYzTOTjmjf0wWmvfLlwKCxEsyMKfYobnJXXYcOHyAwaOJRtEOAwn2SSmVEzlE1DYZ89lFH7XB5rw6tGpSJRJQkvitoVK-aLg_gzmYxbYJPH_VK9ofkfeGfhnZ6bsXoz1vnnhHYOXm7apmKjDEk6NIxKqB_FHDFclemX8rr7liJBaTcJ98I20a9wXGinBUEEyupoQ5gdnoYqLMu_XIQaRYtdmRsCgtm9RorpkJZxaqEpqfusfpFZUntIXkXF31dmx0hL-B5DpA4uFOBI6UmR2oPLAA2LhStMgKtlnl1IYwyrmaeYUybV0rPl0F5jMAr3GZxEa7Ld\"/><input type=\"hidden\" name=\"reason\" value=\"a\"/><input class=\"fbc-button-audio fbc-button\" type=\"submit\" value=\"Get an audio challenge\"/></form></div></div></div><div class=\"fbc-why-fallback\"><a href=\"https://support.google.com/recaptcha#6262736\" target=\"_blank\">Want an easier challenge?</a></div><div class=\"fbc-separator\"></div><div class=\"fbc-privacy\"><a href=\"https://www.google.com/intl/en/policies/privacy/\" target=\"_blank\">Privacy</a> - <a href=\"https://www.google.com/intl/en/policies/terms/\" target=\"_blank\">Terms</a></div></div><script nonce=\"/dAVLmM2x00DwplNUqTwoA\">document.body.className += \" js-enabled\";</script></body></html>"); |
||||||
|
inputDataList.add("<!DOCTYPE HTML><html dir=\"ltr\"><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>reCAPTCHA challenge</title><link rel=\"stylesheet\" href=\"https://www.gstatic.com/recaptcha/api2/v1557729121476/fallback__ltr.css\" type=\"text/css\" charset=\"utf-8\"></head><body><div class=\"fbc\"><div class=\"fbc-alert\"></div><div class=\"fbc-header\"><div class=\"fbc-logo\"><div class=\"fbc-logo-img\"></div><div class=\"fbc-logo-text\">reCAPTCHA</div></div><div class=\"fbc-imageselect-candidates\"></div><div class=\"fbc-imageselect-message-without-candidate-image\"><label for=\"response\" class=\"fbc-imageselect-message-text\"><div class=\"rc-imageselect-desc-no-canonical\">Select all images with <strong>bicycles</strong></div></label></div></div><div><div class=\"fbc-imageselect-challenge\"><form method=\"POST\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLTAhnX3O2lESLZYL9y7w8txPisktNhNpI-h3hZpULhz_-yMEId_7-NW8RGCMbZ6_W8CRH6LnL7Icxdw3nISINh3DGcldAmF8jojjG17gPIVvCCk1WdKfgYSkC_oAThQDESoAOz-VGAahHroLUNSnCm1tHmExy5tRX3EGi0xO3ep-1iMSGuMb_aoo3hxIuEZE4_J37RGoE--t2yoU4pKDxfbnHNYYfl48KgIb_qBzUt9O-gOF5P3oMM7G5HpMHjq3lraD0Ep19tiq5c_P-JO21V7QiJBB8XiwqgIHsEwGaLsm3J2NdQjLHot1xy8_A8x04ZTqHw-d_LnpBOTz8Jry5ym2K-zfkqkK1VtbJUnayZ-i-e5_ErxE3HTesMtzJ6I-ZBu2h8T\"/><div class=\"fbc-payload-imageselect\"><input class=\"fbc-imageselect-checkbox-1\" type=\"checkbox\" name=\"response\" value=\"0\"><input class=\"fbc-imageselect-checkbox-2\" type=\"checkbox\" name=\"response\" value=\"1\"><input class=\"fbc-imageselect-checkbox-3\" type=\"checkbox\" name=\"response\" value=\"2\"><input class=\"fbc-imageselect-checkbox-4\" type=\"checkbox\" name=\"response\" value=\"3\"><input class=\"fbc-imageselect-checkbox-5\" type=\"checkbox\" name=\"response\" value=\"4\"><input class=\"fbc-imageselect-checkbox-6\" type=\"checkbox\" name=\"response\" value=\"5\"><input class=\"fbc-imageselect-checkbox-7\" type=\"checkbox\" name=\"response\" value=\"6\"><input class=\"fbc-imageselect-checkbox-8\" type=\"checkbox\" name=\"response\" value=\"7\"><input class=\"fbc-imageselect-checkbox-9\" type=\"checkbox\" name=\"response\" value=\"8\"><img class=\"fbc-imageselect-payload\" src=\"/recaptcha/api2/payload?c=03AOLTBLTAhnX3O2lESLZYL9y7w8txPisktNhNpI-h3hZpULhz_-yMEId_7-NW8RGCMbZ6_W8CRH6LnL7Icxdw3nISINh3DGcldAmF8jojjG17gPIVvCCk1WdKfgYSkC_oAThQDESoAOz-VGAahHroLUNSnCm1tHmExy5tRX3EGi0xO3ep-1iMSGuMb_aoo3hxIuEZE4_J37RGoE--t2yoU4pKDxfbnHNYYfl48KgIb_qBzUt9O-gOF5P3oMM7G5HpMHjq3lraD0Ep19tiq5c_P-JO21V7QiJBB8XiwqgIHsEwGaLsm3J2NdQjLHot1xy8_A8x04ZTqHw-d_LnpBOTz8Jry5ym2K-zfkqkK1VtbJUnayZ-i-e5_ErxE3HTesMtzJ6I-ZBu2h8T&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\" alt=\"reCAPTCHA challenge image\"/></div><div class=\"fbc-button-verify\"><input type=\"submit\" value=\"Verify\"/></div></form></div></div><div class=\"fbc-buttons\"><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLTAhnX3O2lESLZYL9y7w8txPisktNhNpI-h3hZpULhz_-yMEId_7-NW8RGCMbZ6_W8CRH6LnL7Icxdw3nISINh3DGcldAmF8jojjG17gPIVvCCk1WdKfgYSkC_oAThQDESoAOz-VGAahHroLUNSnCm1tHmExy5tRX3EGi0xO3ep-1iMSGuMb_aoo3hxIuEZE4_J37RGoE--t2yoU4pKDxfbnHNYYfl48KgIb_qBzUt9O-gOF5P3oMM7G5HpMHjq3lraD0Ep19tiq5c_P-JO21V7QiJBB8XiwqgIHsEwGaLsm3J2NdQjLHot1xy8_A8x04ZTqHw-d_LnpBOTz8Jry5ym2K-zfkqkK1VtbJUnayZ-i-e5_ErxE3HTesMtzJ6I-ZBu2h8T\"/><input type=\"hidden\" name=\"reason\" value=\"r\"/><input class=\"fbc-button-reload fbc-button\" type=\"submit\" value=\"Get a new challenge\"/></form></div><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLTAhnX3O2lESLZYL9y7w8txPisktNhNpI-h3hZpULhz_-yMEId_7-NW8RGCMbZ6_W8CRH6LnL7Icxdw3nISINh3DGcldAmF8jojjG17gPIVvCCk1WdKfgYSkC_oAThQDESoAOz-VGAahHroLUNSnCm1tHmExy5tRX3EGi0xO3ep-1iMSGuMb_aoo3hxIuEZE4_J37RGoE--t2yoU4pKDxfbnHNYYfl48KgIb_qBzUt9O-gOF5P3oMM7G5HpMHjq3lraD0Ep19tiq5c_P-JO21V7QiJBB8XiwqgIHsEwGaLsm3J2NdQjLHot1xy8_A8x04ZTqHw-d_LnpBOTz8Jry5ym2K-zfkqkK1VtbJUnayZ-i-e5_ErxE3HTesMtzJ6I-ZBu2h8T\"/><input type=\"hidden\" name=\"reason\" value=\"a\"/><input class=\"fbc-button-audio fbc-button\" type=\"submit\" value=\"Get an audio challenge\"/></form></div></div></div><div class=\"fbc-why-fallback\"><a href=\"https://support.google.com/recaptcha#6262736\" target=\"_blank\">Want an easier challenge?</a></div><div class=\"fbc-separator\"></div><div class=\"fbc-privacy\"><a href=\"https://www.google.com/intl/en/policies/privacy/\" target=\"_blank\">Privacy</a> - <a href=\"https://www.google.com/intl/en/policies/terms/\" target=\"_blank\">Terms</a></div></div><script nonce=\"qBUhkppTt1rZFbcmcQcqhw\">document.body.className += \" js-enabled\";</script></body></html>"); |
||||||
|
inputDataList.add("<!DOCTYPE HTML><html dir=\"ltr\"><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>reCAPTCHA challenge</title><link rel=\"stylesheet\" href=\"https://www.gstatic.com/recaptcha/api2/v1557729121476/fallback__ltr.css\" type=\"text/css\" charset=\"utf-8\"></head><body><div class=\"fbc\"><div class=\"fbc-alert\"></div><div class=\"fbc-header\"><div class=\"fbc-logo\"><div class=\"fbc-logo-img\"></div><div class=\"fbc-logo-text\">reCAPTCHA</div></div><div class=\"fbc-imageselect-candidates\"></div><div class=\"fbc-imageselect-message-without-candidate-image\"><label for=\"response\" class=\"fbc-imageselect-message-text\"><div class=\"rc-imageselect-candidates\"><div class=\"rc-canonical-car\"></div></div><div class=\"rc-imageselect-desc\">Select all images with <strong>cars</strong></div></label></div></div><div><div class=\"fbc-imageselect-challenge\"><form method=\"POST\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLSu6KJ0tMzrENsXJM2H5K8GDF_2PZOEYKew4UxN49JDmoXnrlOFLYbETkJfXyG-kXWdmiTIg3XgR8vVfIf8J4oR1DpEyi9HcPaV9kLNeq-0aK3P1Vw62wBm3OOhU_zeP49kiwmy4icv4ro9NQjfBxw1scZgYsj_R41sBDvlLkofoDMrWNzsAjwFAM2pmbRGKf6qThzQ6ijSAjHBAD_oIK4E4m7kgeUZyEvw9-2MN_8AJoGXtI_74LdvbkngnF1qqK5gIKYX05AyaI39M1-TXfT5Gq2ePu8XMKUR709pZPc22Pw9lni0a1I-UyfGMJJ1u0Pvh0HKO6atPREGFcXwvhOQsSHakAGuzHUE8V5ywUqpW3mNFE0xly-GK07BbGMicQZ-7F-9\"/><div class=\"fbc-payload-imageselect\"><input class=\"fbc-imageselect-checkbox-1\" type=\"checkbox\" name=\"response\" value=\"0\"><input class=\"fbc-imageselect-checkbox-2\" type=\"checkbox\" name=\"response\" value=\"1\"><input class=\"fbc-imageselect-checkbox-3\" type=\"checkbox\" name=\"response\" value=\"2\"><input class=\"fbc-imageselect-checkbox-4\" type=\"checkbox\" name=\"response\" value=\"3\"><input class=\"fbc-imageselect-checkbox-5\" type=\"checkbox\" name=\"response\" value=\"4\"><input class=\"fbc-imageselect-checkbox-6\" type=\"checkbox\" name=\"response\" value=\"5\"><input class=\"fbc-imageselect-checkbox-7\" type=\"checkbox\" name=\"response\" value=\"6\"><input class=\"fbc-imageselect-checkbox-8\" type=\"checkbox\" name=\"response\" value=\"7\"><input class=\"fbc-imageselect-checkbox-9\" type=\"checkbox\" name=\"response\" value=\"8\"><img class=\"fbc-imageselect-payload\" src=\"/recaptcha/api2/payload?c=03AOLTBLSu6KJ0tMzrENsXJM2H5K8GDF_2PZOEYKew4UxN49JDmoXnrlOFLYbETkJfXyG-kXWdmiTIg3XgR8vVfIf8J4oR1DpEyi9HcPaV9kLNeq-0aK3P1Vw62wBm3OOhU_zeP49kiwmy4icv4ro9NQjfBxw1scZgYsj_R41sBDvlLkofoDMrWNzsAjwFAM2pmbRGKf6qThzQ6ijSAjHBAD_oIK4E4m7kgeUZyEvw9-2MN_8AJoGXtI_74LdvbkngnF1qqK5gIKYX05AyaI39M1-TXfT5Gq2ePu8XMKUR709pZPc22Pw9lni0a1I-UyfGMJJ1u0Pvh0HKO6atPREGFcXwvhOQsSHakAGuzHUE8V5ywUqpW3mNFE0xly-GK07BbGMicQZ-7F-9&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\" alt=\"reCAPTCHA challenge image\"/></div><div class=\"fbc-button-verify\"><input type=\"submit\" value=\"Verify\"/></div></form></div></div><div class=\"fbc-buttons\"><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLSu6KJ0tMzrENsXJM2H5K8GDF_2PZOEYKew4UxN49JDmoXnrlOFLYbETkJfXyG-kXWdmiTIg3XgR8vVfIf8J4oR1DpEyi9HcPaV9kLNeq-0aK3P1Vw62wBm3OOhU_zeP49kiwmy4icv4ro9NQjfBxw1scZgYsj_R41sBDvlLkofoDMrWNzsAjwFAM2pmbRGKf6qThzQ6ijSAjHBAD_oIK4E4m7kgeUZyEvw9-2MN_8AJoGXtI_74LdvbkngnF1qqK5gIKYX05AyaI39M1-TXfT5Gq2ePu8XMKUR709pZPc22Pw9lni0a1I-UyfGMJJ1u0Pvh0HKO6atPREGFcXwvhOQsSHakAGuzHUE8V5ywUqpW3mNFE0xly-GK07BbGMicQZ-7F-9\"/><input type=\"hidden\" name=\"reason\" value=\"r\"/><input class=\"fbc-button-reload fbc-button\" type=\"submit\" value=\"Get a new challenge\"/></form></div><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLSu6KJ0tMzrENsXJM2H5K8GDF_2PZOEYKew4UxN49JDmoXnrlOFLYbETkJfXyG-kXWdmiTIg3XgR8vVfIf8J4oR1DpEyi9HcPaV9kLNeq-0aK3P1Vw62wBm3OOhU_zeP49kiwmy4icv4ro9NQjfBxw1scZgYsj_R41sBDvlLkofoDMrWNzsAjwFAM2pmbRGKf6qThzQ6ijSAjHBAD_oIK4E4m7kgeUZyEvw9-2MN_8AJoGXtI_74LdvbkngnF1qqK5gIKYX05AyaI39M1-TXfT5Gq2ePu8XMKUR709pZPc22Pw9lni0a1I-UyfGMJJ1u0Pvh0HKO6atPREGFcXwvhOQsSHakAGuzHUE8V5ywUqpW3mNFE0xly-GK07BbGMicQZ-7F-9\"/><input type=\"hidden\" name=\"reason\" value=\"a\"/><input class=\"fbc-button-audio fbc-button\" type=\"submit\" value=\"Get an audio challenge\"/></form></div></div></div><div class=\"fbc-why-fallback\"><a href=\"https://support.google.com/recaptcha#6262736\" target=\"_blank\">Want an easier challenge?</a></div><div class=\"fbc-separator\"></div><div class=\"fbc-privacy\"><a href=\"https://www.google.com/intl/en/policies/privacy/\" target=\"_blank\">Privacy</a> - <a href=\"https://www.google.com/intl/en/policies/terms/\" target=\"_blank\">Terms</a></div></div><script nonce=\"/dM7smPRYX0+FKHz5eMxYw\">document.body.className += \" js-enabled\";</script></body></html>"); |
||||||
|
inputDataList.add("<!DOCTYPE HTML><html dir=\"ltr\"><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>reCAPTCHA challenge</title><link rel=\"stylesheet\" href=\"https://www.gstatic.com/recaptcha/api2/v1557729121476/fallback__ltr.css\" type=\"text/css\" charset=\"utf-8\"></head><body><div class=\"fbc\"><div class=\"fbc-alert\"></div><div class=\"fbc-header\"><div class=\"fbc-logo\"><div class=\"fbc-logo-img\"></div><div class=\"fbc-logo-text\">reCAPTCHA</div></div><div class=\"fbc-imageselect-candidates\"></div><div class=\"fbc-imageselect-message-without-candidate-image\"><label for=\"response\" class=\"fbc-imageselect-message-text\"><div class=\"rc-imageselect-desc-no-canonical\">Select all images with a <strong>bus</strong>.</div></label></div></div><div><div class=\"fbc-imageselect-challenge\"><form method=\"POST\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLSe4m_kEnKDI7Zg9oYjJ28Y1XKwS7uZ10NlWRoq-2e5tLG3r47ClAG3F_pTLqQC_krxnHkt8UBIV5EfW5goPvQQQN2W8ll3YYiMjWM_ge1_Z9lk_7M5KCriFBFhmPMB1MtMfeXS5LpcekGDSq9J2O20zySzNHXZ7xkvx5vJ327ioNoDEizwInEaLMG5tMqaWthqZE_-DZlpI7n3v35Glt3hNuT_utgSlBBqt7yipMuy-dunICsXT3Oxojeb-zcvPk3SFsWyMcTI-354eV1PfA-7q0mOGIADYuavzciBKjfZEHrsR9su48sHj_AH8RG4b_8yBzmR7WAj7Va-EhZzxDnlGdfBVqUo5wVUyJxlZe6NWkNkBwiaisxhC8As0VJIY0u_ugms\"/><div class=\"fbc-payload-imageselect\"><input class=\"fbc-imageselect-checkbox-1\" type=\"checkbox\" name=\"response\" value=\"0\"><input class=\"fbc-imageselect-checkbox-2\" type=\"checkbox\" name=\"response\" value=\"1\"><input class=\"fbc-imageselect-checkbox-3\" type=\"checkbox\" name=\"response\" value=\"2\"><input class=\"fbc-imageselect-checkbox-4\" type=\"checkbox\" name=\"response\" value=\"3\"><input class=\"fbc-imageselect-checkbox-5\" type=\"checkbox\" name=\"response\" value=\"4\"><input class=\"fbc-imageselect-checkbox-6\" type=\"checkbox\" name=\"response\" value=\"5\"><input class=\"fbc-imageselect-checkbox-7\" type=\"checkbox\" name=\"response\" value=\"6\"><input class=\"fbc-imageselect-checkbox-8\" type=\"checkbox\" name=\"response\" value=\"7\"><input class=\"fbc-imageselect-checkbox-9\" type=\"checkbox\" name=\"response\" value=\"8\"><img class=\"fbc-imageselect-payload\" src=\"/recaptcha/api2/payload?c=03AOLTBLSe4m_kEnKDI7Zg9oYjJ28Y1XKwS7uZ10NlWRoq-2e5tLG3r47ClAG3F_pTLqQC_krxnHkt8UBIV5EfW5goPvQQQN2W8ll3YYiMjWM_ge1_Z9lk_7M5KCriFBFhmPMB1MtMfeXS5LpcekGDSq9J2O20zySzNHXZ7xkvx5vJ327ioNoDEizwInEaLMG5tMqaWthqZE_-DZlpI7n3v35Glt3hNuT_utgSlBBqt7yipMuy-dunICsXT3Oxojeb-zcvPk3SFsWyMcTI-354eV1PfA-7q0mOGIADYuavzciBKjfZEHrsR9su48sHj_AH8RG4b_8yBzmR7WAj7Va-EhZzxDnlGdfBVqUo5wVUyJxlZe6NWkNkBwiaisxhC8As0VJIY0u_ugms&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\" alt=\"reCAPTCHA challenge image\"/></div><div class=\"fbc-button-verify\"><input type=\"submit\" value=\"Verify\"/></div></form></div></div><div class=\"fbc-buttons\"><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLSe4m_kEnKDI7Zg9oYjJ28Y1XKwS7uZ10NlWRoq-2e5tLG3r47ClAG3F_pTLqQC_krxnHkt8UBIV5EfW5goPvQQQN2W8ll3YYiMjWM_ge1_Z9lk_7M5KCriFBFhmPMB1MtMfeXS5LpcekGDSq9J2O20zySzNHXZ7xkvx5vJ327ioNoDEizwInEaLMG5tMqaWthqZE_-DZlpI7n3v35Glt3hNuT_utgSlBBqt7yipMuy-dunICsXT3Oxojeb-zcvPk3SFsWyMcTI-354eV1PfA-7q0mOGIADYuavzciBKjfZEHrsR9su48sHj_AH8RG4b_8yBzmR7WAj7Va-EhZzxDnlGdfBVqUo5wVUyJxlZe6NWkNkBwiaisxhC8As0VJIY0u_ugms\"/><input type=\"hidden\" name=\"reason\" value=\"r\"/><input class=\"fbc-button-reload fbc-button\" type=\"submit\" value=\"Get a new challenge\"/></form></div><div class=\"fbc-button-holder\"><form method=\"POST\" tabindex=\"-1\"><input type=\"hidden\" name=\"c\" value=\"03AOLTBLSe4m_kEnKDI7Zg9oYjJ28Y1XKwS7uZ10NlWRoq-2e5tLG3r47ClAG3F_pTLqQC_krxnHkt8UBIV5EfW5goPvQQQN2W8ll3YYiMjWM_ge1_Z9lk_7M5KCriFBFhmPMB1MtMfeXS5LpcekGDSq9J2O20zySzNHXZ7xkvx5vJ327ioNoDEizwInEaLMG5tMqaWthqZE_-DZlpI7n3v35Glt3hNuT_utgSlBBqt7yipMuy-dunICsXT3Oxojeb-zcvPk3SFsWyMcTI-354eV1PfA-7q0mOGIADYuavzciBKjfZEHrsR9su48sHj_AH8RG4b_8yBzmR7WAj7Va-EhZzxDnlGdfBVqUo5wVUyJxlZe6NWkNkBwiaisxhC8As0VJIY0u_ugms\"/><input type=\"hidden\" name=\"reason\" value=\"a\"/><input class=\"fbc-button-audio fbc-button\" type=\"submit\" value=\"Get an audio challenge\"/></form></div></div></div><div class=\"fbc-why-fallback\"><a href=\"https://support.google.com/recaptcha#6262736\" target=\"_blank\">Want an easier challenge?</a></div><div class=\"fbc-separator\"></div><div class=\"fbc-privacy\"><a href=\"https://www.google.com/intl/en/policies/privacy/\" target=\"_blank\">Privacy</a> - <a href=\"https://www.google.com/intl/en/policies/terms/\" target=\"_blank\">Terms</a></div></div><script nonce=\"AxGVM2LSHezkZMBRJNGTTQ\">document.body.className += \" js-enabled\";</script></body></html>"); |
||||||
|
|
||||||
|
for (int i = 0; i < inputDataList.size(); ++i) { |
||||||
|
resultCheckboxList.add(new ArrayList<>()); |
||||||
|
|
||||||
|
resultCheckboxList.get(i).add(0); |
||||||
|
resultCheckboxList.get(i).add(1); |
||||||
|
resultCheckboxList.get(i).add(2); |
||||||
|
resultCheckboxList.get(i).add(3); |
||||||
|
resultCheckboxList.get(i).add(4); |
||||||
|
resultCheckboxList.get(i).add(5); |
||||||
|
resultCheckboxList.get(i).add(6); |
||||||
|
resultCheckboxList.get(i).add(7); |
||||||
|
resultCheckboxList.get(i).add(8); |
||||||
|
} |
||||||
|
|
||||||
|
resultTitleList.add("Select all images with cars"); |
||||||
|
resultTitleList.add("Select all images with mountains or hills"); |
||||||
|
resultTitleList.add("Select all images with crosswalks"); |
||||||
|
resultTitleList.add("Select all images with bridges"); |
||||||
|
resultTitleList.add("Select all images with bicycles"); |
||||||
|
resultTitleList.add("Select all images with cars"); |
||||||
|
resultTitleList.add("Select all images with a bus"); |
||||||
|
|
||||||
|
resultCParameterList.add("03AOLTBLSABiot1uRaxXzd4_PRTTGbxW4AV8NOI947nBNVIBwdfOC2DwB0gdrPnfe8YYLI3kkW_tzwTJbQwZk0_nKaIsfXLaGWnnrEhvRMstWDzRin27w_Yalo6OS8Yr5sIqYBUfI496UtsoDGSL8NVQAbxjXocz_E7DTNXVBMfdLPx093-9vUHKH4yt4Mo6HuKSXmBPC2KNL4j_yl78YQ_VG1mprDwUkQeW-MQZxBOQb4MyhWZMOmNp_aRgHKGpJYHpcVMljDUvwZRk_xLw-NoJAWsgCIAt6pfrUE9jvHxjE4fep8OCH2T-6g-_Lla_UovuyEVIfUi1MQa6KWQMypsavA2mVUjTvIe_NkkO8fJ6gteHqUc7a9FTIHHs1OR5uV7fD0SCn9G5ii"); |
||||||
|
resultCParameterList.add("03AOLTBLRv_ZKMPWAc0bBYTnC9wPIQElGB6H6SrifMnrj1yDeFnUPNpOPgGZHdaGPVzfieFHzQOh4GIKWUBPQ_aMs3gp0EJmgdP-LAGFBZIreVB-boqP1DyIblN-3ws1bxCyVvw4kSanc08rPVr7G2nwy7b0kkBV82x8ypU1QP1fQR1HxC8qUddQZSLvgmkFiRatbaOqMdsMPDwl1ENJVrzTkX0sQGmfqnf2aisqRbSsiH0BrW1_wTLPjiGv1SW-FrBYWKJZBWP_FanbTftUuhKLaYTjABDD2mcBg-7-QEpFbaFqi7cwSA4Y-SsCiJg7y0DBL8PjsDSxOqoY_Upc5I-0GI3X21KJLJMLxq9tRkWZbsyhHXNyFzzM_4PfOIiGErbTMzICa596q7"); |
||||||
|
resultCParameterList.add("03AOLTBLQdQO0rHKgYInXnNyPho7cEllKtboAokZsqQPHyE5VkAEXwvkvin540F_CAPIA872WSFqdlShtJvERZPgOP_DmuO4lAF7CxIRDeRzyMd2kKF-ivS3XADiISevh50q1qtolKOW6nhdktgo4JxW3nej539yRZxJ4X6SRde_29QsGgqtTUUB82CYWZ2vOa9yclvuMMLPTFfIIRQHy4DNQXqVOEKV0PfvBatjLhq4ekuYgEoHbni97ePTmjvfsL0gy6CR2SAmOf6m7oJm2snVyBBgxWfMnPue6wypmHco7JRLcEsRHOvUDBLa6rMT5p7fiRP3sxPQNcv_H6prK_EAtSPOB29SELUlf7G72UlcxBNOaKxsOY_xzyFHnyFPRTERh2xc7FEmDF"); |
||||||
|
resultCParameterList.add("03AOLTBLTkdQd0zGmSjFw9OCiKz18VT-h4lleJaBwtNMSDPFsUSR_9qepA1uCUW_FNAju-tgYzTOTjmjf0wWmvfLlwKCxEsyMKfYobnJXXYcOHyAwaOJRtEOAwn2SSmVEzlE1DYZ89lFH7XB5rw6tGpSJRJQkvitoVK-aLg_gzmYxbYJPH_VK9ofkfeGfhnZ6bsXoz1vnnhHYOXm7apmKjDEk6NIxKqB_FHDFclemX8rr7liJBaTcJ98I20a9wXGinBUEEyupoQ5gdnoYqLMu_XIQaRYtdmRsCgtm9RorpkJZxaqEpqfusfpFZUntIXkXF31dmx0hL-B5DpA4uFOBI6UmR2oPLAA2LhStMgKtlnl1IYwyrmaeYUybV0rPl0F5jMAr3GZxEa7Ld"); |
||||||
|
resultCParameterList.add("03AOLTBLTAhnX3O2lESLZYL9y7w8txPisktNhNpI-h3hZpULhz_-yMEId_7-NW8RGCMbZ6_W8CRH6LnL7Icxdw3nISINh3DGcldAmF8jojjG17gPIVvCCk1WdKfgYSkC_oAThQDESoAOz-VGAahHroLUNSnCm1tHmExy5tRX3EGi0xO3ep-1iMSGuMb_aoo3hxIuEZE4_J37RGoE--t2yoU4pKDxfbnHNYYfl48KgIb_qBzUt9O-gOF5P3oMM7G5HpMHjq3lraD0Ep19tiq5c_P-JO21V7QiJBB8XiwqgIHsEwGaLsm3J2NdQjLHot1xy8_A8x04ZTqHw-d_LnpBOTz8Jry5ym2K-zfkqkK1VtbJUnayZ-i-e5_ErxE3HTesMtzJ6I-ZBu2h8T"); |
||||||
|
resultCParameterList.add("03AOLTBLSu6KJ0tMzrENsXJM2H5K8GDF_2PZOEYKew4UxN49JDmoXnrlOFLYbETkJfXyG-kXWdmiTIg3XgR8vVfIf8J4oR1DpEyi9HcPaV9kLNeq-0aK3P1Vw62wBm3OOhU_zeP49kiwmy4icv4ro9NQjfBxw1scZgYsj_R41sBDvlLkofoDMrWNzsAjwFAM2pmbRGKf6qThzQ6ijSAjHBAD_oIK4E4m7kgeUZyEvw9-2MN_8AJoGXtI_74LdvbkngnF1qqK5gIKYX05AyaI39M1-TXfT5Gq2ePu8XMKUR709pZPc22Pw9lni0a1I-UyfGMJJ1u0Pvh0HKO6atPREGFcXwvhOQsSHakAGuzHUE8V5ywUqpW3mNFE0xly-GK07BbGMicQZ-7F-9"); |
||||||
|
resultCParameterList.add("03AOLTBLSe4m_kEnKDI7Zg9oYjJ28Y1XKwS7uZ10NlWRoq-2e5tLG3r47ClAG3F_pTLqQC_krxnHkt8UBIV5EfW5goPvQQQN2W8ll3YYiMjWM_ge1_Z9lk_7M5KCriFBFhmPMB1MtMfeXS5LpcekGDSq9J2O20zySzNHXZ7xkvx5vJ327ioNoDEizwInEaLMG5tMqaWthqZE_-DZlpI7n3v35Glt3hNuT_utgSlBBqt7yipMuy-dunICsXT3Oxojeb-zcvPk3SFsWyMcTI-354eV1PfA-7q0mOGIADYuavzciBKjfZEHrsR9su48sHj_AH8RG4b_8yBzmR7WAj7Va-EhZzxDnlGdfBVqUo5wVUyJxlZe6NWkNkBwiaisxhC8As0VJIY0u_ugms"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testCheckBoxParsing() throws CaptchaNoJsHtmlParser.CaptchaNoJsV2ParsingError { |
||||||
|
for (int i = 0; i < inputDataList.size(); ++i) { |
||||||
|
String inputData = inputDataList.get(i); |
||||||
|
CaptchaInfo captchaInfo = new CaptchaInfo(); |
||||||
|
parser.parseCheckboxes(inputData, captchaInfo); |
||||||
|
|
||||||
|
for (int j = 0; j < captchaInfo.checkboxes.size(); j++) { |
||||||
|
Integer checkbox = captchaInfo.checkboxes.get(j); |
||||||
|
assertEquals(checkbox, resultCheckboxList.get(i).get(j)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testTitleParsing() throws CaptchaNoJsHtmlParser.CaptchaNoJsV2ParsingError { |
||||||
|
for (int i = 0; i < inputDataList.size(); ++i) { |
||||||
|
String inputData = inputDataList.get(i); |
||||||
|
CaptchaInfo captchaInfo = new CaptchaInfo(); |
||||||
|
parser.parseChallengeTitle(inputData, captchaInfo); |
||||||
|
|
||||||
|
String title = captchaInfo.captchaTitle.getTitle(); |
||||||
|
assertEquals(resultTitleList.get(i), title); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testCValueParsing() throws CaptchaNoJsHtmlParser.CaptchaNoJsV2ParsingError { |
||||||
|
for (int i = 0; i < inputDataList.size(); ++i) { |
||||||
|
String inputData = inputDataList.get(i); |
||||||
|
CaptchaInfo captchaInfo = new CaptchaInfo(); |
||||||
|
parser.parseCParameter(inputData, captchaInfo); |
||||||
|
|
||||||
|
String cParameter = captchaInfo.cParameter; |
||||||
|
assertEquals(resultCParameterList.get(i), cParameter); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue