blob: d8bb6bda1ec8a32c2f3481b74ca309bc50995572 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/
package org.apache.roller.weblogger.ui.rendering.servlets;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Iterator;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.roller.util.RollerConstants;
import org.apache.roller.weblogger.WebloggerException;
import org.apache.roller.weblogger.config.WebloggerConfig;
import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
import org.apache.roller.weblogger.business.search.IndexManager;
import org.apache.roller.weblogger.business.WebloggerFactory;
import org.apache.roller.weblogger.business.WeblogEntryManager;
import org.apache.roller.weblogger.pojos.WeblogEntryComment;
import org.apache.roller.weblogger.pojos.WeblogEntryComment.ApprovalStatus;
import org.apache.roller.weblogger.pojos.WeblogEntry;
import org.apache.roller.weblogger.pojos.Weblog;
import org.apache.roller.weblogger.ui.rendering.plugins.comments.CommentAuthenticator;
import org.apache.roller.weblogger.ui.rendering.plugins.comments.CommentValidationManager;
import org.apache.roller.weblogger.ui.rendering.plugins.comments.DefaultCommentAuthenticator;
import org.apache.roller.weblogger.ui.rendering.util.WeblogCommentRequest;
import org.apache.roller.weblogger.ui.rendering.util.WeblogEntryCommentForm;
import org.apache.roller.weblogger.util.GenericThrottle;
import org.apache.roller.weblogger.util.IPBanList;
import org.apache.roller.weblogger.util.MailUtil;
import org.apache.roller.weblogger.util.I18nMessages;
import org.apache.roller.weblogger.util.RollerMessages;
import org.apache.roller.weblogger.util.RollerMessages.RollerMessage;
import org.apache.roller.weblogger.util.URLUtilities;
import org.apache.roller.weblogger.util.Utilities;
import org.apache.roller.weblogger.util.cache.CacheManager;
/**
* The CommentServlet handles all incoming weblog entry comment posts.
*
* We validate each incoming comment based on various comment settings and if
* all checks are passed then the comment is saved.
*
* Incoming comments are tested against the MT Bannedwordslist. If they are found to
* be spam, then they are marked as spam and hidden from view.
*
* If email notification is turned on, each new comment will result in an email
* sent to the blog owner and all who have commented on the same post.
*/
public class CommentServlet extends HttpServlet {
private static Log log = LogFactory.getLog(CommentServlet.class);
private CommentAuthenticator authenticator = null;
private CommentValidationManager commentValidationManager = null;
private GenericThrottle commentThrottle = null;
/**
* Initialization.
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
log.info("Initializing CommentServlet");
// lookup the authenticator we are going to use and instantiate it
try {
String name = WebloggerConfig
.getProperty("comment.authenticator.classname");
Class clazz = Class.forName(name);
this.authenticator = (CommentAuthenticator) clazz.newInstance();
} catch (Exception e) {
log.error(e);
this.authenticator = new DefaultCommentAuthenticator();
}
// instantiate a comment validation manager for comment spam checking
commentValidationManager = new CommentValidationManager();
// are we doing throttling?
if (WebloggerConfig.getBooleanProperty("comment.throttle.enabled")) {
int threshold = 25;
try {
threshold = Integer.parseInt(WebloggerConfig
.getProperty("comment.throttle.threshold"));
} catch (Exception e) {
log.warn(
"bad input for config property comment.throttle.threshold",
e);
}
int interval = RollerConstants.MIN_IN_MS;
try {
interval = Integer.parseInt(WebloggerConfig
.getProperty("comment.throttle.interval"));
// convert from seconds to milliseconds
interval = interval * RollerConstants.SEC_IN_MS;
} catch (Exception e) {
log.warn(
"bad input for config property comment.throttle.interval",
e);
}
int maxEntries = 250;
try {
maxEntries = Integer.parseInt(WebloggerConfig
.getProperty("comment.throttle.maxentries"));
} catch (Exception e) {
log.warn(
"bad input for config property comment.throttle.maxentries",
e);
}
commentThrottle = new GenericThrottle(threshold, interval,
maxEntries);
log.info("Comment Throttling ENABLED");
} else {
log.info("Comment Throttling DISABLED");
}
}
/**
* Handle incoming http GET requests.
*
* The CommentServlet does not support GET requests, it's a 404.
*/
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
/**
* Service incoming POST requests.
*
* Here we handle incoming comment postings.
*/
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String error = null;
String dispatch_url;
Weblog weblog;
WeblogEntry entry;
String message = null;
RollerMessages messages = new RollerMessages();
// are we doing a preview? or a post?
String method = request.getParameter("method");
final boolean preview;
if (method != null && method.equals("preview")) {
preview = true;
messages.addMessage("commentServlet.previewCommentOnly");
log.debug("Handling comment preview post");
} else {
preview = false;
log.debug("Handling regular comment post");
}
// throttling protection against spammers
if (commentThrottle != null
&& commentThrottle.processHit(request.getRemoteAddr())) {
log.debug("ABUSIVE " + request.getRemoteAddr());
IPBanList.getInstance().addBannedIp(request.getRemoteAddr());
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
WeblogCommentRequest commentRequest;
try {
commentRequest = new WeblogCommentRequest(request);
// lookup weblog specified by comment request
weblog = WebloggerFactory.getWeblogger().getWeblogManager()
.getWeblogByHandle(commentRequest.getWeblogHandle());
if (weblog == null) {
throw new WebloggerException("unable to lookup weblog: "
+ commentRequest.getWeblogHandle());
}
// lookup entry specified by comment request
entry = commentRequest.getWeblogEntry();
if (entry == null) {
throw new WebloggerException("unable to lookup entry: "
+ commentRequest.getWeblogAnchor());
}
// we know what the weblog entry is, so setup our urls
dispatch_url = "/roller-ui/rendering/page/" + weblog.getHandle();
if (commentRequest.getLocale() != null) {
dispatch_url += "/" + commentRequest.getLocale();
}
dispatch_url += "/entry/"
+ URLUtilities.encode(commentRequest.getWeblogAnchor());
} catch (Exception e) {
// some kind of error parsing the request or looking up weblog
log.debug("error creating page request", e);
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
log.debug("Doing comment posting for entry = " + entry.getPermalink());
// collect input from request params and construct new comment object
// fields: name, email, url, content, notify
// TODO: data validation on collected comment data
WeblogEntryComment comment = new WeblogEntryComment();
comment.setName(commentRequest.getName());
comment.setEmail(commentRequest.getEmail());
// Validate url
if (StringUtils.isNotEmpty(commentRequest.getUrl())) {
String theUrl = commentRequest.getUrl().trim().toLowerCase();
StringBuilder url = new StringBuilder();
if (theUrl.startsWith("http://")) {
url.append(theUrl);
} else if (theUrl.startsWith("https://")) {
url.append(theUrl);
} else {
url.append("http://").append(theUrl);
}
comment.setUrl(url.toString());
} else {
comment.setUrl("");
}
comment.setContent(commentRequest.getContent());
comment.setNotify(commentRequest.isNotify());
comment.setWeblogEntry(entry);
comment.setRemoteHost(request.getRemoteHost());
comment.setPostTime(new Timestamp(System.currentTimeMillis()));
// set comment content-type depending on if html is allowed
if (WebloggerRuntimeConfig
.getBooleanProperty("users.comments.htmlenabled")) {
comment.setContentType("text/html");
} else {
comment.setContentType("text/plain");
}
// set whatever comment plugins are configured
comment.setPlugins(WebloggerRuntimeConfig
.getProperty("users.comments.plugins"));
WeblogEntryCommentForm cf = new WeblogEntryCommentForm();
cf.setData(comment);
if (preview) {
cf.setPreview(comment);
}
I18nMessages messageUtils = I18nMessages.getMessages(commentRequest
.getLocaleInstance());
// check if comments are allowed for this entry
// this checks site-wide settings, weblog settings, and entry settings
if (!entry.getCommentsStillAllowed() || !entry.isPublished()) {
error = messageUtils.getString("comments.disabled");
// Must have an email and also must be valid
} else if (StringUtils.isEmpty(commentRequest.getEmail())
|| StringUtils.isNotEmpty(commentRequest.getEmail())
&& !Utilities.isValidEmailAddress(commentRequest.getEmail())) {
error = messageUtils
.getString("error.commentPostFailedEmailAddress");
log.debug("Email Adddress is invalid : "
+ commentRequest.getEmail());
// if there is an URL it must be valid
} else if (StringUtils.isNotEmpty(comment.getUrl())
&& !new UrlValidator(new String[] { "http", "https" })
.isValid(comment.getUrl())) {
error = messageUtils.getString("error.commentPostFailedURL");
log.debug("URL is invalid : " + comment.getUrl());
// if this is a real comment post then authenticate request
} else if (!preview && !this.authenticator.authenticate(request)) {
String[] msg = { request.getParameter("answer") };
error = messageUtils.getString("error.commentAuthFailed", msg);
log.debug("Comment failed authentication");
}
// bail now if we have already found an error
if (error != null) {
cf.setError(error);
request.setAttribute("commentForm", cf);
RequestDispatcher dispatcher = request
.getRequestDispatcher(dispatch_url);
dispatcher.forward(request, response);
return;
}
int validationScore = commentValidationManager.validateComment(comment,
messages);
log.debug("Comment Validation score: " + validationScore);
if (!preview) {
if (validationScore == RollerConstants.PERCENT_100
&& weblog.getCommentModerationRequired()) {
// Valid comments go into moderation if required
comment.setStatus(ApprovalStatus.PENDING);
message = messageUtils
.getString("commentServlet.submittedToModerator");
} else if (validationScore == RollerConstants.PERCENT_100) {
// else they're approved
comment.setStatus(ApprovalStatus.APPROVED);
message = messageUtils
.getString("commentServlet.commentAccepted");
} else {
// Invalid comments are marked as spam
log.debug("Comment marked as spam");
comment.setStatus(ApprovalStatus.SPAM);
error = messageUtils
.getString("commentServlet.commentMarkedAsSpam");
// add specific error messages if they exist
if (messages.getErrorCount() > 0) {
Iterator errors = messages.getErrors();
RollerMessage errorKey;
StringBuilder buf = new StringBuilder();
buf.append("<ul>");
while (errors.hasNext()) {
errorKey = (RollerMessage) errors.next();
buf.append("<li>");
if (errorKey.getArgs() != null) {
buf.append(messageUtils.getString(
errorKey.getKey(), errorKey.getArgs()));
} else {
buf.append(messageUtils.getString(errorKey.getKey()));
}
buf.append("</li>");
}
buf.append("</ul>");
error += buf.toString();
}
}
try {
if (!ApprovalStatus.SPAM.equals(comment.getStatus())
|| !WebloggerRuntimeConfig
.getBooleanProperty("comments.ignoreSpam.enabled")) {
WeblogEntryManager mgr = WebloggerFactory.getWeblogger()
.getWeblogEntryManager();
mgr.saveComment(comment);
WebloggerFactory.getWeblogger().flush();
// Send email notifications only to subscribers if comment
// is 100% valid
boolean notifySubscribers = (validationScore == RollerConstants.PERCENT_100);
MailUtil.sendEmailNotification(comment, messages,
messageUtils, notifySubscribers);
// only re-index/invalidate the cache if comment isn't
// moderated
if (!weblog.getCommentModerationRequired()) {
IndexManager manager = WebloggerFactory.getWeblogger()
.getIndexManager();
// remove entry before (re)adding it, or in case it
// isn't Published
manager.removeEntryIndexOperation(entry);
// if published, index the entry
if (entry.isPublished()) {
manager.addEntryIndexOperation(entry);
}
// Clear all caches associated with comment
CacheManager.invalidate(comment);
}
// comment was successful, clear the comment form
cf = new WeblogEntryCommentForm();
}
} catch (WebloggerException re) {
log.error("Error saving comment", re);
error = re.getMessage();
}
}
// the work has been done, now send the user back to the entry page
if (error != null) {
cf.setError(error);
}
if (message != null) {
cf.setMessage(message);
}
request.setAttribute("commentForm", cf);
log.debug("comment processed, forwarding to " + dispatch_url);
RequestDispatcher dispatcher = request
.getRequestDispatcher(dispatch_url);
dispatcher.forward(request, response);
}
}