diff --git a/CHANGES.txt b/CHANGES.txt index e6d7ab94..784b9266 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,12 @@ +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 + + New in 1.2.3 (2014-12-08) - Hotfix for captchas diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index eb8073a5..e5840dd1 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.5" + versionCode 43 } compileOptions { @@ -69,13 +69,13 @@ android { dependencies { compile 'com.android.support:support-v13:21.0.0' compile 'com.android.support:appcompat-v7:21.0.2' + compile 'com.android.support:support-v13:18.0.0' - 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' 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);} 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..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 @@ -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,117 @@ 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 && !reply.usePass) { + // 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); - } + if (!reply.usePass) { + 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); + } + }); } - }); + }; + + if (reply.usePass) { + captchaHashListener.onHash(null); + } else { + getCaptchaHash(captchaHashListener, reply.captchaChallenge, reply.captchaResponse); + } } public static interface ReplyListener { 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. 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