Merge branch 'dev' into material

Conflicts:
	Clover/app/build.gradle
material
Floens 11 years ago
commit 11e3573d3d
  1. 9
      CHANGES.txt
  2. 10
      Clover/app/build.gradle
  3. 3
      Clover/app/proguard.cfg
  4. 6
      Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java
  5. 195
      Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java
  6. 2
      README.md
  7. 27
      docs/gcaptcha.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

@ -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')
}

@ -131,4 +131,5 @@
public *;
}
-keep public class pl.droidsonroids.gif.GifIOException{*;}
-keep public class pl.droidsonroids.gif.GifIOException{<init>(int);}
-keep class pl.droidsonroids.gif.GifInfoHandle{<init>(long,int,int,int);}

@ -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) {

@ -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 {

@ -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.

@ -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

Loading…
Cancel
Save