org.shredzone.commons.captcha.impl.DefaultCaptchaService.java Source code

Java tutorial

Introduction

Here is the source code for org.shredzone.commons.captcha.impl.DefaultCaptchaService.java

Source

/*
 * Shredzone Commons
 *
 * Copyright (C) 2012 Richard "Shred" Krber
 *   http://commons.shredzone.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.shredzone.commons.captcha.impl;

import java.awt.image.BufferedImage;
import java.util.Random;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;

import org.shredzone.commons.captcha.CaptchaGenerator;
import org.shredzone.commons.captcha.CaptchaService;
import org.springframework.stereotype.Component;

/**
 * Default implementation of {@link CaptchaService}.
 *
 * @author Richard "Shred" Krber
 */
@Component("captchaService")
public class DefaultCaptchaService implements CaptchaService {

    private static final String CHARSET = "ABCDEFGHJLMNOPQRSTUWZ";
    private static final int NUMBER_OF_CHARS = 5;
    private static final String CAPTCHA_NAME = "captcha.position";
    private static final String LASTCLICK_NAME = "captcha.lastclick";

    private final Random rnd = new Random();

    @Resource
    private CaptchaGenerator captchaGenerator;

    @Override
    public BufferedImage createCaptcha(HttpSession session) {
        int captchaPos = computeCaptchaPosition(session);
        return captchaGenerator.createCaptcha(computeChars(captchaPos));
    }

    @Override
    public boolean isValidCaptcha(HttpSession session, int x, int y) {
        Integer pos = getCaptchaPosition(session);

        if (pos == null) {
            // There was no captcha generated yet, so the answer is always false.
            return false;
        }

        int cw = captchaGenerator.getWidth();
        int ch = captchaGenerator.getHeight();

        if (x < 0 || y < 0 || x >= cw || y >= ch) {
            // The click was outside of the captcha, so the answer is always false.
            return false;
        }

        if (x == 0 && y == 0) {
            // Ignore the simplest possible coordinate. No human being would click
            // there... ;-)
            return false;
        }

        int boxWidth = cw / NUMBER_OF_CHARS;
        int answer = x / boxWidth;

        setLastclickPosition(session, answer);

        return answer == pos;
    }

    /**
     * Compute a random set of characters, with exactly one 'X' at the given position.
     *
     * @param pos
     *            position of the 'X'
     * @return captcha text
     */
    private char[] computeChars(int pos) {
        char[] chars = new char[NUMBER_OF_CHARS];

        for (int ix = 0; ix < NUMBER_OF_CHARS; ix++) {
            if (ix == pos) {
                chars[ix] = 'X';
            } else {
                chars[ix] = CHARSET.charAt(rnd.nextInt(CHARSET.length()));
            }
        }

        return chars;
    }

    /**
     * Computes the position of the correct captcha answer.
     * <p>
     * Makes sure the new correct answer is never at the same position as the previous
     * click. This will keep spammers from just clicking at the same position until they
     * gave the right answer by lucky chance.
     *
     * @param session
     *            {@link HttpSession} with captcha data
     * @return position of the correct answer
     */
    private int computeCaptchaPosition(HttpSession session) {
        int newPos;

        Integer oldPos = getLastclickPosition(session);
        if (oldPos != null) {
            // Make sure newPos is always != oldPos
            newPos = rnd.nextInt(NUMBER_OF_CHARS - 1);
            if (newPos >= oldPos)
                newPos++;
        } else {
            newPos = rnd.nextInt(NUMBER_OF_CHARS);
        }

        setCaptchaPosition(session, newPos);
        return newPos;
    }

    /**
     * Gets the last captcha position from the session.
     *
     * @param session
     *            {@link HttpSession}
     * @return the last captcha position, or {@code null} if there is none yet
     */
    private Integer getCaptchaPosition(HttpSession session) {
        return (Integer) session.getAttribute(CAPTCHA_NAME);
    }

    /**
     * Sets the captcha position.
     *
     * @param session
     *            {@link HttpSession}
     * @param pos
     *            captcha position to store
     */
    private void setCaptchaPosition(HttpSession session, int pos) {
        session.setAttribute(CAPTCHA_NAME, pos);
    }

    /**
     * Gets the position of the last click.
     *
     * @param session
     *            {@link HttpSession}
     * @return position of the last click, or {@code null} if the user did not click yet
     */
    private Integer getLastclickPosition(HttpSession session) {
        return (Integer) session.getAttribute(LASTCLICK_NAME);
    }

    /**
     * Sets the position of the last click.
     *
     * @param session
     *            {@link HttpSession}
     * @param pos
     *            position of the last click
     */
    private void setLastclickPosition(HttpSession session, int pos) {
        session.setAttribute(LASTCLICK_NAME, pos);
    }

}