From 1a03c005fe23c58f58c025b2017f8f48f28381bd Mon Sep 17 00:00:00 2001 From: Floens Date: Wed, 10 Dec 2014 23:10:07 +0100 Subject: [PATCH 1/7] Properly fix captchas, use the official noscript fallback method. --- .../java/org/floens/chan/chan/ChanUrls.java | 6 +- .../chan/core/manager/ReplyManager.java | 189 +++++++++++------- docs/gcaptcha.txt | 27 ++- 3 files changed, 143 insertions(+), 79 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 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 From 4486975dcf57f4334c2a4a594e942e9fc7744b08 Mon Sep 17 00:00:00 2001 From: Floens Date: Wed, 10 Dec 2014 23:26:29 +0100 Subject: [PATCH 2/7] Release v1.2.4 --- CHANGES.txt | 5 +++++ Clover/app/build.gradle | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e6d7ab94..9776742e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,8 @@ +New in 1.2.4 (2014-12-10) +- Properly fixed captchas +- Other bug fixes + + New in 1.2.3 (2014-12-08) - Hotfix for captchas diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index ddbab2b9..2773b963 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -8,8 +8,8 @@ android { minSdkVersion 14 targetSdkVersion 21 - versionName "v1.2.3" - versionCode 41 + versionName "v1.2.4" + versionCode 42 } compileOptions { From 0e44a42760dc3366de89a2c7876b64d9068b59ec Mon Sep 17 00:00:00 2001 From: Floens Date: Thu, 11 Dec 2014 00:26:38 +0100 Subject: [PATCH 3/7] Fix typo in readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f60fe357..40fdaa69 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This repo contains all the code for Clover. [Join this G+ community](https://plus.google.com/communities/108906508206092146956) and follow the instructions in the "About this community" box. -## Contibuting +## Contributing See the [Clover setup guide](https://github.com/Floens/Clover/wiki/Building-Clover) when you want to make changes to the code. [The issues page](https://github.com/Floens/Clover/issues) is for reporting bugs and feature requests. Be sure to search for existing items on the todo list and issues page before reporting anything. From 491c35c53c4b1726cc2a59eb391047a1022aa188 Mon Sep 17 00:00:00 2001 From: Floens Date: Thu, 11 Dec 2014 12:41:21 +0100 Subject: [PATCH 4/7] Fix passes. --- .../org/floens/chan/core/manager/ReplyManager.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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 bc23ed92..f0c4adbe 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 @@ -380,7 +380,7 @@ public class ReplyManager { CaptchaHashListener captchaHashListener = new CaptchaHashListener() { @Override public void onHash(String captchaHash) { - if (captchaHash == null) { + if (captchaHash == null && !reply.usePass) { // Could not find a hash in the response html ReplyResponse e = new ReplyResponse(); e.isUserError = true; @@ -409,7 +409,9 @@ public class ReplyManager { entity.addTextBody("spoiler", "on"); } - entity.addTextBody("g-recaptcha-response", captchaHash, TEXT_UTF_8); + if (!reply.usePass) { + entity.addTextBody("g-recaptcha-response", captchaHash, TEXT_UTF_8); + } entity.addTextBody("mode", "regist"); entity.addTextBody("pwd", reply.password); @@ -481,7 +483,11 @@ public class ReplyManager { } }; - getCaptchaHash(captchaHashListener, reply.captchaChallenge, reply.captchaResponse); + if (reply.usePass) { + captchaHashListener.onHash(null); + } else { + getCaptchaHash(captchaHashListener, reply.captchaChallenge, reply.captchaResponse); + } } public static interface ReplyListener { From 164e125bd117bb530d185be8050433a03271eb88 Mon Sep 17 00:00:00 2001 From: Floens Date: Thu, 11 Dec 2014 12:50:56 +0100 Subject: [PATCH 5/7] Release v1.2.5 --- CHANGES.txt | 4 ++++ Clover/app/build.gradle | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9776742e..784b9266 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +New in 1.2.5 (2014-12-11) +- Fix 4chan pass + + New in 1.2.4 (2014-12-10) - Properly fixed captchas - Other bug fixes diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index 2773b963..385fc5ea 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -8,8 +8,8 @@ android { minSdkVersion 14 targetSdkVersion 21 - versionName "v1.2.4" - versionCode 42 + versionName "v1.2.5" + versionCode 43 } compileOptions { From 9365a99497daf1f8c73c2145df5e9be3df1f91f8 Mon Sep 17 00:00:00 2001 From: Floens Date: Thu, 11 Dec 2014 19:16:36 +0100 Subject: [PATCH 6/7] Update jsoup --- Clover/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index 385fc5ea..5081fc51 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -67,7 +67,7 @@ android { } dependencies { - compile 'org.jsoup:jsoup:1.7.3' + compile 'org.jsoup:jsoup:1.8.1' compile 'com.j256.ormlite:ormlite-core:4.48' compile 'com.j256.ormlite:ormlite-android:4.48' compile 'com.android.support:support-v13:18.0.0' From c22e75f0db5760158f99fef6483f2b06b900fb89 Mon Sep 17 00:00:00 2001 From: Floens Date: Thu, 11 Dec 2014 19:20:43 +0100 Subject: [PATCH 7/7] Update gif drawable --- Clover/app/build.gradle | 2 +- Clover/app/proguard.cfg | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index 5081fc51..22095414 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -72,7 +72,7 @@ dependencies { compile 'com.j256.ormlite:ormlite-android:4.48' compile 'com.android.support:support-v13:18.0.0' compile 'com.koushikdutta.ion:ion:2.0.1' - compile 'pl.droidsonroids.gif:android-gif-drawable:1.0.12' + compile 'pl.droidsonroids.gif:android-gif-drawable:1.1.0' compile files('libs/httpclientandroidlib-1.2.1.jar') } diff --git a/Clover/app/proguard.cfg b/Clover/app/proguard.cfg index 7f3763cc..a43aa602 100644 --- a/Clover/app/proguard.cfg +++ b/Clover/app/proguard.cfg @@ -131,4 +131,5 @@ public *; } --keep public class pl.droidsonroids.gif.GifIOException{*;} +-keep public class pl.droidsonroids.gif.GifIOException{(int);} +-keep class pl.droidsonroids.gif.GifInfoHandle{(long,int,int,int);}