Android Open Source - Android-Lib-reCAPTCHA Re Captcha






From Project

Back to project page Android-Lib-reCAPTCHA.

License

The source code is released under:

Apache License

If you think the Android project Android-Lib-reCAPTCHA listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package android.lib.recaptcha;
/* w  ww  .  j ava 2  s.  com*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * <code>ReCaptcha</code> extends {@link android.widget.ImageView} to let you embed a <a href="http://captcha.net/">CAPTCHA</a>
 * in your applications in order to protect them against spam and other types of automated abuse.
 *
 * @see <a href="https://developers.google.com/recaptcha/">reCAPTCHA</a>
 */
public class ReCaptcha extends ImageView {
    /**
     * Listener that is called when an attempt to show a <a href="http://captcha.net/">CAPTCHA</a>
     * is completed.
     */
    public interface OnShowChallengeListener {
        /**
         * Called when an attempt to show a <a href="http://captcha.net/">CAPTCHA</a> is completed.
         *
         * @param shown <code>true</code> if a <a href="http://captcha.net/">CAPTCHA</a> is shown;
         *              otherwise, <code>false</code>.
         */
        void onChallengeShown(boolean shown);
    }

    /**
     * Listener that is called when an answer entered by the user to solve the <a href="http://captcha.net/">CAPTCHA</a>
     * displayed is verified.
     */
    public interface OnVerifyAnswerListener {
        /**
         * Called when an answer entered by the user to solve the <a href="http://captcha.net/">CAPTCHA</a>
         * displayed is verified.
         *
         * @param success <code>true</code> if the <a href="http://captcha.net/">CAPTCHA</a> is solved successfully;
         *                otherwise, <code>false</code>.
         */
        void onAnswerVerified(boolean success);
    }

    private static final String TAG = "ReCaptcha";

    private static final String VERIFICATION_URL = "http://www.google.com/recaptcha/api/verify";

    private static final String CHALLENGE_URL = "http://www.google.com/recaptcha/api/challenge?k=%s";
    private static final String RECAPTCHA_OBJECT_TOKEN_URL = "http://www.google.com/recaptcha/api/reload?c=%s&k=%s&type=%s";
    private static final String IMAGE_URL = "http://www.google.com/recaptcha/api/image?c=%s";

    private String imageToken;
    private HashMap<String, String> publicKeyChallengeMap = new HashMap<String, String>();

    public ReCaptcha(final Context context) {
        super(context);
    }

    public ReCaptcha(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public ReCaptcha(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Returns a new instance of {@link org.apache.http.client.HttpClient} for downloading ReCaptcha images.
     * <p>Subclasses may override this method and return customized {@link org.apache.http.client.HttpClient},
     * such as an {@link android.net.http.AndroidHttpClient} with custom {@link org.apache.http.params.HttpParams}.</p>
     * <p>The default behavior returns a {@link org.apache.http.impl.client.DefaultHttpClient}</p>
     *
     * @return a {@link org.apache.http.client.HttpClient} for downloading ReCaptcha images.
     */
    protected HttpClient createHttpClient() {
        return new DefaultHttpClient();
    }

    /**
     * Downloads and shows a <a href="http://captcha.net/">CAPTCHA</a> image.
     * <p>You can obtain a public key by <a href="http://www.google.com/recaptcha/whyrecaptcha">signing up for API keys</a>
     * for your application.</p>
     * <p>This method executes synchronously and should not be invoked from the UI thread.
     * For asynchronous invocation, you may use {@link android.os.AsyncTask} or call {@link #showChallengeAsync(String, android.lib.recaptcha.ReCaptcha.OnShowChallengeListener)} instead.</p>
     *
     * @param publicKey The public key that is unique to your domain and sub-domains (unless it is a global key).
     * @return <code>true</code> if a <a href="http://captcha.net/">CAPTCHA</a> {@link android.graphics.Bitmap}
     * is successfully downloaded and shown; otherwise, <code>false</code>.
     * @throws android.lib.recaptcha.ReCaptchaException if the downloaded <a href="http://captcha.net/">CAPTCHA</a> content is malformed.
     * @throws java.io.IOException                      in case of a protocol or network connection problem.
     * @see #showChallengeAsync(String, android.lib.recaptcha.ReCaptcha.OnShowChallengeListener)
     */
    public final boolean showChallenge(final String publicKey) throws ReCaptchaException, IOException {
        if (TextUtils.isEmpty(publicKey)) {
            throw new IllegalArgumentException("publicKey cannot be null or empty");
        }

        this.setImageDrawable(null);

        this.imageToken = null;

        final Bitmap bitmap = this.downloadImage(publicKey);

        this.setImageBitmap(bitmap);

        return bitmap != null;
    }

    /**
     * Downloads and shows a <a href="http://captcha.net/">CAPTCHA</a> image asynchronously.
     * <p>This method executes asynchronously and can be invoked from the UI thread.
     * For synchronous invocation, use {@link #showChallenge(String)}.</p>
     *
     * @param publicKey The public key that is unique to your domain and sub-domains (unless it is global key).
     * @param listener  The callback to call when an attempt to show a <a href="http://captcha.net/">CAPTCHA</a> is completed.
     * @see #showChallenge(String)
     */
    public final void showChallengeAsync(final String publicKey, final OnShowChallengeListener listener) {
        if (TextUtils.isEmpty(publicKey)) {
            throw new IllegalArgumentException("publicKey cannot be null or empty");
        }

        this.setImageDrawable(null);

        this.imageToken = null;

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(final Message message) {
                final Bitmap bitmap = (Bitmap) message.obj;
                final Bitmap scaled = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() * 2, bitmap.getHeight() * 2, true);

                bitmap.recycle();

                ReCaptcha.this.setImageBitmap(scaled);

                if (listener != null) {
                    listener.onChallengeShown(message.obj != null);
                }
            }
        };

        new AsyncTask<String, Void, Bitmap>() {
            @Override
            protected Bitmap doInBackground(final String... publicKeys) {
                try {
                    return ReCaptcha.this.downloadImage(publicKeys[0]);
                } catch (final ReCaptchaException e) {
                    Log.e(ReCaptcha.TAG, "The downloaded CAPTCHA content is malformed", e);
                } catch (final IOException e) {
                    Log.e(ReCaptcha.TAG, "A protocol or network connection problem has occurred", e);
                }

                return null;
            }

            @Override
            protected void onPostExecute(final Bitmap bitmap) {
                handler.sendMessage(handler.obtainMessage(0, bitmap));
            }
        }.execute(publicKey);
    }

    /**
     * Checks whether the answer entered by the user is correct after your application is successfully
     * displaying <a href="https://developers.google.com/recaptcha/">reCAPTCHA</a>.
     * <p>This method executes synchronously and should not be invoked from the UI thread.
     * For asynchronous invocation, you may use {@link android.os.AsyncTask} or call {@link #verifyAnswerAsync(String, String, android.lib.recaptcha.ReCaptcha.OnVerifyAnswerListener)} instead.</p>
     *
     * @param privateKey The private key that is unique to your domain and sub-domains (unless it is a global key).
     * @param answer     The string the user entered to solve the <a href="http://captcha.net/">CAPTCHA</a> displayed.
     * @return <code>true</code> if the <a href="https://developers.google.com/recaptcha/">reCAPTCHA</a> was successful;
     * otherwise <code>false</code>.
     * @throws java.io.IOException in case of a protocol or network connection problem.
     * @see #verifyAnswerAsync(String, String, android.lib.recaptcha.ReCaptcha.OnVerifyAnswerListener)
     */
    public final boolean verifyAnswer(final String privateKey, final String answer) throws IOException {
        if (TextUtils.isEmpty(privateKey)) {
            throw new IllegalArgumentException("privateKey cannot be null or empty");
        }

        if (TextUtils.isEmpty(answer)) {
            throw new IllegalArgumentException("answer cannot be null or empty");
        }

        return this.submitAnswer(privateKey, answer);
    }

    /**
     * Checks asynchronously whether the answer entered by the user is correct after your application
     * is successfully displaying <a href="https://developers.google.com/recaptcha/">reCAPTCHA</a>.
     *
     * @param privateKey The private key that is unique to your domain and sub-domains (unless it is a global key).
     * @param answer     The string the user entered to solve the <a href="http://captcha.net/">CAPTCHA</a> displayed.
     * @param listener   The callback to call when an answer entered by the user is verified.
     * @see #verifyAnswer(String, String)
     */
    public final void verifyAnswerAsync(final String privateKey, final String answer, final OnVerifyAnswerListener listener) {
        if (TextUtils.isEmpty(privateKey)) {
            throw new IllegalArgumentException("privateKey cannot be null or empty");
        }

        if (TextUtils.isEmpty(answer)) {
            throw new IllegalArgumentException("answer cannot be null or empty");
        }

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(final Message message) {
                if (listener != null) {
                    listener.onAnswerVerified((Boolean) message.obj);
                }
            }
        };

        new AsyncTask<String, Void, Boolean>() {
            @Override
            protected Boolean doInBackground(final String... params) {
                try {
                    return ReCaptcha.this.submitAnswer(params[0], params[1]);
                } catch (final IOException e) {
                    Log.e(ReCaptcha.TAG, "A protocol or network connection problem has occurred", e);
                }

                return Boolean.FALSE;
            }

            @Override
            protected void onPostExecute(final Boolean result) {
                handler.sendMessage(handler.obtainMessage(0, result));
            }
        }.execute(privateKey, answer);
    }

    private Bitmap downloadImage(final String publicKey) throws ReCaptchaException, IOException {
        final HttpClient httpClient = this.createHttpClient();

        try {
            String challenge = getChallenge(publicKey);

            if (challenge == null) {
                throw new ReCaptchaException("ReCaptcha challenge not found");
            }

            String imageToken = getImageToken(challenge, publicKey);
            if (imageToken == null) {
                throw new ReCaptchaException("Image token not found");
            }
            this.imageToken = imageToken;     
            final String imageUrl = String.format(ReCaptcha.IMAGE_URL, imageToken);

            final HttpResponse response = httpClient.execute(new HttpGet(imageUrl));

            try {
                final Bitmap bitmap = BitmapFactory.decodeStream(response.getEntity().getContent());

                if (bitmap == null) {
                    throw new ReCaptchaException("Invalid CAPTCHA image");
                }

                return bitmap;
            } finally {
                if (response.getEntity() != null) {
                    response.getEntity().consumeContent();
                }
            }
        } catch (JSONException e) {
            throw new ReCaptchaException("Unable to parse challenge response");
        } finally {
            httpClient.getConnectionManager().shutdown();
        }
        return null;
    }

    private String getImageToken(String challenge, String publicKey) throws IOException {
        HttpClient httpClient = this.createHttpClient();
        String imageTokenUrl = String.format(ReCaptcha.RECAPTCHA_OBJECT_TOKEN_URL, challenge, publicKey, "image");
        String imageTokenResponse = httpClient.execute(new HttpGet(imageTokenUrl), new BasicResponseHandler());
        return substringBetween(imageTokenResponse, "('", "',");

    }

    private String getChallenge(String publicKey) throws IOException, JSONException {
        if (!publicKeyChallengeMap.containsKey(publicKey)) {
            HttpClient httpClient = this.createHttpClient();
            String challenegeResponse = httpClient.execute(new HttpGet(String.format(ReCaptcha.CHALLENGE_URL, publicKey)), new BasicResponseHandler());
            final String recaptchaStateString = substringBetween(
                    challenegeResponse
                    , "RecaptchaState = ", "}") + "}";

            JSONObject recaptchaStateObject = new JSONObject(recaptchaStateString);
            String challenge = recaptchaStateObject.getString("challenge");
            publicKeyChallengeMap.put(publicKey, challenge);
            return challenge;
        } else {
            return publicKeyChallengeMap.get(publicKey);
        }
    }


    private boolean submitAnswer(final String privateKey, final String answer) throws IOException {
        final HttpClient httpClient = this.createHttpClient();
        final HttpPost request = new HttpPost(ReCaptcha.VERIFICATION_URL);
        final List<NameValuePair> params = new ArrayList<NameValuePair>();

        params.add(new BasicNameValuePair("privatekey", privateKey));
        params.add(new BasicNameValuePair("remoteip", "127.0.0.1"));
        params.add(new BasicNameValuePair("challenge", this.imageToken));
        params.add(new BasicNameValuePair("response", answer));

        try {
            request.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

            return httpClient.execute(request, new BasicResponseHandler()).startsWith("true");
        } catch (final UnsupportedEncodingException e) {
            Log.e(ReCaptcha.TAG, "UTF-8 encoding is not supported by this platform", e);
        } finally {
            httpClient.getConnectionManager().shutdown();
        }

        return false;
    }

    private static String substringBetween(String str, String open, String close) {
        if (str == null || open == null || close == null) {
            return null;
        }
        int start = str.indexOf(open);
        if (start != -1) {
            int end = str.indexOf(close, start + open.length());
            if (end != -1) {
                return str.substring(start + open.length(), end);
            }
        }
        return null;
    }
}




Java Source Code List

android.lib.recaptcha.ReCaptchaException.java
android.lib.recaptcha.ReCaptcha.java
android.lib.recaptcha.samples.SampleActivity.java