Fix captcha

captchafix2
Floens 11 years ago
parent 21a5b4ba68
commit 47ec05b2e4
  1. 8
      Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java
  2. 75
      Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java
  3. 42
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java
  4. 110
      docs/gcaptcha.txt

@ -38,12 +38,8 @@ public class ChanUrls {
return scheme + "://a.4cdn.org/" + board + "/thread/" + no + ".json";
}
public static String getCaptchaChallengeUrl() {
return scheme + "://www.google.com/recaptcha/api/challenge?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc";
}
public static String getCaptchaImageUrl(String challenge) {
return scheme + "://www.google.com/recaptcha/api2/payload?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc&c=" + challenge;
public static String getCaptchaDomain() {
return scheme + "://www.google.com/";
}
public static String getCaptchaFallback() {

@ -19,6 +19,7 @@ package org.floens.chan.core.manager;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
@ -31,6 +32,7 @@ import org.floens.chan.utils.Logger;
import org.floens.chan.utils.Utils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.File;
@ -60,7 +62,6 @@ import ch.boye.httpclientandroidlib.util.EntityUtils;
public class ReplyManager {
private static final String TAG = "ReplyManager";
private static final Pattern challengePattern = Pattern.compile("challenge.?:.?'([\\w-]+)'");
private static final Pattern responsePattern = Pattern.compile("<!-- thread:([0-9]+),no:([0-9]+) -->");
private static final int POST_TIMEOUT = 10000;
@ -170,22 +171,6 @@ public class ReplyManager {
public abstract void onFileLoading();
}
/**
* Get the CAPTCHA challenge hash from an JSON response.
*
* @param total The total response from the server
* @return The pattern, or null when none was found.
*/
public static String getChallenge(String total) {
Matcher matcher = challengePattern.matcher(total);
if (matcher.find() && matcher.groupCount() == 1) {
return matcher.group(1);
} else {
return null;
}
}
public void sendPass(Pass pass, final PassListener listener) {
Logger.i(TAG, "Sending pass login request");
@ -336,6 +321,49 @@ public class ReplyManager {
public String responseData = "";
}
public void getCaptchaChallenge(final CaptchaChallengeListener listener, String reuseHtml) {
HttpPost httpPost = new HttpPost(ChanUrls.getCaptchaFallback());
HttpPostSendListener postListener = new HttpPostSendListener() {
@Override
public void onResponse(String responseString, HttpClient client, HttpResponse response) {
if (responseString != null) {
Document document = Jsoup.parseBodyFragment(responseString, ChanUrls.getCaptchaDomain());
Elements images = document.select("div.fbc-challenge img");
String imageUrl = images.first() == null ? "" : images.first().absUrl("src");
Elements inputs = document.select("div.fbc-challenge input");
String challenge = "";
for (Element input : inputs) {
if (input.attr("name").equals("c")) {
challenge = input.attr("value");
break;
}
}
if (!TextUtils.isEmpty(imageUrl) && !TextUtils.isEmpty(challenge)) {
listener.onChallenge(imageUrl, challenge);
return;
}
}
listener.onError();
}
};
if (TextUtils.isEmpty(reuseHtml)) {
sendHttpPost(httpPost, postListener);
} else {
Logger.i(TAG, "Reusing html " + reuseHtml);
postListener.onResponse(reuseHtml, null, null);
}
}
public interface CaptchaChallengeListener {
public void onChallenge(String imageUrl, String challenge);
public void onError();
}
private void getCaptchaHash(final CaptchaHashListener listener, String challenge, String response) {
HttpPost httpPost = new HttpPost(ChanUrls.getCaptchaFallback());
@ -354,17 +382,17 @@ public class ReplyManager {
Elements verificationToken = document.select("div.fbc-verification-token textarea");
String hash = verificationToken.text();
if (hash.length() > 0) {
listener.onHash(hash);
listener.onHash(hash, responseString);
return;
}
}
listener.onHash(null);
listener.onHash(null, responseString);
}
});
}
private interface CaptchaHashListener {
public void onHash(String hash);
public void onHash(String hash, String html);
}
/**
@ -379,12 +407,13 @@ public class ReplyManager {
CaptchaHashListener captchaHashListener = new CaptchaHashListener() {
@Override
public void onHash(String captchaHash) {
public void onHash(String captchaHash, String captchaHtml) {
if (captchaHash == null && !reply.usePass) {
// Could not find a hash in the response html
ReplyResponse e = new ReplyResponse();
e.isUserError = true;
e.isCaptchaError = true;
e.captchaHtml = captchaHtml;
listener.onResponse(e);
return;
}
@ -484,7 +513,7 @@ public class ReplyManager {
};
if (reply.usePass) {
captchaHashListener.onHash(null);
captchaHashListener.onHash(null, null);
} else {
getCaptchaHash(captchaHashListener, reply.captchaChallenge, reply.captchaResponse);
}
@ -535,6 +564,8 @@ public class ReplyManager {
* The thread no the post has
*/
public int threadNo = -1;
public String captchaHtml;
}
/**

@ -44,15 +44,10 @@ import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;
import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.NetworkImageView;
import com.android.volley.toolbox.StringRequest;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.ChanPreferences;
import org.floens.chan.core.manager.ReplyManager;
import org.floens.chan.core.manager.ReplyManager.ReplyResponse;
@ -209,7 +204,7 @@ public class ReplyFragment extends DialogFragment {
});
showCommentCount();
getCaptcha();
getCaptcha(null);
} else {
Logger.e(TAG, "Loadable in ReplyFragment was null");
closeReply();
@ -268,7 +263,7 @@ public class ReplyFragment extends DialogFragment {
captchaContainer.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
getCaptcha();
getCaptcha(null);
}
});
captchaInput = (TextView) container.findViewById(R.id.reply_captcha);
@ -519,7 +514,7 @@ public class ReplyFragment extends DialogFragment {
loadView.setView(text);
}
private void getCaptcha() {
private void getCaptcha(String reuseHtml) {
if (gettingCaptcha)
return;
gettingCaptcha = true;
@ -527,29 +522,22 @@ public class ReplyFragment extends DialogFragment {
captchaContainer.setView(null);
captchaInput.setText("");
String url = ChanUrls.getCaptchaChallengeUrl();
ChanApplication.getVolleyRequestQueue().add(new StringRequest(Method.GET, url, new Response.Listener<String>() {
ChanApplication.getReplyManager().getCaptchaChallenge(new ReplyManager.CaptchaChallengeListener() {
@Override
public void onResponse(String result) {
if (context != null) {
String challenge = ReplyManager.getChallenge(result);
if (challenge != null) {
captchaChallenge = challenge;
String imageUrl = ChanUrls.getCaptchaImageUrl(challenge);
public void onChallenge(String imageUrl, String challenge) {
gettingCaptcha = false;
NetworkImageView captchaImage = new NetworkImageView(context);
captchaImage.setImageUrl(imageUrl, ChanApplication.getVolleyImageLoader());
captchaContainer.setView(captchaImage);
if (context != null) {
captchaChallenge = challenge;
gettingCaptcha = false;
}
NetworkImageView captchaImage = new NetworkImageView(context);
captchaImage.setImageUrl(imageUrl, ChanApplication.getVolleyImageLoader());
captchaContainer.setView(captchaImage);
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
public void onError() {
gettingCaptcha = false;
if (context != null) {
@ -559,7 +547,7 @@ public class ReplyFragment extends DialogFragment {
captchaContainer.setView(text);
}
}
}));
}, reuseHtml);
}
/**
@ -621,7 +609,7 @@ public class ReplyFragment extends DialogFragment {
cancelButton.setEnabled(true);
setClosable(true);
flipPage(1);
getCaptcha();
getCaptcha(response.captchaHtml);
captchaInput.setText("");
} else if (response.isSuccessful) {
shouldSaveDraft = false;

@ -7,25 +7,121 @@ https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLV
Put this in an iframe and when the user has successfully filled in the captcha the user is told to copy paste the key into the real website field (outside the iframe, g-captcha-response)
https://www.google.com/recaptcha/api/challenge?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc
contains the key 'challenge'
Now load the image
https://www.google.com/recaptcha/api2/payload?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc&c=CHALLENGE
contains an image and and a field named "c" with the challenge
after the user has solved the image, do a POST to
https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc
POST:
c = CHALLENGE
response = USER_RESPONSE
c = the challenge given
response = text response from the user
You'll have to get hash inside the textarea of div.fbc-verification-token
Next if the captcha was solved, get the hash inside the textarea of div.fbc-verification-token
If the textarea isn't present, the captcha wasn't solved correctly you'll reuse the received html.
next send off the reply to 4chan
POST:
g-captcha-response: HASH
Initial HTML:
<!DOCTYPE HTML>
<html dir="ltr">
<head>
<meta http-equiv='content-type' content='text/html; charset=UTF-8'>
<title>reCAPTCHA-uitdaging</title>
<link rel="stylesheet" href="https://www.gstatic.com/recaptcha/api2/r20150202092117/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>
<div>
<div class="fbc-challenge">
<form method="POST">
<input type="hidden" name="c" value="03AHJ_VuvGIGhhDoAQAIA8K-vUZHFTAlzF2lAYqmqNIEACc2s6Tu1pZMJScewJo48x0zPzepF1fSgczr9N6TqRoqw-OfTbd4IcAOi7qIQHaTjiftUCI4kpZxRCfmwnJbpeQWl_-SB9Hcg2Jfn-Ri8hnlpHFi5_TRB__Qd5Ni3NoSRP4RBSITkzDB7hQcZN4dZFzGXuB7NJalhTDw5eqwfVre6zIPqxgMW3jkXPTh5C4vAL_NNvNlAXRhMSLBAwxWKV_72fBpr_tWaOOi4z1Jvall4PuYmPi9vj5Ls2A46XKZr85YnJFzaZ4F8ooG5OEv7yM9ZKVByw95rO" />
<div class="fbc-message">
<label for="response">Voer de tekst in</label>
</div>
<div class="fbc-input-field">
<input dir="ltr" type="text" id="response" name="response" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
</div>
<div class="fbc-payload">
<img src="/recaptcha/api2/payload?c=03AHJ_VuvGIGhhDoAQAIA8K-vUZHFTAlzF2lAYqmqNIEACc2s6Tu1pZMJScewJo48x0zPzepF1fSgczr9N6TqRoqw-OfTbd4IcAOi7qIQHaTjiftUCI4kpZxRCfmwnJbpeQWl_-SB9Hcg2Jfn-Ri8hnlpHFi5_TRB__Qd5Ni3NoSRP4RBSITkzDB7hQcZN4dZFzGXuB7NJalhTDw5eqwfVre6zIPqxgMW3jkXPTh5C4vAL_NNvNlAXRhMSLBAwxWKV_72fBpr_tWaOOi4z1Jvall4PuYmPi9vj5Ls2A46XKZr85YnJFzaZ4F8ooG5OEv7yM9ZKVByw95rO&amp;k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc" alt="reCAPTCHA-uitdagingsafbeelding" />
</div>
<div class="fbc-button-verify">
<input type="submit" value="Verifiëren" />
</div>
</form>
</div>
<div class="fbc-buttons">
<div class="fbc-button-holder">
<form method="POST" tabindex="-1">
<input type="hidden" name="c" value="03AHJ_VuvGIGhhDoAQAIA8K-vUZHFTAlzF2lAYqmqNIEACc2s6Tu1pZMJScewJo48x0zPzepF1fSgczr9N6TqRoqw-OfTbd4IcAOi7qIQHaTjiftUCI4kpZxRCfmwnJbpeQWl_-SB9Hcg2Jfn-Ri8hnlpHFi5_TRB__Qd5Ni3NoSRP4RBSITkzDB7hQcZN4dZFzGXuB7NJalhTDw5eqwfVre6zIPqxgMW3jkXPTh5C4vAL_NNvNlAXRhMSLBAwxWKV_72fBpr_tWaOOi4z1Jvall4PuYmPi9vj5Ls2A46XKZr85YnJFzaZ4F8ooG5OEv7yM9ZKVByw95rO" />
<input type="hidden" name="reason" value="r" />
<input class="fbc-button-reload fbc-button" type="submit" value="Een nieuwe uitdaging proberen" />
</form>
</div>
<div class="fbc-button-holder">
<form method="POST" tabindex="-1">
<input type="hidden" name="c" value="03AHJ_VuvGIGhhDoAQAIA8K-vUZHFTAlzF2lAYqmqNIEACc2s6Tu1pZMJScewJo48x0zPzepF1fSgczr9N6TqRoqw-OfTbd4IcAOi7qIQHaTjiftUCI4kpZxRCfmwnJbpeQWl_-SB9Hcg2Jfn-Ri8hnlpHFi5_TRB__Qd5Ni3NoSRP4RBSITkzDB7hQcZN4dZFzGXuB7NJalhTDw5eqwfVre6zIPqxgMW3jkXPTh5C4vAL_NNvNlAXRhMSLBAwxWKV_72fBpr_tWaOOi4z1Jvall4PuYmPi9vj5Ls2A46XKZr85YnJFzaZ4F8ooG5OEv7yM9ZKVByw95rO" />
<input type="hidden" name="reason" value="a" />
<input class="fbc-button-audio fbc-button" type="submit" value="Een audio-uitdaging proberen" />
</form>
</div>
</div>
</div>
<div class="fbc-separator"></div>
<div class="fbc-paste-area">
<div class="fbc-privacy">
<a href="https://www.google.com/intl/nl/policies/privacy/" target="_blank">Privacy</a> - <a href="https://www.google.com/intl/nl/policies/terms/" target="_blank">Voorwaarden</a>
</div>
</div>
</div>
</body>
</html>
Copy-paste code HTML:
<!DOCTYPE HTML>
<html dir="ltr">
<head>
<meta http-equiv='content-type' content='text/html; charset=UTF-8'>
<title>reCAPTCHA-uitdaging</title>
<link rel="stylesheet" href="https://www.gstatic.com/recaptcha/api2/r20150202092117/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>
<div class="fbc-success">
<div class="fbc-message">Kopieëer deze code</div>
<div class="fbc-verification-token">
<textarea dir="ltr" readonly onclick="this.select()">
03AHJ_Vuvppg-3fh3FZzsT5ArpJD66n9npxpdMArjhFJTH9F_l0-Yp4hQ8LNCDvULQL1hM-WsiMotodqbXN1XDYtmwS18KHmCbLWJEK8UTw4nXKUouTQhKqBK_smQtUPo4nO8vb5VWgFEDjutq3rajk_pwF-TeeXovo3l7prXLHGkZZ6751kGoys3uf9v6QZkANtvVoFtzMvLQ1aOjHB5EftJ7baw6ZFP9GfB6J6r5ANqAvtDrt0ZD7myVIMbYes24QuMOOhl4x8_g
</textarea>
</div>
<div class="fbc-message">Deze code is 2 minuten geldig</div>
</div>
<div class="fbc-separator"></div>
<div class="fbc-message fbc-paste">Plak de code hier</div>
<div class="fbc-paste-area">
<div class="fbc-privacy">
<a href="https://www.google.com/intl/nl/policies/privacy/" target="_blank">Privacy</a> - <a href="https://www.google.com/intl/nl/policies/terms/" target="_blank">Voorwaarden</a>
</div>
</div>
</div>
</body>
</html>

Loading…
Cancel
Save