/*
* File : $Source: /alkacon/cvs/alkacon/com.alkacon.opencms.formgenerator/src/com/alkacon/opencms/formgenerator/CmsCaptchaField.java,v $
* Date : $Date: 2011/03/09 15:14:35 $
* Version: $Revision: 1.12 $
*
* This file is part of the Alkacon OpenCms Add-On Module Package
*
* Copyright (c) 2010 Alkacon Software GmbH (http://www.alkacon.com)
*
* The Alkacon OpenCms Add-On Module Package is free software:
* you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alkacon OpenCms Add-On Module Package 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 General Public License
* along with the Alkacon OpenCms Add-On Module Package.
* If not, see http://www.gnu.org/licenses/.
*
* For further information about Alkacon Software GmbH, please see the
* company website: http://www.alkacon.com.
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org.
*/
package com.alkacon.opencms.formgenerator;
import org.opencms.flex.CmsFlexController;
import org.opencms.i18n.CmsMessages;
import org.opencms.jsp.CmsJspActionElement;
import org.opencms.main.CmsLog;
import org.opencms.util.CmsStringUtil;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import com.octo.captcha.CaptchaException;
import com.octo.captcha.service.CaptchaService;
import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.image.ImageCaptchaService;
import com.octo.captcha.service.text.TextCaptchaService;
/**
* Creates captcha images and validates the pharses submitted by a request parameter.
* <p>
*
* @author Thomas Weckert
*
* @author Achim Westermann
*
* @version $Revision: 1.12 $
*
* @since 7.0.4
*/
public class CmsCaptchaField extends A_CmsField {
/** Request parameter name of the captcha phrase. */
public static final String C_PARAM_CAPTCHA_PHRASE = "captchaphrase";
/** Session parameter name to store the webform captcha settings. */
protected static final String SESSION_PARAM_CAPTCHASETTINGS = "__oamp_webform_captchasettings";
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsCaptchaField.class);
/** HTML field type: captcha image. */
private static final String TYPE = "captcha";
/** The settings to render captcha images. */
private CmsCaptchaSettings m_captchaSettings;
/**
* Creates a new captcha field.
* <p>
*
* @param captchaSettings the settings to render captcha images
* @param fieldLabel the localized label of this field
* @param fieldValue the submitted value of this field
*/
public CmsCaptchaField(CmsCaptchaSettings captchaSettings, String fieldLabel, String fieldValue) {
super();
m_captchaSettings = captchaSettings;
setName(C_PARAM_CAPTCHA_PHRASE);
setValue(fieldValue);
setLabel(fieldLabel);
setMandatory(true);
}
/**
* Returns the type of the input field, e.g. "text" or "select".
* <p>
*
* @return the type of the input field
*/
public static String getStaticType() {
return TYPE;
}
/**
* @see com.alkacon.opencms.formgenerator.I_CmsField#buildHtml(CmsFormHandler, CmsMessages, String, boolean, String)
*/
@Override
public String buildHtml(
CmsFormHandler formHandler,
CmsMessages messages,
String errorKey,
boolean showMandatory,
String infoKey) {
StringBuffer captchaHtml = new StringBuffer(256);
String errorMessage = createStandardErrorMessage(errorKey, messages);
CmsCaptchaSettings captchaSettings = getCaptchaSettings();
if (m_captchaSettings.isMathField()) {
// this is a math captcha, print the challenge directly
String sessionId = formHandler.getRequest().getSession(true).getId();
TextCaptchaService service = (TextCaptchaService)CmsCaptchaServiceCache.getSharedInstance().getCaptchaService(
m_captchaSettings,
formHandler.getCmsObject());
captchaHtml.append("<div style=\"margin: 0 0 2px 0;\">");
captchaHtml.append(service.getTextChallengeForID(
sessionId,
formHandler.getCmsObject().getRequestContext().getLocale()));
captchaHtml.append("</div>\n");
} else {
// image captcha, insert image
captchaHtml.append("<img id=\"form_captcha_id\" src=\"").append(
formHandler.link("/system/modules/com.alkacon.opencms.formgenerator/pages/captcha.jsp?"
+ captchaSettings.toRequestParams(formHandler.getCmsObject())
+ "#"
+ System.currentTimeMillis())).append("\" width=\"").append(captchaSettings.getImageWidth()).append(
"\" height=\"").append(captchaSettings.getImageHeight()).append("\" alt=\"\"/>").append("\n");
captchaHtml.append("<br/>\n");
}
Map<String, Object> stAttributes = new HashMap<String, Object>();
// set captcha HTML code as additional attribute
stAttributes.put("captcha", captchaHtml.toString());
return createHtml(formHandler, messages, stAttributes, getType(), null, errorMessage, showMandatory);
}
/**
* Returns the captcha settings of this field.
* <p>
*
* @return the captcha settings of this field
*/
public CmsCaptchaSettings getCaptchaSettings() {
return m_captchaSettings;
}
/**
* @see com.alkacon.opencms.formgenerator.I_CmsField#getType()
*/
public String getType() {
return TYPE;
}
/**
* Validates the captcha phrase entered by the user.
* <p>
*
* @param jsp the Cms JSP
* @param captchaPhrase the captcha phrase to be validate
* @return true, if the captcha phrase entered by the user is correct, false otherwise
*/
public boolean validateCaptchaPhrase(CmsJspActionElement jsp, String captchaPhrase) {
boolean result = false;
CmsCaptchaSettings settings = m_captchaSettings;
// check if there are changed captcha settings stored in the session (true if first image generation failed)
CmsCaptchaSettings sessionSettings = (CmsCaptchaSettings)jsp.getRequest().getSession().getAttribute(
SESSION_PARAM_CAPTCHASETTINGS);
if (sessionSettings != null) {
// use captcha settings from session to validate the response
settings = sessionSettings;
jsp.getRequest().getSession().removeAttribute(SESSION_PARAM_CAPTCHASETTINGS);
}
String sessionId = jsp.getRequest().getSession().getId();
if (CmsStringUtil.isNotEmpty(captchaPhrase)) {
// try to validate the phrase
try {
CaptchaService captchaService = CmsCaptchaServiceCache.getSharedInstance().getCaptchaService(
settings,
jsp.getCmsObject());
if (captchaService != null) {
result = captchaService.validateResponseForID(sessionId, captchaPhrase).booleanValue();
}
} catch (CaptchaServiceException cse) {
// most often this will be
// "com.octo.captcha.service.CaptchaServiceException: Invalid ID, could not validate unexisting or already validated captcha"
// in case someone hits the back button and submits again
}
}
return result;
}
/**
* Writes a Captcha JPEG image to the servlet response output stream.
* <p>
*
* @param cms an initialized Cms JSP action element
* @throws IOException if something goes wrong
*/
public void writeCaptchaImage(CmsJspActionElement cms) throws IOException {
// remove eventual session attribute containing captcha settings
cms.getRequest().getSession().removeAttribute(SESSION_PARAM_CAPTCHASETTINGS);
String sessionId = cms.getRequest().getSession().getId();
Locale locale = cms.getRequestContext().getLocale();
BufferedImage captchaImage = null;
int maxTries = 10;
do {
try {
maxTries--;
captchaImage = ((ImageCaptchaService)CmsCaptchaServiceCache.getSharedInstance().getCaptchaService(
m_captchaSettings,
cms.getCmsObject())).getImageChallengeForID(sessionId, locale);
} catch (CaptchaException cex) {
// image size is too small, increase dimensions and try it again
if (LOG.isInfoEnabled()) {
LOG.info(cex);
LOG.info(Messages.get().getBundle().key(
Messages.LOG_ERR_CAPTCHA_CONFIG_IMAGE_SIZE_2,
new Object[] {m_captchaSettings.getPresetPath(), new Integer(maxTries)}));
}
m_captchaSettings.setImageHeight((int)(m_captchaSettings.getImageHeight() * 1.1));
m_captchaSettings.setImageWidth((int)(m_captchaSettings.getImageWidth() * 1.1));
// IMPORTANT: store changed captcha settings in session, they have to be used when validating the phrase
cms.getRequest().getSession().setAttribute(SESSION_PARAM_CAPTCHASETTINGS, m_captchaSettings.clone());
}
} while ((captchaImage == null) && (maxTries > 0));
ServletOutputStream out = null;
try {
CmsFlexController controller = CmsFlexController.getController(cms.getRequest());
HttpServletResponse response = controller.getTopResponse();
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
ByteArrayOutputStream captchaImageOutput = new ByteArrayOutputStream();
ImageIO.write(captchaImage, "jpg", captchaImageOutput);
out = cms.getResponse().getOutputStream();
out.write(captchaImageOutput.toByteArray());
out.flush();
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error(e.getLocalizedMessage(), e);
}
cms.getResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
try {
if (out != null) {
out.close();
}
} catch (Throwable t) {
// intentionally left blank
}
}
}
}