From 47ec05b2e40244726690b030f983751e0674d4ca Mon Sep 17 00:00:00 2001 From: Floens Date: Fri, 6 Feb 2015 13:22:14 +0100 Subject: [PATCH] Fix captcha --- .../java/org/floens/chan/chan/ChanUrls.java | 8 +- .../chan/core/manager/ReplyManager.java | 75 ++++++++---- .../chan/ui/fragment/ReplyFragment.java | 42 +++---- docs/gcaptcha.txt | 110 ++++++++++++++++-- 4 files changed, 173 insertions(+), 62 deletions(-) diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java index e39311df..2b640b7c 100644 --- a/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java +++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java @@ -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() { diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java b/Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java index f0c4adbe..d478a491 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java @@ -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(""); 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; } /** diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java index 9a223eae..313d734c 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java @@ -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() { + 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; diff --git a/docs/gcaptcha.txt b/docs/gcaptcha.txt index 580d6ab5..c435480b 100644 --- a/docs/gcaptcha.txt +++ b/docs/gcaptcha.txt @@ -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: + + + + + + reCAPTCHA-uitdaging + + + +
+
+
+ +
+
+
+
+ +
+ +
+
+ +
+
+ reCAPTCHA-uitdagingsafbeelding +
+
+ +
+
+
+
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+
+
+ +
+
+ + +Copy-paste code HTML: + + + + + reCAPTCHA-uitdaging + + + +
+
+
+ +
+
+
Kopieƫer deze code
+
+ +
+
Deze code is 2 minuten geldig
+
+
+
Plak de code hier
+
+ +
+
+ +