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 b5550d69..3ac78be8 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 @@ -43,7 +43,11 @@ public class ChanUrls { } public static String getCaptchaImageUrl(String challenge) { - return scheme + "://www.google.com/recaptcha/api/image?c=" + challenge; + return scheme + "://www.google.com/recaptcha/api2/payload?c=" + challenge; + } + + public static String getCaptchaFallback() { + return scheme + "://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc"; } public static String getImageUrl(String board, String code, String extension) { 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 c6143432..bc23ed92 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 @@ -29,6 +29,9 @@ import org.floens.chan.core.model.SavedReply; import org.floens.chan.ui.activity.ImagePickActivity; import org.floens.chan.utils.Logger; import org.floens.chan.utils.Utils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; import java.io.File; import java.io.IOException; @@ -333,6 +336,37 @@ public class ReplyManager { public String responseData = ""; } + private void getCaptchaHash(final CaptchaHashListener listener, String challenge, String response) { + HttpPost httpPost = new HttpPost(ChanUrls.getCaptchaFallback()); + + MultipartEntityBuilder entity = MultipartEntityBuilder.create(); + + entity.addTextBody("c", challenge, TEXT_UTF_8); + entity.addTextBody("response", response, TEXT_UTF_8); + + httpPost.setEntity(entity.build()); + + sendHttpPost(httpPost, new HttpPostSendListener() { + @Override + public void onResponse(String responseString, HttpClient client, HttpResponse response) { + if (responseString != null) { + Document document = Jsoup.parseBodyFragment(responseString); + Elements verificationToken = document.select("div.fbc-verification-token textarea"); + String hash = verificationToken.text(); + if (hash.length() > 0) { + listener.onHash(hash); + return; + } + } + listener.onHash(null); + } + }); + } + + private interface CaptchaHashListener { + public void onHash(String hash); + } + /** * Send an reply off to the server. * @@ -343,96 +377,111 @@ public class ReplyManager { public void sendReply(final Reply reply, final ReplyListener listener) { Logger.i(TAG, "Sending reply request: " + reply.board + ", " + reply.resto); - HttpPost httpPost = new HttpPost(ChanUrls.getReplyUrl(reply.board)); - - MultipartEntityBuilder entity = MultipartEntityBuilder.create(); + CaptchaHashListener captchaHashListener = new CaptchaHashListener() { + @Override + public void onHash(String captchaHash) { + if (captchaHash == null) { + // Could not find a hash in the response html + ReplyResponse e = new ReplyResponse(); + e.isUserError = true; + e.isCaptchaError = true; + listener.onResponse(e); + return; + } - reply.password = Long.toHexString(random.nextLong()); + HttpPost httpPost = new HttpPost(ChanUrls.getReplyUrl(reply.board)); - entity.addTextBody("name", reply.name, TEXT_UTF_8); - entity.addTextBody("email", reply.email, TEXT_UTF_8); + MultipartEntityBuilder entity = MultipartEntityBuilder.create(); - entity.addTextBody("sub", reply.subject, TEXT_UTF_8); - entity.addTextBody("com", reply.comment, TEXT_UTF_8); + reply.password = Long.toHexString(random.nextLong()); - if (reply.resto >= 0) { - entity.addTextBody("resto", Integer.toString(reply.resto)); - } + entity.addTextBody("name", reply.name, TEXT_UTF_8); + entity.addTextBody("email", reply.email, TEXT_UTF_8); - if (reply.spoilerImage) { - entity.addTextBody("spoiler", "on"); - } + entity.addTextBody("sub", reply.subject, TEXT_UTF_8); + entity.addTextBody("com", reply.comment, TEXT_UTF_8); - entity.addTextBody("recaptcha_challenge_field", reply.captchaChallenge); - entity.addTextBody("g-recaptcha-response", reply.captchaResponse, TEXT_UTF_8); + if (reply.resto >= 0) { + entity.addTextBody("resto", Integer.toString(reply.resto)); + } - entity.addTextBody("mode", "regist"); - entity.addTextBody("pwd", reply.password); + if (reply.spoilerImage) { + entity.addTextBody("spoiler", "on"); + } - if (reply.usePass) { - httpPost.addHeader("Cookie", "pass_id=" + reply.passId); - } + entity.addTextBody("g-recaptcha-response", captchaHash, TEXT_UTF_8); - if (reply.file != null) { - entity.addBinaryBody("upfile", reply.file, ContentType.APPLICATION_OCTET_STREAM, reply.fileName); - } + entity.addTextBody("mode", "regist"); + entity.addTextBody("pwd", reply.password); - httpPost.setEntity(entity.build()); + if (reply.usePass) { + httpPost.addHeader("Cookie", "pass_id=" + reply.passId); + } - sendHttpPost(httpPost, new HttpPostSendListener() { - @Override - public void onResponse(String responseString, HttpClient client, HttpResponse response) { - ReplyResponse e = new ReplyResponse(); + if (reply.file != null) { + entity.addBinaryBody("upfile", reply.file, ContentType.APPLICATION_OCTET_STREAM, reply.fileName); + } - if (responseString == null) { - e.isNetworkError = true; - } else { - e.responseData = responseString; + httpPost.setEntity(entity.build()); - if (responseString.contains("No file selected")) { - e.isUserError = true; - e.isFileError = true; - } else if (responseString.contains("You forgot to solve the CAPTCHA") - || responseString.contains("You seem to have mistyped the CAPTCHA")) { - e.isUserError = true; - e.isCaptchaError = true; - } else if (responseString.toLowerCase(Locale.ENGLISH).contains("post successful")) { - e.isSuccessful = true; - } - } + sendHttpPost(httpPost, new HttpPostSendListener() { + @Override + public void onResponse(String responseString, HttpClient client, HttpResponse response) { + ReplyResponse e = new ReplyResponse(); - if (e.isSuccessful) { - Matcher matcher = responsePattern.matcher(e.responseData); - - int threadNo = -1; - int no = -1; - if (matcher.find()) { - try { - threadNo = Integer.parseInt(matcher.group(1)); - no = Integer.parseInt(matcher.group(2)); - } catch (NumberFormatException err) { - err.printStackTrace(); + if (responseString == null) { + e.isNetworkError = true; + } else { + e.responseData = responseString; + + if (responseString.contains("No file selected")) { + e.isUserError = true; + e.isFileError = true; + } else if (responseString.contains("You forgot to solve the CAPTCHA") + || responseString.contains("You seem to have mistyped the CAPTCHA")) { + e.isUserError = true; + e.isCaptchaError = true; + } else if (responseString.toLowerCase(Locale.ENGLISH).contains("post successful")) { + e.isSuccessful = true; + } } - } - if (threadNo >= 0 && no >= 0) { - SavedReply savedReply = new SavedReply(); - savedReply.board = reply.board; - savedReply.no = no; - savedReply.password = reply.password; + if (e.isSuccessful) { + Matcher matcher = responsePattern.matcher(e.responseData); + + int threadNo = -1; + int no = -1; + if (matcher.find()) { + try { + threadNo = Integer.parseInt(matcher.group(1)); + no = Integer.parseInt(matcher.group(2)); + } catch (NumberFormatException err) { + err.printStackTrace(); + } + } - ChanApplication.getDatabaseManager().saveReply(savedReply); + if (threadNo >= 0 && no >= 0) { + SavedReply savedReply = new SavedReply(); + savedReply.board = reply.board; + savedReply.no = no; + savedReply.password = reply.password; - e.threadNo = threadNo; - e.no = no; - } else { - Logger.w(TAG, "No thread & no in the response"); - } - } + ChanApplication.getDatabaseManager().saveReply(savedReply); - listener.onResponse(e); + e.threadNo = threadNo; + e.no = no; + } else { + Logger.w(TAG, "No thread & no in the response"); + } + } + + listener.onResponse(e); + } + }); } - }); + }; + + getCaptchaHash(captchaHashListener, reply.captchaChallenge, reply.captchaResponse); } public static interface ReplyListener { diff --git a/docs/gcaptcha.txt b/docs/gcaptcha.txt index bbd9cca0..590789ea 100644 --- a/docs/gcaptcha.txt +++ b/docs/gcaptcha.txt @@ -1,20 +1,31 @@ - - -4chan key: +4chan captcha key: 6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc +Google supports a noscript version of the new recaptcha, described here https://developers.google.com/recaptcha/docs/faq + https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc +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' -Clover supports the fallback method (for now) +Now load the image +https://www.google.com/recaptcha/api2/payload?c=CHALLENGE + +after the user has solved the image, do a POST to -Stuff https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc -into an iframe +POST: +c = CHALLENGE +response = USER_RESPONSE + +You'll have to get hash inside the textarea of div.fbc-verification-token + +next send off the reply to 4chan +POST: +g-captcha-response: HASH -add an input field named -g-recaptcha-response