Retire incoming Trackback support and normalize comment links (#178)

* Remove legacy outbound trackback action

* Retire incoming Trackback support and normalize comment links

* Remove remaining outbound Trackback references

* Preserve compatibility while retiring incoming Trackback
diff --git a/app/src/main/java/org/apache/roller/weblogger/pojos/WeblogEntryComment.java b/app/src/main/java/org/apache/roller/weblogger/pojos/WeblogEntryComment.java
index 06861d5..727788c 100644
--- a/app/src/main/java/org/apache/roller/weblogger/pojos/WeblogEntryComment.java
+++ b/app/src/main/java/org/apache/roller/weblogger/pojos/WeblogEntryComment.java
@@ -23,6 +23,7 @@
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.roller.util.UUIDGenerator;
+import org.apache.roller.weblogger.util.CommentAuthorUrl;
 
 
 /**
@@ -130,6 +131,13 @@
     public void setUrl(String url) {
         this.url = url;
     }
+
+    /**
+     * URL of the comment writer when it can be safely rendered as a link.
+     */
+    public String getSafeUrl() {
+        return CommentAuthorUrl.normalize(this.url);
+    }
     
     
     /**
diff --git a/app/src/main/java/org/apache/roller/weblogger/pojos/wrapper/WeblogEntryCommentWrapper.java b/app/src/main/java/org/apache/roller/weblogger/pojos/wrapper/WeblogEntryCommentWrapper.java
index 639b31b..073bbe7 100644
--- a/app/src/main/java/org/apache/roller/weblogger/pojos/wrapper/WeblogEntryCommentWrapper.java
+++ b/app/src/main/java/org/apache/roller/weblogger/pojos/wrapper/WeblogEntryCommentWrapper.java
@@ -38,12 +38,17 @@
     
     // url strategy to use for any url building
     private final URLStrategy urlStrategy;
+
+    private final String safeUrl;
     
     
     // this is private so that we can force the use of the .wrap(pojo) method
     private WeblogEntryCommentWrapper(WeblogEntryComment toWrap, URLStrategy strat) {
         this.pojo = toWrap;
         this.urlStrategy = strat;
+        String normalizedUrl = toWrap.getSafeUrl();
+        this.safeUrl = normalizedUrl == null
+                ? "" : StringEscapeUtils.escapeHtml4(normalizedUrl);
     }
     
     
@@ -93,7 +98,7 @@
      * Value is always html escaped.
      */
     public String getUrl() {
-        return StringEscapeUtils.escapeHtml4(this.pojo.getUrl());
+        return safeUrl;
     }
     
     
@@ -147,7 +152,7 @@
     
     
     /**
-     * Get the http referrer of the comment poster, used for trackbacks.
+     * Get the HTTP referrer of the comment poster.
      *
      * Value is always html escaped.
      */
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/WeblogRequestMapper.java b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/WeblogRequestMapper.java
index 693be62..ee12cb7 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/WeblogRequestMapper.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/WeblogRequestMapper.java
@@ -56,7 +56,6 @@
     private static final String RSD_SERVLET = "/roller-ui/rendering/rsd";
     
     private static final String COMMENT_SERVLET = "/roller-ui/rendering/comment";
-    private static final String TRACKBACK_SERVLET = "/roller-ui/rendering/trackback";
     
     
     // url patterns that are not allowed to be considered weblog handles
@@ -259,45 +258,24 @@
         
         StringBuilder forwardUrl = new StringBuilder(64);
         
-        // POST urls, like comment and trackback servlets
+        // POST URLs for the comment servlet
         if("POST".equals(request.getMethod())) {
-            // posting to permalink, this means comment or trackback
-            if(context.equals("entry")) {
-                // trackback requests are required to have an "excerpt" param
-                if(request.getParameter("excerpt") != null) {
+            // Comment requests post content to a permalink.
+            if("entry".equals(context) && request.getParameter("content") != null) {
                     
-                    forwardUrl.append(TRACKBACK_SERVLET);
+                forwardUrl.append(COMMENT_SERVLET);
+                forwardUrl.append('/');
+                forwardUrl.append(handle);
+                if(locale != null) {
                     forwardUrl.append('/');
-                    forwardUrl.append(handle);
-                    if(locale != null) {
-                        forwardUrl.append('/');
-                        forwardUrl.append(locale);
-                    }
-                    forwardUrl.append('/');
-                    forwardUrl.append(context);
-                    if(data != null) {
-                        forwardUrl.append('/');
-                        forwardUrl.append(data);
-                    }
-                    
-                // comment requests are required to have a "content" param
-                } else if(request.getParameter("content") != null) {
-                    
-                    forwardUrl.append(COMMENT_SERVLET);
-                    forwardUrl.append('/');
-                    forwardUrl.append(handle);
-                    if(locale != null) {
-                        forwardUrl.append('/');
-                        forwardUrl.append(locale);
-                    }
-                    forwardUrl.append('/');
-                    forwardUrl.append(context);
-                    if(data != null) {
-                        forwardUrl.append('/');
-                        forwardUrl.append(data);
-                    }
+                    forwardUrl.append(locale);
                 }
-                
+                forwardUrl.append('/');
+                forwardUrl.append(context);
+                if(data != null) {
+                    forwardUrl.append('/');
+                    forwardUrl.append(data);
+                }
             } else {
                 // someone posting data where they aren't supposed to
                 return null;
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/model/ConfigModel.java b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/model/ConfigModel.java
index fb86421..3a91207 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/model/ConfigModel.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/model/ConfigModel.java
@@ -101,11 +101,6 @@
         return getBooleanProperty("users.comments.emailnotify");
     }
     
-    public boolean getTrackbacksEnabled() {
-        return getBooleanProperty("users.trackbacks.enabled");
-    }
-    
-    
     /** Get Roller version string */
     public String getRollerVersion() {
         return WebloggerFactory.getWeblogger().getVersion();
@@ -146,4 +141,3 @@
     }
     
 }
-
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/model/URLModel.java b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/model/URLModel.java
index 7ca561a..a548bd8 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/model/URLModel.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/model/URLModel.java
@@ -137,6 +137,16 @@
     public String getCommentAuthenticator() {
         return getSite()+"/CommentAuthenticatorServlet?weblog="+weblog.getHandle();
     }
+
+    /**
+     * Retained temporarily so older custom templates continue to render.
+     *
+     * @deprecated This endpoint is no longer available.
+     */
+    @Deprecated(since = "6.1.6", forRemoval = true)
+    public String trackback(String anchor) {
+        return "";
+    }
     
     
     public String themeResource(String theme, String filePath) {
@@ -184,11 +194,6 @@
     }
     
     
-    public String trackback(String anchor) {
-        return urlStrategy.getWeblogEntryURL(weblog, locale, anchor, true);
-    }
-
-    
     public String date(String dateString) {
         return urlStrategy.getWeblogCollectionURL(weblog, locale, null, dateString, null, -1, true);
     }
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/plugins/comments/TrackbackLinkbackCommentValidator.java b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/plugins/comments/TrackbackLinkbackCommentValidator.java
index d722328..f2efdc8 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/plugins/comments/TrackbackLinkbackCommentValidator.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/plugins/comments/TrackbackLinkbackCommentValidator.java
@@ -1,9 +1,10 @@
 /*
  * 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
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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
  *
@@ -11,65 +12,29 @@
  * 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.
+ * limitations under the License.
  */
-
 package org.apache.roller.weblogger.ui.rendering.plugins.comments;
 
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.util.ResourceBundle;
-
 import org.apache.roller.util.RollerConstants;
-import org.apache.roller.weblogger.business.WebloggerFactory;
-import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
 import org.apache.roller.weblogger.pojos.WeblogEntryComment;
-import org.apache.roller.weblogger.util.LinkbackExtractor;
 import org.apache.roller.weblogger.util.RollerMessages;
 
 /**
- * Validates comment if comment's URL links back to the comment's entry,
- * intended for use with trackbacks only.
+ * No-op retained temporarily for installations that still name this plugin.
+ *
+ * @deprecated The associated protocol endpoint is no longer available.
  */
+@Deprecated(since = "6.1.6", forRemoval = true)
 public class TrackbackLinkbackCommentValidator implements CommentValidator {
-    
-    private ResourceBundle bundle = ResourceBundle.getBundle("ApplicationResources");
-    
+
     @Override
     public String getName() {
-        return bundle.getString("comment.validator.trackbackLinkbackName");
+        return "Compatibility comment validator";
     }
-    
+
     @Override
     public int validate(WeblogEntryComment comment, RollerMessages messages) {
-        
-        // linkback validation can be toggled at runtime, so check if it's enabled
-        // if it's disabled then just return a score of 100
-        if(!WebloggerRuntimeConfig.getBooleanProperty("site.trackbackVerification.enabled")) {
-            return RollerConstants.PERCENT_100;
-        }
-        
-        int ret = 0;
-        LinkbackExtractor linkback = null;
-        try {
-            linkback = new LinkbackExtractor(
-                    comment.getUrl(),
-                    WebloggerFactory.getWeblogger().getUrlStrategy().getWeblogEntryURL(
-                    comment.getWeblogEntry().getWebsite(),
-                    null,
-                    comment.getWeblogEntry().getAnchor(),
-                    true));
-        } catch (MalformedURLException ignored1) {
-        } catch (IOException ignored2) {}
-        
-        if (linkback != null && linkback.getExcerpt() != null) {
-            ret = RollerConstants.PERCENT_100;
-        } else {
-            messages.addError("comment.validator.trackbackLinkbackMessage");
-        }
-        return ret;
+        return RollerConstants.PERCENT_100;
     }
-    
 }
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/servlets/CommentServlet.java b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/servlets/CommentServlet.java
index 677a8f9..2e2b5bd 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/servlets/CommentServlet.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/servlets/CommentServlet.java
@@ -32,7 +32,6 @@
 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;
@@ -49,6 +48,7 @@
 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.CommentAuthorUrl;
 import org.apache.roller.weblogger.util.GenericThrottle;
 import org.apache.roller.weblogger.util.IPBanList;
 import org.apache.roller.weblogger.util.MailUtil;
@@ -231,21 +231,10 @@
         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("");
-        }
+        String submittedCommentUrl = StringUtils.trimToEmpty(commentRequest.getUrl());
+        String normalizedCommentUrl = CommentAuthorUrl.normalizeInput(submittedCommentUrl);
+        comment.setUrl(normalizedCommentUrl != null
+                ? normalizedCommentUrl : submittedCommentUrl);
         
         comment.setContent(commentRequest.getContent());
         comment.setNotify(commentRequest.isNotify());
@@ -288,9 +277,8 @@
             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())) {
+        } else if (StringUtils.isNotEmpty(submittedCommentUrl)
+                && normalizedCommentUrl == null) {
                 error = messageUtils.getString("error.commentPostFailedURL");
                 log.debug("URL is invalid : " + comment.getUrl());
             // if this is a real comment post then authenticate request
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/servlets/TrackbackServlet.java b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/servlets/TrackbackServlet.java
deleted file mode 100644
index 2b43a4e..0000000
--- a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/servlets/TrackbackServlet.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.io.PrintWriter;
-import java.sql.Timestamp;
-import java.util.Date;
-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.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.roller.util.RollerConstants;
-import org.apache.roller.weblogger.WebloggerException;
-import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
-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.CommentValidationManager;
-import org.apache.roller.weblogger.ui.rendering.plugins.comments.TrackbackLinkbackCommentValidator;
-import org.apache.roller.weblogger.ui.rendering.util.WeblogTrackbackRequest;
-import org.apache.roller.weblogger.util.I18nMessages;
-import org.apache.roller.weblogger.util.MailUtil;
-import org.apache.roller.weblogger.util.RollerMessages;
-import org.apache.roller.weblogger.util.cache.CacheManager;
-
-
-/**
- * Roller's Trackback server implementation. POSTing to this Servlet will add a
- * Trackback to a Weblog Entry. For more info on Trackback, read the spec:
- * <a href="http://www.movabletype.org/documentation/trackback/specification.html">MT Trackback</a>.
- */
-public class TrackbackServlet extends HttpServlet { 
-    
-    private static Log logger = LogFactory.getLog(TrackbackServlet.class);
-    
-    private CommentValidationManager commentValidationManager = null;
-    
-
-    @Override
-    public void init(ServletConfig config) throws ServletException {
-        commentValidationManager = new CommentValidationManager();
-        
-        // add trackback verification validator just for trackbacks
-        commentValidationManager.addCommentValidator(new TrackbackLinkbackCommentValidator());
-    }
-    
-    
-    /**
-     * Handle incoming http GET requests.
-     *
-     * The TrackbackServlet 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 trackback posts.
-     */
-    @Override
-    public void doPost(HttpServletRequest request, HttpServletResponse response)
-            throws ServletException, IOException {
-        
-        String error = null;
-        PrintWriter pw = response.getWriter();
-        
-        Weblog weblog = null;
-        WeblogEntry entry = null;
-        
-        RollerMessages messages = new RollerMessages();
-        
-        WeblogTrackbackRequest trackbackRequest = null;
-        if (!WebloggerRuntimeConfig.getBooleanProperty("users.trackbacks.enabled")) {
-            error = "Trackbacks are disabled for this site";
-        } else {
-            
-            try {
-                trackbackRequest = new WeblogTrackbackRequest(request);
-                
-                if ((trackbackRequest.getTitle() == null) ||
-                        "".equals(trackbackRequest.getTitle())) {
-                    trackbackRequest.setTitle(trackbackRequest.getUrl());
-                }
-                
-                if (trackbackRequest.getExcerpt() == null) {
-                    trackbackRequest.setExcerpt("");
-                } else if (trackbackRequest.getExcerpt().length() >= RollerConstants.TEXTWIDTH_255) {
-                    trackbackRequest.setExcerpt(trackbackRequest.getExcerpt().substring(0,
-                            RollerConstants.TEXTWIDTH_255 - 3)+"...");
-                }
-                
-                // lookup weblog specified by comment request
-                weblog = WebloggerFactory.getWeblogger().getWeblogManager()
-                        .getWeblogByHandle(trackbackRequest.getWeblogHandle());
-                
-                if (weblog == null) {
-                    throw new WebloggerException("unable to lookup weblog: "+
-                            trackbackRequest.getWeblogHandle());
-                }
-                
-                // lookup entry specified by comment request
-                WeblogEntryManager weblogMgr = WebloggerFactory.getWeblogger().getWeblogEntryManager();
-                entry = weblogMgr.getWeblogEntryByAnchor(weblog, trackbackRequest.getWeblogAnchor());
-                
-                if (entry == null) {
-                    throw new WebloggerException("unable to lookup entry: "+
-                            trackbackRequest.getWeblogAnchor());
-                }
-                
-            } catch (Exception e) {
-                // some kind of error parsing the request or looking up weblog
-                logger.debug("error creating trackback request", e);
-                error = e.getMessage();
-            }
-        }
-        
-        if (error != null) {
-            pw.println(this.getErrorResponse(error));
-            return;
-        }
-        
-        try {            
-            // check if trackbacks are allowed for this entry
-            // this checks site-wide settings, weblog settings, and entry settings
-            if (entry != null && entry.getCommentsStillAllowed() && entry.isPublished()) {
-                
-                // Track trackbacks as comments
-                WeblogEntryComment comment = new WeblogEntryComment();
-                comment.setContent("[Trackback] "+trackbackRequest.getExcerpt());
-                comment.setName(trackbackRequest.getBlogName());
-                comment.setUrl(trackbackRequest.getUrl());
-                comment.setWeblogEntry(entry);
-                comment.setRemoteHost(request.getRemoteHost());
-                comment.setNotify(Boolean.FALSE);
-                comment.setPostTime(new Timestamp(new Date().getTime()));
-                
-                // run new trackback through validators
-                int validationScore = commentValidationManager.validateComment(comment, messages);
-                logger.debug("Comment Validation score: " + validationScore);
-                
-                if (validationScore == RollerConstants.PERCENT_100 && weblog.getCommentModerationRequired()) {
-                    // Valid comments go into moderation if required
-                    comment.setStatus(ApprovalStatus.PENDING);
-                } else if (validationScore == RollerConstants.PERCENT_100) {
-                    // else they're approved
-                    comment.setStatus(ApprovalStatus.APPROVED);
-                } else {
-                    // Invalid comments are marked as spam
-                    comment.setStatus(ApprovalStatus.SPAM);
-                }
-                
-                // save, commit, send response
-                if (!ApprovalStatus.SPAM.equals(comment.getStatus()) ||
-                        !WebloggerRuntimeConfig.getBooleanProperty("trackbacks.ignoreSpam.enabled")) {
-                    
-                    WeblogEntryManager mgr = WebloggerFactory.getWeblogger().getWeblogEntryManager();
-                    mgr.saveComment(comment);
-                    WebloggerFactory.getWeblogger().flush();
-                    
-                    // only invalidate the cache if comment isn't moderated
-                    if(!weblog.getCommentModerationRequired()) {
-                        // Clear all caches associated with comment
-                        CacheManager.invalidate(comment);
-                    }
-                    
-                    // Send email notifications
-                    MailUtil.sendEmailNotification(comment, messages, 
-                            I18nMessages.getMessages(trackbackRequest.getLocaleInstance()),
-                            validationScore == RollerConstants.PERCENT_100);
-                    
-                    if (ApprovalStatus.PENDING.equals(comment.getStatus())) {
-                        pw.println(this.getSuccessResponse("Trackback submitted to moderator"));
-                    } else {
-                        pw.println(this.getSuccessResponse("Trackback accepted"));
-                    }
-                }
-                
-            } else if (entry!=null) {
-                error = "Comments and Trackbacks are disabled for the entry specified.";
-            } else {
-                error = "Entry not specified.";
-            }
-            
-        } catch (Exception e) {
-            error = e.getMessage();
-            if ( error == null ) {
-                error = e.getClass().getName();
-            }
-        }
-        
-        if(error!= null) {
-            pw.println(this.getErrorResponse(error));
-        }
-        
-    }
-    
-    
-    private String getSuccessResponse(String message) {
-        
-        StringBuilder output = new StringBuilder();
-        
-        output.append("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
-        output.append("<response>");
-        output.append("<error>0</error>");
-        output.append("<message>");
-        output.append(message);
-        output.append("</message>");
-        output.append("</response>");
-            
-        return output.toString();
-    }
-    
-    
-    private String getErrorResponse(String message) {
-        
-        StringBuilder output = new StringBuilder();
-        
-        output.append("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
-        output.append("<response>");
-        output.append("<error>1</error>");
-        output.append("<message>ERROR: ");
-        output.append(message);
-        output.append("</message>");
-        output.append("</response>");
-            
-        return output.toString();
-    }
-    
-}
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/util/WeblogTrackbackRequest.java b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/util/WeblogTrackbackRequest.java
deleted file mode 100644
index 11807c6..0000000
--- a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/util/WeblogTrackbackRequest.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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.util;
-
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import javax.servlet.http.HttpServletRequest;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.roller.weblogger.WebloggerException;
-import org.apache.roller.weblogger.business.WebloggerFactory;
-import org.apache.roller.weblogger.business.WeblogEntryManager;
-import org.apache.roller.weblogger.pojos.WeblogEntry;
-
-
-/**
- * Represents a request to post a weblog entry trackback.
- */
-public class WeblogTrackbackRequest extends WeblogRequest {
-    
-    private static Log log = LogFactory.getLog(WeblogTrackbackRequest.class);
-    
-    private static final String TRACKBACK_SERVLET = "/roller-ui/rendering/trackback";
-    
-    // lightweight attributes
-    private String blogName = null;
-    private String url = null;
-    private String excerpt = null;
-    private String title = null;
-    private String weblogAnchor = null;
-    
-    // heavyweight attributes
-    private WeblogEntry weblogEntry = null;
-    
-    
-    public WeblogTrackbackRequest() {}
-    
-    
-    public WeblogTrackbackRequest(HttpServletRequest request) 
-            throws InvalidRequestException {
-        
-        // let our parent take care of their business first
-        // parent determines weblog handle and locale if specified
-        super(request);
-        
-        String servlet = request.getServletPath();
-        
-        // we only want the path info left over from after our parents parsing
-        String pathInfo = this.getPathInfo();
-        
-        // was this request bound for the comment servlet?
-        if(servlet == null || !TRACKBACK_SERVLET.equals(servlet)) {
-            throw new InvalidRequestException("not a weblog trackback request, "+
-                    request.getRequestURL());
-        }
-        
-        
-        /*
-         * parse path info.  we expect ...
-         *
-         * /entry/<anchor> - permalink
-         */
-        if(pathInfo != null && !pathInfo.isBlank()) {
-            
-            // we should only ever get 2 path elements
-            String[] pathElements = pathInfo.split("/");
-            if(pathElements.length == 2) {
-                
-                String context = pathElements[0];
-                if("entry".equals(context)) {
-                    this.weblogAnchor = URLDecoder.decode(pathElements[1], StandardCharsets.UTF_8);
-                } else {
-                    throw new InvalidRequestException("bad path info, "+
-                            request.getRequestURL());
-                }
-                
-            } else {
-                throw new InvalidRequestException("bad path info, "+
-                        request.getRequestURL());
-            }
-            
-        } else {
-            // bad request
-            throw new InvalidRequestException("bad path info, "+
-                    request.getRequestURL());
-        }
-        
-        
-        /*
-         * parse request parameters
-         *
-         * the only params we currently care about are:
-         *   blog_name - comment author
-         *   url - comment referring url
-         *   excerpt - comment contents
-         *   title - comment title
-         */
-        if(request.getParameter("blog_name") != null) {
-            this.blogName = request.getParameter("blog_name");
-        }
-        
-        if(request.getParameter("url") != null) {
-            this.url = request.getParameter("url");
-        }
-        
-        if(request.getParameter("excerpt") != null) {
-            this.excerpt = request.getParameter("excerpt");
-        }
-        
-        if(request.getParameter("title") != null) {
-            this.title = request.getParameter("title");
-        }
-        
-        // a little bit of validation, trackbacks enforce that all params
-        // must have a value, so any nulls equals a bad request
-        if(this.blogName == null || this.url == null || 
-                this.excerpt == null || this.title == null) {
-            throw new InvalidRequestException("bad request data.  did not "+
-                    "receive values for all trackback params (blog_name, url, excerpt, title)");
-        }
-        
-        if(log.isDebugEnabled()) {
-            log.debug("name = "+this.blogName);
-            log.debug("url = "+this.url);
-            log.debug("excerpt = "+this.excerpt);
-            log.debug("title = "+this.title);
-            log.debug("weblogAnchor = "+this.weblogAnchor);
-        }
-    }
-
-    public String getBlogName() {
-        return blogName;
-    }
-
-    public void setBlogName(String blogName) {
-        this.blogName = blogName;
-    }
-
-    public String getUrl() {
-        return url;
-    }
-
-    public void setUrl(String url) {
-        this.url = url;
-    }
-
-    public String getExcerpt() {
-        return excerpt;
-    }
-
-    public void setExcerpt(String excerpt) {
-        this.excerpt = excerpt;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public String getWeblogAnchor() {
-        return weblogAnchor;
-    }
-
-    public void setWeblogAnchor(String weblogAnchor) {
-        this.weblogAnchor = weblogAnchor;
-    }
-
-    public WeblogEntry getWeblogEntry() {
-        
-        if(weblogEntry == null && weblogAnchor != null) {
-            try {
-                WeblogEntryManager wmgr = WebloggerFactory.getWeblogger().getWeblogEntryManager();
-                weblogEntry = wmgr.getWeblogEntryByAnchor(getWeblog(), weblogAnchor);
-            } catch (WebloggerException ex) {
-                log.error("Error getting weblog entry "+weblogAnchor, ex);
-            }
-        }
-        
-        return weblogEntry;
-    }
-
-    public void setWeblogEntry(WeblogEntry weblogEntry) {
-        this.weblogEntry = weblogEntry;
-    }
-    
-}
diff --git a/app/src/main/java/org/apache/roller/weblogger/util/BannedwordslistChecker.java b/app/src/main/java/org/apache/roller/weblogger/util/BannedwordslistChecker.java
index 46ec921..cd8e41b 100644
--- a/app/src/main/java/org/apache/roller/weblogger/util/BannedwordslistChecker.java
+++ b/app/src/main/java/org/apache/roller/weblogger/util/BannedwordslistChecker.java
@@ -27,7 +27,7 @@
 import org.apache.roller.weblogger.pojos.Weblog;
 
 /**
- * Checks comment, trackbacks and referrers for spam.
+ * Checks comments and referrers for spam.
  * @author Lance Lavandowska
  * @author Dave Johnson
  */
@@ -50,17 +50,6 @@
     }
     
     /** 
-     * Test trackback comment, applying all bannedwordslists, if configured
-     * @return True if comment matches bannedwordslist term
-     */
-    public static boolean checkTrackback(WeblogEntryComment comment) {
-        if (WebloggerConfig.getBooleanProperty("site.bannedwordslist.enable.trackbacks")) {
-            return testComment(comment);
-        }
-        return false;
-    }
-
-    /** 
      * Test referrer URL, applying bannedwordslist and website bannedwordslist only if configured
      * @return True if comment matches bannedwordslist term
      */
@@ -101,4 +90,3 @@
         return ret;
     }        
 }
-
diff --git a/app/src/main/java/org/apache/roller/weblogger/util/CommentAuthorUrl.java b/app/src/main/java/org/apache/roller/weblogger/util/CommentAuthorUrl.java
new file mode 100644
index 0000000..c3cd5c1
--- /dev/null
+++ b/app/src/main/java/org/apache/roller/weblogger/util/CommentAuthorUrl.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ */
+package org.apache.roller.weblogger.util;
+
+import java.net.IDN;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Locale;
+
+/**
+ * Normalizes comment author URLs before they are rendered as links.
+ */
+public final class CommentAuthorUrl {
+
+    private CommentAuthorUrl() {
+    }
+
+    public static String normalize(String value) {
+        if (value == null || value.isBlank()) {
+            return null;
+        }
+        String normalized = value.trim();
+        try {
+            URI uri = new URI(normalized);
+            String scheme = uri.getScheme();
+            if (scheme == null
+                    || !(scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))
+                    || uri.isOpaque()
+                    || !hasValidAuthority(uri)) {
+                return null;
+            }
+            return normalized;
+        } catch (IllegalArgumentException | URISyntaxException ignored) {
+            return null;
+        }
+    }
+
+    /**
+     * Normalizes user-entered URLs, retaining the long-standing behavior of
+     * supplying an HTTP scheme when one was omitted.
+     */
+    public static String normalizeInput(String value) {
+        if (value == null || value.isBlank()) {
+            return null;
+        }
+        String normalized = value.trim();
+        String lowerCase = normalized.toLowerCase(Locale.ROOT);
+        if (!lowerCase.startsWith("http://") && !lowerCase.startsWith("https://")) {
+            normalized = "http://" + normalized;
+        }
+        return normalize(normalized);
+    }
+
+    private static boolean hasValidAuthority(URI uri) {
+        String authority = uri.getRawAuthority();
+        if (authority == null || authority.isBlank() || uri.getRawUserInfo() != null) {
+            return false;
+        }
+
+        if (authority.startsWith("[")) {
+            int closingBracket = authority.indexOf(']');
+            if (closingBracket < 2 || uri.getHost() == null) {
+                return false;
+            }
+            return hasValidPort(authority.substring(closingBracket + 1));
+        }
+
+        String host = authority;
+        int colon = authority.lastIndexOf(':');
+        if (colon >= 0) {
+            if (authority.indexOf(':') != colon
+                    || !hasValidPort(authority.substring(colon))) {
+                return false;
+            }
+            host = authority.substring(0, colon);
+        }
+
+        try {
+            return !IDN.toASCII(host).isBlank();
+        } catch (IllegalArgumentException ignored) {
+            return false;
+        }
+    }
+
+    private static boolean hasValidPort(String suffix) {
+        if (suffix.isEmpty()) {
+            return true;
+        }
+        if (suffix.charAt(0) != ':' || suffix.length() == 1) {
+            return false;
+        }
+        try {
+            int port = Integer.parseInt(suffix.substring(1));
+            return port >= 0 && port <= 65535;
+        } catch (NumberFormatException ignored) {
+            return false;
+        }
+    }
+}
diff --git a/app/src/main/java/org/apache/roller/weblogger/util/LinkbackExtractor.java b/app/src/main/java/org/apache/roller/weblogger/util/LinkbackExtractor.java
deleted file mode 100644
index d43378e..0000000
--- a/app/src/main/java/org/apache/roller/weblogger/util/LinkbackExtractor.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
-* 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.util;
-
-import com.rometools.rome.feed.synd.SyndEntry;
-import com.rometools.rome.feed.synd.SyndFeed;
-import com.rometools.rome.io.FeedException;
-import com.rometools.rome.io.SyndFeedInput;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.StringReader;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.swing.text.MutableAttributeSet;
-import javax.swing.text.html.HTML;
-import javax.swing.text.html.HTMLEditorKit;
-import javax.swing.text.html.HTML.Tag;
-import javax.swing.text.html.HTMLEditorKit.Parser;
-import javax.swing.text.html.HTMLEditorKit.ParserCallback;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-
-/**
- * Parses HTML file for referring linkback title and excerpt.
- * 
- * @author David M Johnson
- */
-public class LinkbackExtractor
-{
-    private static Log mLogger        = LogFactory.getFactory().getInstance(
-                                              LinkbackExtractor.class);
-    private boolean    mFound         = false;
-    private String     mTitle         = "";
-    private String     mRssLink       = null;
-    private String     mExcerpt       = null;
-    private String     mPermalink     = null;
-    private int        mStart         = 0;
-    private int        mEnd           = 0;
-    private String     mRequestURL    = null;
-    private String     mRequestURLWWW = null;
-    private String     mRefererURL;
-
-    private static final int MAX_EXCERPT_CHARS = 500;
-    private static final int DESIRED_TITLE_LENGTH = 50;
-
-    //------------------------------------------------------------------------
-    /**
-     * Extract referring page title, excerpt, and permalink.
-     * 
-     * @param refererURL
-     * @param requestURL
-     */
-    public LinkbackExtractor(String refererURL, String requestURL) throws IOException {
-        try {
-            extractByParsingHtml(refererURL, requestURL);
-            if (mRssLink != null) {
-                extractByParsingRss(mRssLink, requestURL);
-            }
-        } catch (Exception e) {
-            if (mLogger.isDebugEnabled()) {
-                mLogger.debug("Extracting linkback", e);
-            }
-        }
-    }
-
-    //------------------------------------------------------------------------
-    private void extractByParsingHtml(String refererURL, String requestURL) throws IOException {
-        URL url = new URL(refererURL);
-        InputStream is = url.openStream();
-
-        mRefererURL = refererURL;
-
-        if (requestURL.startsWith("http://www.")) {
-            mRequestURLWWW = requestURL;
-            mRequestURL = "http://" + mRequestURLWWW.substring(11);
-        } else {
-            mRequestURL = requestURL;
-            mRequestURLWWW = "http://www." + mRequestURL.substring(7);
-        }
-
-        // Trick gets Swing's HTML parser by making its protected getParser() method public
-        // Ignore inaccurate Sonar complaint about useless overriding method:
-        //    http://jira.codehaus.org/browse/SONARJAVA-287
-        Parser parser = (new HTMLEditorKit() {
-            @Override
-            public Parser getParser() {
-                return super.getParser();
-            }
-        }).getParser();
-
-        // Read HTML file into string
-        StringBuilder sb = new StringBuilder();
-        InputStreamReader isr = new InputStreamReader(is);
-        BufferedReader br = new BufferedReader(isr);
-        try {
-            String line;
-            while ((line = br.readLine()) != null) {
-                sb.append(line);
-            }
-        } finally {
-            br.close();
-        }
-
-        // Parse HTML string to find title and start and end position
-        // of the referring excerpt.
-        StringReader sr = new StringReader(sb.toString());
-        parser.parse(sr, new LinkbackCallback(), true);
-
-        if (mStart != 0 && mEnd != 0 && mEnd > mStart) {
-            mExcerpt = sb.toString().substring(mStart, mEnd);
-            mExcerpt = Utilities.removeHTML(mExcerpt);
-
-            if (mExcerpt.length() > MAX_EXCERPT_CHARS) {
-                mExcerpt = mExcerpt.substring(0, MAX_EXCERPT_CHARS) + "...";
-            }
-        }
-
-        if (mTitle.startsWith(">") && mTitle.length() > 1) {
-            mTitle = mTitle.substring(1);
-        }
-    }
-
-    //------------------------------------------------------------------------
-    private void extractByParsingRss(String rssLink, String requestURL)
-            throws FeedException, IOException {
-        SyndFeedInput feedInput = new SyndFeedInput();       
-        SyndFeed feed = feedInput.build(
-            new InputStreamReader(new URL(rssLink).openStream()));
-        String feedTitle = feed.getTitle();
-
-        int count = 0;
-
-        if (mLogger.isDebugEnabled()) {
-            mLogger.debug("Feed parsed, title: " + feedTitle);
-        }
-
-        for (Object objItem : feed.getEntries()) {
-            count++;
-            SyndEntry item = (SyndEntry) objItem;
-            if (item.getDescription().getValue().contains(requestURL)) {
-                mFound = true;
-                mPermalink = item.getLink();
-                if (feedTitle != null && !feedTitle.isBlank()) {
-                    mTitle = feedTitle + ": " + item.getTitle();
-                } else {
-                    mTitle = item.getTitle();
-                }
-                mExcerpt = item.getDescription().getValue();
-                mExcerpt = Utilities.removeHTML(mExcerpt);
-                if (mExcerpt.length() > MAX_EXCERPT_CHARS) {
-                    mExcerpt = mExcerpt.substring(0, MAX_EXCERPT_CHARS) + "...";
-                }
-                break;
-            }
-        }
-
-        if (mLogger.isDebugEnabled()) {
-            mLogger.debug("Parsed " + count + " articles, found linkback=" + mFound);
-        }
-    }
-
-    //------------------------------------------------------------------------
-    /**
-     * Returns the excerpt.
-     * 
-     * @return String
-     */
-    public String getExcerpt() {
-        return mExcerpt;
-    }
-
-    //------------------------------------------------------------------------
-    /**
-     * Returns the title.
-     * 
-     * @return String
-     */
-    public String getTitle() {
-        return mTitle;
-    }
-
-    //------------------------------------------------------------------------
-    /**
-     * Returns the permalink.
-     * 
-     * @return String
-     */
-    public String getPermalink() {
-        return mPermalink;
-    }
-
-    //------------------------------------------------------------------------
-    /**
-     * Sets the permalink.
-     * 
-     * @param permalink
-     *            The permalink to set
-     */
-    public void setPermalink(String permalink)
-    {
-        mPermalink = permalink;
-    }
-
-    /////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Parser callback that finds title and excerpt. As we walk through the HTML
-     * tags, we keep track of the most recently encountered divider tag in the
-     * mStart field. Once we find the referring permalink, we set the mFound
-     * flag. After that, we look for the next divider tag and save it's position
-     * in the mEnd field.
-     */
-    private final class LinkbackCallback extends ParserCallback
-    {
-        // Dividers
-        private Tag[] mDivTags    = { Tag.TD, Tag.DIV, Tag.SPAN,
-                                          Tag.BLOCKQUOTE, Tag.P, Tag.LI,
-                                          Tag.BR, Tag.HR, Tag.PRE, Tag.H1,
-                                          Tag.H2, Tag.H3, Tag.H4, Tag.H5,
-                                          Tag.H6 };
-
-        private List<Tag> mList = Arrays.asList(mDivTags);
-
-        private Tag   mCurrentTag = null;
-
-        /**
-         * Look for divider tags and for the permalink.
-         * 
-         * @param tag
-         *            HTML tag
-         * @param atts
-         *            Attributes of that tag
-         * @param pos
-         *            Tag's position in file
-         */
-        @Override
-        public void handleStartTag(Tag tag, MutableAttributeSet atts, int pos)
-        {
-            if (mList.contains(tag) && !mFound)
-            {
-                mStart = pos;
-            }
-            else if (mList.contains(tag) && mFound && mEnd == 0)
-            {
-                mEnd = pos;
-            }
-            else if (tag.equals(Tag.A))
-            {
-                String href = (String) atts.getAttribute(HTML.Attribute.HREF);
-                if (href == null) {
-                    return;
-                }
-                int hashPos = href.lastIndexOf('#');
-                if (hashPos != -1)
-                {
-                    href = href.substring(0, hashPos);
-                }
-                if (href != null
-                        && (href.equals(mRequestURL) || href
-                                .equals(mRequestURLWWW)))
-                {
-                    mFound = true;
-                }
-            }
-            mCurrentTag = tag;
-        }
-
-        /**
-         * Needed to handle SPAN tag.
-         */
-        @Override
-        public void handleSimpleTag(Tag tag, MutableAttributeSet atts, int pos)
-        {
-            if (mList.contains(tag) && mFound && mEnd == 0)
-            {
-                mEnd = pos;
-            }
-            else if (tag.equals(Tag.LINK))
-            {
-                // Look out for RSS autodiscovery link
-                String title = (String) atts.getAttribute(HTML.Attribute.TITLE);
-                String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
-                if (title != null && type != null
-                        && type.equals("application/rss+xml")
-                        && title.equals("RSS"))
-                {
-                    mRssLink = (String) atts.getAttribute(HTML.Attribute.HREF);
-
-                    if (mLogger.isDebugEnabled())
-                    {
-                        mLogger.debug("Found RSS link " + mRssLink);
-                    }
-
-                    if (mRssLink.startsWith("/") && mRssLink.length() > 1)
-                    {
-                        try
-                        {
-                            URL url = new URL(mRefererURL);
-                            mRssLink = url.getProtocol() + "://"
-                                    + url.getHost() + ":" + url.getPort()
-                                    + mRssLink;
-                        }
-                        catch (MalformedURLException e)
-                        {
-                            mRssLink = null;
-                            if (mLogger.isDebugEnabled())
-                            {
-                                mLogger.debug("Determining RSS URL", e);
-                            }
-                        }
-                    }
-                    else if (!mRssLink.startsWith("http"))
-                    {
-                        int slash = mRefererURL.lastIndexOf('/');
-                        if (slash != -1)
-                        {
-                            mRssLink = mRefererURL.substring(0, slash) + "/"
-                                    + mRssLink;
-                        }
-                    }
-                    if (mLogger.isDebugEnabled())
-                    {
-                        mLogger.debug("Qualified RSS link is " + mRssLink);
-                    }
-                }
-            }
-        }
-
-        /**
-         * Stop at the very first divider tag after the permalink.
-         * 
-         * @param tag
-         *            End tag
-         * @param pos
-         *            Position in HTML file
-         */
-        @Override
-        public void handleEndTag(Tag tag, int pos)
-        {
-            if (mList.contains(tag) && mFound && mEnd == 0)
-            {
-                mEnd = pos;
-            }
-            else if (mList.contains(tag) && !mFound)
-            {
-                mStart = pos;
-            }
-            else
-            {
-                mCurrentTag = null;
-            }
-        }
-
-        /**
-         * Get the page title
-         */
-        @Override
-        public void handleText(char[] data, int pos)
-        {
-            if (mCurrentTag != null && mCurrentTag.equals(Tag.TITLE))
-            {
-                String newText = new String(data);
-                if (mTitle.length() < DESIRED_TITLE_LENGTH)
-                {
-                    mTitle += newText;
-                }
-            }
-        }
-    }
-}
-
diff --git a/app/src/main/resources/ApplicationResources.properties b/app/src/main/resources/ApplicationResources.properties
index caa9abc..9e6a9e9 100644
--- a/app/src/main/resources/ApplicationResources.properties
+++ b/app/src/main/resources/ApplicationResources.properties
@@ -228,9 +228,6 @@
 comment.validator.bannedwordslistName=Banned Words Comment Validator
 comment.validator.bannedwordslistMessage=Comment contains banned/ignored words
 
-comment.validator.trackbackLinkbackName=Trackback Linkback Comment Validator
-comment.validator.trackbackLinkbackMessage=Trackback from site/page that does not link to your weblog entry
-
 comment.validator.akismetName=Akismet Comment Validator
 comment.validator.akismetMessage=Akismet service (akismet.com) says comment is spam
 
@@ -349,17 +346,13 @@
 configForm.newsfeedMaxEntries=Number of entries to provide in feeds
 configForm.styledFeeds=Display styled feeds for browsers
 
-configForm.commentSettings=Comment and Trackback Settings
+configForm.commentSettings=Comment Settings
 configForm.enableComments=Allow weblog comments?
 configForm.ignoreSpamComments=Don''t save comments thought to be spam
-configForm.enableTrackbacks=Allow weblog trackbacks?
-configForm.ignoreSpamTrackbacks=Don''t save trackbacks thought to be spam
 configForm.commentHtmlAllowed=Allow html in comments?
 configForm.commentPlugins=Enabled comment formatting plugins
 configForm.emailComments=Allow email notification of comments?
 configForm.moderationRequired=Require comment moderation for all weblogs
-configForm.enableTrackbackValidation=Enable verification of trackback links?
-
 configForm.fileUploadSettings=File Upload Settings
 configForm.enableFileUploads=Enable File Uploads? (only enable if you trust all users not to upload malicious content)
 configForm.allowedExtensions=Allowed Extensions
@@ -1752,7 +1745,7 @@
 
 websiteSettings.spamPrevention=Spam Prevention
 websiteSettings.ignoreUrls=List of words and regex expressions listed one per \
-line to be added to the banned words list used to check comments, trackbacks and referrers.
+line to be added to the banned words list used to check comments and referrers.
 websiteSettings.bannedWordsList=Words banned in comments (regex allowed)
 websiteSettings.acceptedBannedwordslist=Accepted {0} string and {1} regex banned-words list rules
 websiteSettings.error.processingBannedwordslist=Error processing banned-words list: {0}
diff --git a/app/src/main/resources/ApplicationResources_de.properties b/app/src/main/resources/ApplicationResources_de.properties
index e677f81..35d9ca6 100644
--- a/app/src/main/resources/ApplicationResources_de.properties
+++ b/app/src/main/resources/ApplicationResources_de.properties
@@ -178,8 +178,6 @@
 comment.validator.excessLinksName=Linkanzahl Kommentarpr\u00FCfung
 comment.validator.excessSizeMessage=Der Kommentar hat mehr als {0} Zeichen
 comment.validator.excessSizeName=Gr\u00F6\u00DFen\u00FCberschreitung Kommentarpr\u00FCfung
-comment.validator.trackbackLinkbackMessage=Trackback von Webseite, der nicht auf Ihren Weblogeintrag verlinkt
-comment.validator.trackbackLinkbackName=Trackback R\u00FCcklink Kommentarpr\u00FCfung
 commentManagement.bulkDeletePrompt1=Ihre Abfrage lieferte {0} Kommentare,
 commentManagement.bulkDeletePrompt2=Alle l\u00F6schen?
 commentManagement.columnApproved=Zugelassen
@@ -249,22 +247,19 @@
 configForm.absoluteUrl=Absolute URL zur Site (falls notwendig)
 configForm.requireEmailActivation=Neue Benutzer m\u00FCssen Ihren Zugang per E-Mail aktivieren
 configForm.allowNewUsers=Erlaube das Anlegen neuer Benutzer?
+configForm.commentSettings=Kommentareinstellungen
 configForm.allowedExtensions=Zul\u00E4ssige Dateierweiterungen
 configForm.commentHtmlAllowed=HTML in Kommentaren erlauben?
 configForm.commentPlugins=An-/Abschalten von Plugins zur Kommentarformatierung
-configForm.commentSettings=Kommentar und Trackback Einstellungen
 configForm.editorPages=Bearbeitungsseiten
 configForm.emailComments=E-Mailbenachrichtung bei Kommentaren?
 configForm.enableComments=Kommentare in Weblogs erlauben?
 configForm.enableFileUploads=Datei Uploads erlauben?
-configForm.enableTrackbackValidation=\u00DCberpr\u00FCfung von Trackback Links einschalten?
-configForm.enableTrackbacks=Weblog Trackbacks erlauben?
 configForm.fileUploadSettings=Datei Upload Einstellungen
 configForm.forbiddenExtensions=Verbotene Dateierweiterungen
 configForm.frontpageWeblogAggregated=Aggregierte systemweite Startseite einschalten
 configForm.frontpageWeblogHandle=Handle des Weblogs, welches als Startseiten Blog verwendet werden soll
 configForm.ignoreSpamComments=Als Spam klassifizierte Kommentare nicht speichern
-configForm.ignoreSpamTrackbacks=Als Spam klassifizierte Trackbacks nicht speichern
 configForm.maxDirSize=Maximale Verzeichnisgr\u00F6\u00DFe (MB)
 configForm.maxFileSize=Maximale Dateigr\u00F6\u00DFe (MB)
 configForm.moderationRequired=Erzwinge Kommentarmoderation f\u00FCr alle Weblogs
diff --git a/app/src/main/resources/ApplicationResources_es.properties b/app/src/main/resources/ApplicationResources_es.properties
index 3432d85..e8e7955 100644
--- a/app/src/main/resources/ApplicationResources_es.properties
+++ b/app/src/main/resources/ApplicationResources_es.properties
@@ -136,12 +136,10 @@
 configForm.userSettings=Configuraci\u00F3n de usuario
 configForm.allowNewUsers=\u00BFPermitir nuevos usuarios?
 configForm.registrationUrl=URL de registro externo
+configForm.commentSettings=Configuraci\u00F3n de comentarios
 configForm.editorPages=P\u00E1ginas de editor
 configForm.emailComments=\u00BFNotificaci\u00F3n de comentarios por correo electr\u00F3nico?
-configForm.commentSettings=Configuraci\u00F3n de comentarios y referencias
 configForm.enableComments=\u00BFPermitir comentarios de weblog?
-configForm.enableTrackbacks=\u00BFPermitir referencias de weblog?
-configForm.enableTrackbackValidation=\u00BFHabilitar verificaci\u00F3n de los enlaces de referencias?
 configForm.newsfeedMaxEntries=N\u00FAmero m\u00E1ximo de entradas
 configForm.fileUploadSettings=Configuraci\u00F3n de subida de ficheros
 configForm.enableFileUploads=\u00BFPermitir subir ficheros?
diff --git a/app/src/main/resources/ApplicationResources_fr.properties b/app/src/main/resources/ApplicationResources_fr.properties
index 2a9b715..8c7f4c3 100644
--- a/app/src/main/resources/ApplicationResources_fr.properties
+++ b/app/src/main/resources/ApplicationResources_fr.properties
@@ -156,8 +156,6 @@
 comment.validator.bannedwordslistMessage=Ce commentaire contient certains mots figurant sur la liste noire.
 
 #FIXME
-comment.validator.trackbackLinkbackName=Trackback Linkback Comment Validator
-comment.validator.trackbackLinkbackMessage=Trackback from site/page that does not link to your weblog entry
 
 comment.validator.akismetName=Validation de commentaires Akismet
 comment.validator.akismetMessage=Le service Akismet (akismet.com) indique que ce commentaire est un spam
@@ -261,6 +259,7 @@
 configForm.requireEmailActivation=Les nouveaux utilisateurs doivent activer leur compte par courriel
 configForm.allowNewUsers=Autoriser de nouveaux utilisateurs
 configForm.registrationUrl=URL externe d'enregistrement
+configForm.commentSettings=Paramètres des commentaires
 configForm.editorPages=Pages utilisées comme éditeur de texte.
 
 configForm.weblogSettings=Paramètres d'affichage du blog
@@ -268,15 +267,11 @@
 configForm.newsfeedMaxEntries=Nombre d'entrées dans les fils d'information
 configForm.styledFeeds=Afficher les fils d'information stylisés pour les navigateurs
 
-configForm.commentSettings=Paramètres de commentaires et trackbacks
 configForm.enableComments=Autoriser les commentaires sur ce blog
 configForm.ignoreSpamComments=Ne pas enregistrer les commentaires considérés comme spam
-configForm.enableTrackbacks=Autoriser les trackbacks ?
-configForm.ignoreSpamTrackbacks=Ne pas enregistrer les trackbacks considérés comme spam
 #FIXME
 configForm.emailComments=Envoyer une notification de commentaire par courriel
 configForm.moderationRequired=Modération de commentaires requise pour tous les blogs
-configForm.enableTrackbackValidation=Activer la vérification des trackback
 #FIXME
 
 configForm.fileUploadSettings=Paramètres de transfert de fichiers
@@ -1103,7 +1098,7 @@
 
 websiteSettings.spamPrevention=Prevention de spam
 websiteSettings.ignoreUrls=Liste de mots ou expressions régulières à ajouter \
-à la liste noire utilisée pour vérifier les commentaires, trackbacks et réferrants. \
+à la liste noire utilisée pour vérifier les commentaires et réferrants. \
 Veuillez ajouter une seule expression ou mot par ligne.
 
 websiteSettings.acceptedBannedwordslist={0} mots ou phrases et {1} expression(s) régulière(s) ajouté(s) à la liste noire.
diff --git a/app/src/main/resources/ApplicationResources_ja.properties b/app/src/main/resources/ApplicationResources_ja.properties
index f65cb9e..0111318 100644
--- a/app/src/main/resources/ApplicationResources_ja.properties
+++ b/app/src/main/resources/ApplicationResources_ja.properties
@@ -207,13 +207,11 @@
 configForm.userSettings=\u30E6\u30FC\u30B6\u8A2D\u5B9A
 configForm.allowNewUsers=\u65B0\u898F\u30E6\u30FC\u30B6\u3092\u8A31\u53EF
 configForm.registrationUrl=\u5916\u90E8\u5411\u3051\u767B\u9332URL
+configForm.commentSettings=\u30B3\u30E1\u30F3\u30C8\u8A2D\u5B9A
 configForm.editorPages=\u30A8\u30C7\u30A3\u30BF\u30DA\u30FC\u30B8\u6307\u5B9A
 configForm.emailComments=\u65B0\u3057\u3044\u30B3\u30E1\u30F3\u30C8\u306E\u30E1\u30FC\u30EB\u901A\u77E5\u3092\u8A31\u53EF
 
-configForm.commentSettings=\u30B3\u30E1\u30F3\u30C8\u3068\u30C8\u30E9\u30C3\u30AF\u30D0\u30C3\u30AF\u8A2D\u5B9A
 configForm.enableComments=\u30B3\u30E1\u30F3\u30C8\u3092\u8A31\u53EF
-configForm.enableTrackbacks=\u30C8\u30E9\u30C3\u30AF\u30D0\u30C3\u30AF\u3092\u8A31\u53EF
-configForm.enableTrackbackValidation=\u30C8\u30E9\u30C3\u30AF\u30D0\u30C3\u30AF\u30FB\u30EA\u30F3\u30AF\u306E\u691C\u8A3C\u3092\u884C\u3046
 configForm.moderationRequired=\u3059\u3079\u3066\u306E\u30D6\u30ED\u30B0\u3067\u30B3\u30E1\u30F3\u30C8\u3092\u627F\u8A8D\u5236\u306B\u3059\u308B
 
 configForm.newsfeedMaxEntries=\u30D5\u30A3\u30FC\u30C9\u306B\u542B\u3081\u308B\u30A8\u30F3\u30C8\u30EA\u6570
@@ -948,7 +946,6 @@
 Category.error.descriptionSize=\u8A73\u7D30\u306F255\u6587\u5B57\u4EE5\u5185\u3067\u306A\u3051\u308C\u3070\u3044\u3051\u307E\u305B\u3093
 tabbedmenu.design=\u30C7\u30B6\u30A4\u30F3
 configForm.ignoreSpamComments=\u30B9\u30D1\u30E0\u3068\u601D\u308F\u308C\u308B\u30B3\u30E1\u30F3\u30C8\u306F\u4FDD\u5B58\u3057\u306A\u3044
-configForm.ignoreSpamTrackbacks=\u30B9\u30D1\u30E0\u3068\u601D\u308F\u308C\u308B\u30C8\u30E9\u30C3\u30AF\u30D0\u30C3\u30AF\u306F\u4FDD\u5B58\u3057\u306A\u3044
 pageForm.subtitle=\u30D6\u30ED\u30B0<span>{1}</span>\u306E\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8<span>{0}</span>\u3092\u7DE8\u96C6
 stylesheetEdit.subtitle=\u30AB\u30B9\u30BF\u30E0\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u3092\u7DE8\u96C6
 CreateWeblog.error.emailAddressSize=\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306F255\u6587\u5B57\u4EE5\u5185\u3067\u306A\u3051\u308C\u3070\u3044\u3051\u307E\u305B\u3093
@@ -1028,8 +1025,6 @@
 Category.error.imageBad=\u6307\u5B9A\u3055\u308C\u305F\u753B\u50CF\u306EURL\u306F\u7121\u52B9\u306A\u30A2\u30C9\u30EC\u30B9\u3067\u3059
 comment.validator.excessLinksName=\u30B3\u30E1\u30F3\u30C8\u306E\u30EA\u30F3\u30AF\u6570\u30D0\u30EA\u30C7\u30FC\u30BF
 comment.validator.excessSizeName=\u30B3\u30E1\u30F3\u30C8\u306E\u9577\u3055\u30D0\u30EA\u30C7\u30FC\u30BF
-comment.validator.trackbackLinkbackName=\u30C8\u30E9\u30C3\u30AF\u30D0\u30C3\u30AF\u30FB\u30EA\u30F3\u30AF\u30D0\u30C3\u30AF\u30B3\u30E1\u30F3\u30C8\u30D0\u30EA\u30C7\u30FC\u30BF
-comment.validator.trackbackLinkbackMessage=\u30D6\u30ED\u30B0\u30A8\u30F3\u30C8\u30EA\u3078\u30EA\u30F3\u30AF\u3055\u308C\u3066\u3044\u306A\u3044\u30B5\u30A4\u30C8\u306A\u3044\u3057\u30DA\u30FC\u30B8\u304B\u3089\u306E\u30C8\u30E9\u30C3\u30AF\u30D0\u30C3\u30AF
 commentManagement.bulkDeletePrompt1=\u30AF\u30A8\u30EA\u306B\u8A72\u5F53\u3059\u308B{0}\u306E\u30B3\u30E1\u30F3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3002
 commentManagement.readmore=\u3059\u3079\u3066\u306E\u30B3\u30E1\u30F3\u30C8\u3092\u898B\u308B
 commentServlet.commentAccepted=\u30B3\u30E1\u30F3\u30C8\u304C\u66F8\u304D\u8FBC\u307E\u308C\u307E\u3057\u305F\u3002
diff --git a/app/src/main/resources/ApplicationResources_ko.properties b/app/src/main/resources/ApplicationResources_ko.properties
index bd5839d..3adb9a1 100644
--- a/app/src/main/resources/ApplicationResources_ko.properties
+++ b/app/src/main/resources/ApplicationResources_ko.properties
@@ -171,8 +171,6 @@
 comment.validator.excessSizeMessage=\uc758\uacac\uc774 {0}\uac1c \uc774\uc0c1\uc758 \uae00\uc790\ub97c \ud3ec\ud568\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4.
 comment.validator.bannedwordslistName=\uc758\uacac \uc720\ud6a8\uc131 \uac80\uc0ac: \ube14\ub799\ub9ac\uc2a4\ud2b8
 comment.validator.bannedwordslistMessage=\uc758\uacac\uc774 \ube14\ub799\ub9ac\uc2a4\ud2b8\uc5d0 \ud3ec\ud568\ub41c \ub2e8\uc5b4\ub4e4\uc744 \ud3ec\ud568\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4.
-comment.validator.trackbackLinkbackName=\uc758\uacac \uc720\ud6a8\uc131 \uac80\uc0ac: \ud2b8\ub799\ubc31 \ub9c1\ud06c\ubc31
-comment.validator.trackbackLinkbackMessage=\uadc0\ud558\uc758 \uc6f9\ub85c\uadf8 \uae30\uc0ac\uc5d0 \uc5f0\uacb0\ub418\uc9c0 \uc54a\ub294 \uc0ac\uc774\ud2b8\ub098 \ud398\uc774\uc9c0\ub85c\ubd80\ud130\uc758 \ud2b8\ub799\ubc31\uc785\ub2c8\ub2e4.
 comment.validator.akismetName=\uc758\uacac\uc720\ud6a8\uc131 \uac80\uc0ac: Akismet \uc11c\ube44\uc2a4
 comment.validator.akismetMessage=Akismet \uc11c\ube44\uc2a4(akismet.com)\uc5d0\uc11c\ub294 \uc774 \uc758\uacac\uc744 \uc2a4\ud338\uc73c\ub85c \uc5ec\uae30\uace0 \uc788\uc2b5\ub2c8\ub2e4.
 
@@ -272,6 +270,7 @@
 configForm.requireEmailActivation=\uc0c8 \uc0ac\uc6a9\uc790\uac00 \uc804\uc790\uc6b0\ud3b8\uc744 \ud1b5\ud574 \uacc4\uc815 \ud65c\uc131\ud654 \ud544\uc694
 configForm.allowNewUsers=\uc0c8 \uc0ac\uc6a9\uc790\ub4e4\uc744 \ud5c8\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
 configForm.registrationUrl=\uc678\ubd80 \ub4f1\ub85d URL
+configForm.commentSettings=\uc758\uacac \ud658\uacbd \uc124\uc815 \uc815\ubcf4
 configForm.editorPages=\ud3b8\uc9d1 \ud398\uc774\uc9c0\ub4e4
 
 configForm.weblogSettings=\uc6f9\ub85c\uadf8 \ub80c\ub354\ub9c1 \ud658\uacbd \uc124\uc815 \uc815\ubcf4
@@ -279,16 +278,12 @@
 configForm.newsfeedMaxEntries=\ub274\uc2a4 \ud53c\ub4dc\ub4e4\uc5d0\uc11c \uc81c\uacf5\ub418\ub294 \uc6f9\ub85c\uadf8 \uae30\uc0ac\uc758 \uac1c\uc218
 configForm.styledFeeds=\ube0c\ub77c\uc6b0\uc800\ub4e4\uc744 \uc704\ud574 \uc2a4\ud0c0\uc77c\ud654\ub41c \ub274\uc2a4 \ud53c\ub4dc\ub4e4\uc758 \ud45c\uc2dc
 
-configForm.commentSettings=\uc758\uacac\uacfc \ud2b8\ub799\ubc31 \ud658\uacbd \uc124\uc815 \uc815\ubcf4
 configForm.enableComments=\uc6f9\ub85c\uadf8 \uc758\uacac\ub4e4\uc744 \ud5c8\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
 configForm.ignoreSpamComments=\uc2a4\ud338\uc73c\ub85c \uc5ec\uaca8\uc9c0\ub294 \uc758\uacac \uc800\uc7a5 \uc548\ud568.
-configForm.enableTrackbacks=\uc6f9\ub85c\uadf8 \ud2b8\ub799\ubc31\ub4e4\uc744 \ud5c8\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
-configForm.ignoreSpamTrackbacks=\uc2a4\ud338\uc73c\ub85c \uc5ec\uaca8\uc9c0\ub294 \ud2b8\ub799\ubc31 \uc800\uc7a5 \uc548\ud568.
 configForm.commentHtmlAllowed=\uc758\uacac\uc5d0 HTML \ud0dc\uadf8\ub97c \ud5c8\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
 configForm.commentPlugins=\uc758\uacac \ud3ec\ub9f7 \uc801\uc6a9 \ud50c\ub7ec\uadf8\uc778 \ud65c\uc131\ud654/\ube44\ud65c\uc131\ud654
 configForm.emailComments=\uc758\uacac\ub4e4\uc5d0 \ub300\ud574 \uc804\uc790\uc6b0\ud3b8 \ud1b5\uc9c0\ub97c \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
 configForm.moderationRequired=\ubaa8\ub4e0 \uc6f9\ub85c\uadf8\ub4e4\uc758 \uc758\uacac \uc870\uc815\uc744 \ud544\uc218\uc801\uc73c\ub85c \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
-configForm.enableTrackbackValidation=\ud2b8\ub799\ubc31 \ub9c1\ud06c\ub4e4\uc758 \uac80\uc99d\uc744 \ud65c\uc131\ud654\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
 
 configForm.fileUploadSettings=\ud30c\uc77c \uc5c5\ub85c\ub4dc \ud658\uacbd \uc124\uc815 \uc815\ubcf4
 configForm.enableFileUploads=\ud30c\uc77c \uc5c5\ub85c\ub4dc\ub97c \ud65c\uc131\ud654\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
diff --git a/app/src/main/resources/ApplicationResources_ru.properties b/app/src/main/resources/ApplicationResources_ru.properties
index a5b12a5..78aa084 100644
--- a/app/src/main/resources/ApplicationResources_ru.properties
+++ b/app/src/main/resources/ApplicationResources_ru.properties
@@ -156,12 +156,11 @@
 configForm.userSettings=\u041D\u0430\u0441\u0440\u043E\u0439\u043A\u0438 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435\u0439

 configForm.allowNewUsers=\u0420\u0430\u0437\u0440\u0435\u0448\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0445?

 configForm.registrationUrl=\u0410\u0434\u0440\u0435\u0441 \u0432\u043D\u0435\u0448\u043D\u0435\u0439 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438

+configForm.commentSettings=\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0435\u0432

 configForm.editorPages=\u0421\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0430

 configForm.emailComments=\u0423\u0432\u0435\u0434\u043E\u043C\u043B\u044F\u0442\u044C \u043F\u043E Email \u043E \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u044F\u0445?

 

-configForm.commentSettings=\u041D\u0430\u0442\u0440\u043E\u0439\u043A\u0438 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0435\u0432 \u0438 Trackback

 configForm.enableComments=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438?

-configForm.enableTrackbacks=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C trackbacks?

 

 configForm.fileUploadSettings=\u041D\u0430\u0441\u0440\u043E\u0439\u043A\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438 \u0444\u0430\u0439\u043B\u043E\u0432

 configForm.enableFileUploads=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0443?

diff --git a/app/src/main/resources/ApplicationResources_zh_CN.properties b/app/src/main/resources/ApplicationResources_zh_CN.properties
index 44583e0..b14a965 100644
--- a/app/src/main/resources/ApplicationResources_zh_CN.properties
+++ b/app/src/main/resources/ApplicationResources_zh_CN.properties
@@ -224,8 +224,6 @@
 comment.validator.bannedwordslistName=\u7981\u7528\u8BCD\u8BC4\u8BBA\u68C0\u67E5\u5668
 comment.validator.bannedwordslistMessage=\u8BC4\u8BBA\u4E2D\u5305\u542B\u7981\u7528/\u5FFD\u7565\u7684\u8BCD\u6C47
 
-comment.validator.trackbackLinkbackName=\u8BC4\u8BBA\u5F15\u7528\u68C0\u67E5
-comment.validator.trackbackLinkbackMessage=\u5F15\u7528\u672A\u94FE\u63A5\u5230\u672C\u535A\u5BA2\u7684\u7F51\u9875
 
 comment.validator.akismetName=\u8BC4\u8BBA\u8FC7\u6EE4\u9650\u5236
 comment.validator.akismetMessage=\u8BC4\u8BBA\u5185\u5BB9\u88AB\u8FC7\u6EE4\u670D\u52A1\u65B9(akismet.com)\u8BA4\u5B9A\u4E3A\u5783\u573E\u4FE1\u606F
@@ -333,6 +331,7 @@
 configForm.requireEmailActivation=\u8981\u6C42\u65B0\u7528\u6237\u901A\u8FC7\u7535\u5B50\u90AE\u4EF6\u6FC0\u6D3B\u8D26\u53F7
 configForm.allowNewUsers=\u662F\u5426\u5141\u8BB8\u65B0\u7528\u6237\u6CE8\u518C\uFF1F
 configForm.registrationUrl=\u5916\u90E8\u6CE8\u518CURL
+configForm.commentSettings=\u8BC4\u8BBA\u8BBE\u7F6E
 configForm.editorPages=\u7F16\u8F91\u5668\u9875\u9762
 
 configForm.webServicesSettings=Web\u670D\u52A1\u8BBE\u7F6E
@@ -345,16 +344,12 @@
 configForm.newsfeedMaxEntries=\u65B0\u95FB\u6E90\u63D0\u4F9B\u7684\u6587\u7AE0\u6570\u91CF\u4E0A\u9650
 configForm.styledFeeds=\u5728\u6D4F\u89C8\u5668\u4E2D\u663E\u793A\u5E26\u6837\u5F0F\u7684\u65B0\u95FB\u6E90
 
-configForm.commentSettings=\u8BC4\u8BBA\u548C\u5F15\u7528\u8BBE\u7F6E
 configForm.enableComments=\u5141\u8BB8\u6587\u7AE0\u8BC4\u8BBA\uFF1F
 configForm.ignoreSpamComments=\u4E0D\u4FDD\u5B58\u5783\u573E\u8BC4\u8BBA
-configForm.enableTrackbacks=\u5141\u8BB8\u535A\u5BA2\u5F15\u7528\uFF1F
-configForm.ignoreSpamTrackbacks=\u4E0D\u4FDD\u5B58\u5783\u573E\u5F15\u7528
 configForm.commentHtmlAllowed=\u5141\u8BB8\u8BC4\u8BBA\u4E2D\u4F7F\u7528HTML\uFF1F
 configForm.commentPlugins=\u542F\u7528/\u7981\u7528\u8BC4\u8BBA\u683C\u5F0F\u5316\u63D2\u4EF6
 configForm.emailComments=\u5C06\u8BC4\u8BBA\u53D1\u9001\u7535\u5B50\u90AE\u4EF6\u901A\u77E5\uFF1F
 configForm.moderationRequired=\u6240\u6709\u535A\u5BA2\u9700\u8981\u7BA1\u7406\u8BC4\u8BBA
-configForm.enableTrackbackValidation=\u542F\u7528\u5F15\u7528\u94FE\u63A5\u6838\u67E5\uFF1F
 
 configForm.fileUploadSettings=\u6587\u4EF6\u4E0A\u4F20\u8BBE\u7F6E
 configForm.enableFileUploads=\u542F\u7528\u6587\u4EF6\u4E0A\u4F20\uFF1F
diff --git a/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties b/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties
index 32fbc59..cdb7d52 100644
--- a/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties
+++ b/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties
@@ -152,7 +152,7 @@
 search.index.comments=true
 
 #----------------------------------
-# comments and trackbacks
+# comments
 
 # comment throttling
 comment.throttle.enabled=false
@@ -192,9 +192,6 @@
 # enables site full bannedwordslist check on comment posts (default: true)
 site.bannedwordslist.enable.comments=true
 
-# enables site full bannedwordslist check at time of trackback post (default: true)
-site.bannedwordslist.enable.trackbacks=true
-
 # enables partial bannedwordslist check (not including bannedwordslist.txt) for each incoming referrer
 site.bannedwordslist.enable.referrers=false
 
@@ -385,7 +382,7 @@
 #---------------------------------------------------------------------
 # LDAP authentication properties -- valid only if LDAP authentication
 # authentication.method via authentication.method setting.
-# See also comments and trackbacks section above for addition LDAP
+# See also the comments section above for additional LDAP
 # config options.
 
 # Set these properties for a custom LDAP schema (optional)
diff --git a/app/src/main/resources/org/apache/roller/weblogger/config/runtimeConfigDefs.xml b/app/src/main/resources/org/apache/roller/weblogger/config/runtimeConfigDefs.xml
index 0071170..12091fe 100644
--- a/app/src/main/resources/org/apache/roller/weblogger/config/runtimeConfigDefs.xml
+++ b/app/src/main/resources/org/apache/roller/weblogger/config/runtimeConfigDefs.xml
@@ -151,7 +151,7 @@
    </display-group>
    
    
-   <!-- Comment & Trackback Settings Group -->
+   <!-- Comment Settings Group -->
    <display-group name="commentSettings" key="configForm.commentSettings" >
    
       <property-def  name="users.comments.enabled"  key="configForm.enableComments">
@@ -162,14 +162,6 @@
          <type>boolean</type>
          <default-value>false</default-value>
       </property-def>
-      <property-def  name="users.trackbacks.enabled"  key="configForm.enableTrackbacks">
-         <type>boolean</type>
-         <default-value>true</default-value>
-      </property-def>
-      <property-def  name="trackbacks.ignoreSpam.enabled"  key="configForm.ignoreSpamTrackbacks">
-         <type>boolean</type>
-         <default-value>true</default-value>
-      </property-def>
       <property-def  name="users.comments.htmlenabled"  key="configForm.commentHtmlAllowed">
          <type>boolean</type>
          <default-value>false</default-value>
@@ -187,11 +179,6 @@
          <type>boolean</type>
          <default-value>false</default-value>
       </property-def>
-      <property-def name="site.trackbackVerification.enabled" key="configForm.enableTrackbackValidation">
-         <type>boolean</type>
-         <default-value>false</default-value>
-      </property-def>
-
    </display-group >
    
    <!-- File Upload Settings Group -->
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp
index 8d3d417..4a98777 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp
@@ -273,14 +273,22 @@
                                             </s:else>
                                         </div>
 
-                                        <s:if test="#comment.url != null && !#comment.url.equals('')">
+                                        <s:set var="safeCommentUrl" value="#comment.safeUrl"/>
+                                        <s:if test="#safeCommentUrl != null">
                                             <div class="details">
                                                 <s:text name="commentManagement.commentByURL"/>&nbsp;:&nbsp;
-                                                <a href="<s:property value="#comment.url" />">
+                                                <a href='<s:property value="#safeCommentUrl" escapeHtml="true" />'>
                                                     <str:truncateNicely upper="60" appendToEnd="..."><s:property
-                                                            value="#comment.url"/></str:truncateNicely></a>
+                                                            value="#safeCommentUrl" escapeHtml="true"/></str:truncateNicely></a>
                                             </div>
                                         </s:if>
+                                        <s:elseif test="#comment.url != null && #comment.url.trim().length() > 0">
+                                            <div class="details">
+                                                <s:text name="commentManagement.commentByURL"/>&nbsp;:&nbsp;
+                                                <str:truncateNicely upper="60" appendToEnd="..."><s:property
+                                                        value="#comment.url" escapeHtml="true"/></str:truncateNicely>
+                                            </div>
+                                        </s:elseif>
 
                                         <div class="details">
                                             <s:text name="commentManagement.postTime"/>&nbsp;:&nbsp;
diff --git a/app/src/main/webapp/WEB-INF/velocity/weblog.vm b/app/src/main/webapp/WEB-INF/velocity/weblog.vm
index 212f376..b80804d 100644
--- a/app/src/main/webapp/WEB-INF/velocity/weblog.vm
+++ b/app/src/main/webapp/WEB-INF/velocity/weblog.vm
@@ -22,7 +22,6 @@
 
     #includeTemplate($weblog $pageName)
     #showAutodiscoveryLinks($weblog)
-    #showTrackbackAutodiscovery($entry)
     #showMetaDescription()
     #showAnalyticsTrackingCode($weblog)
 
@@ -115,28 +114,11 @@
 
 
 #**
- * Display a trackback auto-discovery RDF comment for a WeblogEntry, but only
- * if trackbacks are enabled and comments are allowed for the entry.
- **#
-#macro( showTrackbackAutodiscovery $entry )
-#if($config.trackbacksEnabled && $model.weblog.allowComments && $entry.commentsStillAllowed)
-<!--
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-         xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
-         xmlns:dc="http://purl.org/dc/elements/1.1/">
-<rdf:Description
-    rdf:about="$url.entry($entry.anchor)"
-    trackback:ping="$url.trackback($entry.anchor)"
-    dc:title="$entry.title"
-    dc:identifier="$url.entry($entry.anchor)"
-    dc:subject="$entry.category.name"
-    dc:description="$entry.title"
-    dc:creator="$entry.creator.userName"
-    dc:date="$entry.pubTime" />
-</rdf:RDF>
--->
+Deprecated no-op retained temporarily for custom template compatibility.
+*#
+#macro(showTrackbackAutodiscovery $entry)
 #end
-#end
+
 
 #**
 Adds a meta description tag, suitable for use in HTML header sections.  This tag is frequently used by
@@ -1041,5 +1023,3 @@
 #end
 
 
-
-
diff --git a/app/src/main/webapp/WEB-INF/web.xml b/app/src/main/webapp/WEB-INF/web.xml
index 019975c..31539fc 100644
--- a/app/src/main/webapp/WEB-INF/web.xml
+++ b/app/src/main/webapp/WEB-INF/web.xml
@@ -100,12 +100,11 @@
         <dispatcher>FORWARD</dispatcher>
     </filter-mapping>
 
-    <!-- Ip Banning is mapped for comment and trackbacks only.
+    <!-- Ip Banning is mapped for comments only.
     Note: this filter does nothing if an ip ban list is not configured. -->
     <filter-mapping>
         <filter-name>IPBanFilter</filter-name>
         <url-pattern>/roller-ui/rendering/comment/*</url-pattern>
-        <url-pattern>/roller-ui/rendering/trackback/*</url-pattern>
         <dispatcher>FORWARD</dispatcher>
     </filter-mapping>
     
@@ -243,12 +242,6 @@
     </servlet>
 
     <servlet>
-        <servlet-name>TrackbackServlet</servlet-name>
-        <servlet-class>org.apache.roller.weblogger.ui.rendering.servlets.TrackbackServlet</servlet-class>
-        <load-on-startup>7</load-on-startup>
-    </servlet>
-
-    <servlet>
         <servlet-name>RSDServlet</servlet-name>
         <servlet-class>org.apache.roller.weblogger.ui.rendering.servlets.RSDServlet</servlet-class>
         <load-on-startup>7</load-on-startup>
@@ -384,11 +377,6 @@
     </servlet-mapping>
 
     <servlet-mapping>
-        <servlet-name>TrackbackServlet</servlet-name>
-        <url-pattern>/roller-ui/rendering/trackback/*</url-pattern>
-    </servlet-mapping>
-
-    <servlet-mapping>
         <servlet-name>RSDServlet</servlet-name>
         <url-pattern>/roller-ui/rendering/rsd/*</url-pattern>
     </servlet-mapping>
diff --git a/app/src/main/webapp/robots.txt b/app/src/main/webapp/robots.txt
index 1d9bb7c..b442054 100644
--- a/app/src/main/webapp/robots.txt
+++ b/app/src/main/webapp/robots.txt
@@ -4,6 +4,5 @@
 Disallow: /theme
 Disallow: /language
 Disallow: /resources
-Disallow: /trackback
 Disallow: /comment
-Disallow: /main.do
\ No newline at end of file
+Disallow: /main.do
diff --git a/app/src/main/webapp/themes/base.css b/app/src/main/webapp/themes/base.css
index 7f5f104..ce424ec 100644
--- a/app/src/main/webapp/themes/base.css
+++ b/app/src/main/webapp/themes/base.css
@@ -212,7 +212,7 @@
   border: 1px solid #999;

   font-size: 1em;

 }

-#nextEntry {
+#nextEntry {

     text-align: right;

 }

 #previousEntry {

diff --git a/app/src/main/webapp/themes/basic/_day.vm b/app/src/main/webapp/themes/basic/_day.vm
index 8d3a756..a798ccd 100644
--- a/app/src/main/webapp/themes/basic/_day.vm
+++ b/app/src/main/webapp/themes/basic/_day.vm
@@ -30,7 +30,6 @@
                 <a href="$link" class="commentsLink">$text.get("macro.weblog.comments") [$commentCount]</a>
             #end
         </p>
-        #showTrackbackAutodiscovery($entry)
       </div>
     #end
 
diff --git a/app/src/main/webapp/themes/basicmobile/_day.vm b/app/src/main/webapp/themes/basicmobile/_day.vm
index 8d3a756..a798ccd 100644
--- a/app/src/main/webapp/themes/basicmobile/_day.vm
+++ b/app/src/main/webapp/themes/basicmobile/_day.vm
@@ -30,7 +30,6 @@
                 <a href="$link" class="commentsLink">$text.get("macro.weblog.comments") [$commentCount]</a>
             #end
         </p>
-        #showTrackbackAutodiscovery($entry)
       </div>
     #end
 
diff --git a/app/src/test/java/org/apache/roller/weblogger/pojos/wrapper/WeblogEntryCommentWrapperTest.java b/app/src/test/java/org/apache/roller/weblogger/pojos/wrapper/WeblogEntryCommentWrapperTest.java
new file mode 100644
index 0000000..fe8a1f7
--- /dev/null
+++ b/app/src/test/java/org/apache/roller/weblogger/pojos/wrapper/WeblogEntryCommentWrapperTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ */
+package org.apache.roller.weblogger.pojos.wrapper;
+
+import org.apache.roller.weblogger.pojos.WeblogEntryComment;
+import org.apache.roller.weblogger.util.CommentAuthorUrl;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class WeblogEntryCommentWrapperTest {
+
+    @Test
+    void exposesEscapedHttpAndHttpsAuthorUrls() {
+        WeblogEntryComment comment = new WeblogEntryComment();
+        comment.setUrl(" https://example.org/profile?a=1&b=2 ");
+
+        WeblogEntryCommentWrapper wrapper = WeblogEntryCommentWrapper.wrap(comment, null);
+
+        assertEquals("https://example.org/profile?a=1&amp;b=2", wrapper.getUrl());
+        assertEquals("https://example.org/profile?a=1&b=2", comment.getSafeUrl());
+    }
+
+    @Test
+    void acceptsLocalAndInternationalizedAuthorUrls() {
+        assertEquals("http://localhost:8080/profile",
+                CommentAuthorUrl.normalize("http://localhost:8080/profile"));
+        assertEquals("https://intranet/profile",
+                CommentAuthorUrl.normalize("https://intranet/profile"));
+        assertEquals("http://my_host.example.com/profile",
+                CommentAuthorUrl.normalize("http://my_host.example.com/profile"));
+        assertEquals("https://例え.テスト/profile",
+                CommentAuthorUrl.normalize("https://例え.テスト/profile"));
+        assertEquals("http://Example.org/CaseSensitive",
+                CommentAuthorUrl.normalizeInput("Example.org/CaseSensitive"));
+    }
+
+    @Test
+    void omitsUnsupportedOrMalformedAuthorUrls() {
+        WeblogEntryComment comment = new WeblogEntryComment();
+        comment.setUrl("javascript:alert(1)");
+        assertEquals("", WeblogEntryCommentWrapper.wrap(comment, null).getUrl());
+        assertNull(comment.getSafeUrl());
+
+        comment.setUrl("//example.org/profile");
+        assertEquals("", WeblogEntryCommentWrapper.wrap(comment, null).getUrl());
+
+        comment.setUrl("not a url");
+        assertEquals("", WeblogEntryCommentWrapper.wrap(comment, null).getUrl());
+
+        comment.setUrl("  ");
+        assertEquals("", WeblogEntryCommentWrapper.wrap(comment, null).getUrl());
+
+        assertNull(CommentAuthorUrl.normalize("https://user@example.org/profile"));
+        assertNull(CommentAuthorUrl.normalize("https://example.org:99999/profile"));
+    }
+}
diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/rendering/IncomingTrackbackRemovalTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/rendering/IncomingTrackbackRemovalTest.java
new file mode 100644
index 0000000..a7491fc
--- /dev/null
+++ b/app/src/test/java/org/apache/roller/weblogger/ui/rendering/IncomingTrackbackRemovalTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ */
+package org.apache.roller.weblogger.ui.rendering;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.roller.util.RollerConstants;
+import org.apache.roller.weblogger.ui.rendering.model.ConfigModel;
+import org.apache.roller.weblogger.ui.rendering.model.URLModel;
+import org.apache.roller.weblogger.ui.rendering.plugins.comments.TrackbackLinkbackCommentValidator;
+import org.apache.roller.weblogger.util.BannedwordslistChecker;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class IncomingTrackbackRemovalTest {
+
+    @Test
+    void incomingTrackbackEndpointsAndSettingsAreRemoved() {
+        assertThrows(ClassNotFoundException.class, () -> Class.forName(
+                "org.apache.roller.weblogger.ui.rendering.servlets.TrackbackServlet"));
+        assertThrows(ClassNotFoundException.class, () -> Class.forName(
+                "org.apache.roller.weblogger.ui.rendering.util.WeblogTrackbackRequest"));
+        assertThrows(NoSuchMethodException.class,
+                () -> ConfigModel.class.getMethod("getTrackbacksEnabled"));
+        assertThrows(NoSuchMethodException.class,
+                () -> BannedwordslistChecker.class.getMethod(
+                        "checkTrackback",
+                        org.apache.roller.weblogger.pojos.WeblogEntryComment.class));
+    }
+
+    @Test
+    @SuppressWarnings("removal")
+    void legacyExtensionPointsRemainHarmlessForOneRelease() throws Exception {
+        assertEquals("", new URLModel().trackback("entry"));
+        assertEquals(RollerConstants.PERCENT_100,
+                new TrackbackLinkbackCommentValidator().validate(null, null));
+        assertFileContains(
+                "src/main/webapp/WEB-INF/velocity/weblog.vm",
+                "#macro(showTrackbackAutodiscovery $entry)\n#end");
+    }
+
+    @Test
+    void deploymentAndRuntimeConfigurationDoNotExposeTrackbacks() throws Exception {
+        assertFileDoesNotContain("src/main/webapp/WEB-INF/web.xml", "trackback");
+        assertResourceDoesNotContain(
+                "org/apache/roller/weblogger/config/runtimeConfigDefs.xml", "trackback");
+        assertFileDoesNotContain("../docs/roller-user-guide.adoc", "trackback");
+        assertFileDoesNotContain("../docs/roller-template-guide.adoc", "trackback");
+    }
+
+    private void assertFileDoesNotContain(String path, String value) throws Exception {
+        String content = Files.readString(resolveAppPath(path), StandardCharsets.UTF_8);
+        assertFalse(content.toLowerCase().contains(value), path);
+    }
+
+    private void assertFileContains(String path, String value) throws Exception {
+        String content = Files.readString(resolveAppPath(path), StandardCharsets.UTF_8);
+        assertTrue(content.contains(value), path);
+    }
+
+    private Path resolveAppPath(String path) throws Exception {
+        Path current = Path.of(getClass().getProtectionDomain()
+                .getCodeSource().getLocation().toURI()).toAbsolutePath();
+        while (current != null) {
+            if (Files.isDirectory(current.resolve("src/main"))) {
+                return current.resolve(path).normalize();
+            }
+            current = current.getParent();
+        }
+        throw new IllegalStateException("Unable to locate the app module");
+    }
+
+    private void assertResourceDoesNotContain(String path, String value) throws Exception {
+        try (InputStream input = getClass().getClassLoader().getResourceAsStream(path)) {
+            assertNotNull(input, path);
+            String content = new String(input.readAllBytes(), StandardCharsets.UTF_8);
+            assertFalse(content.toLowerCase().contains(value), path);
+        }
+    }
+}
diff --git a/app/src/test/java/org/apache/roller/weblogger/util/LinkbackExtractorTest.java b/app/src/test/java/org/apache/roller/weblogger/util/LinkbackExtractorTest.java
deleted file mode 100644
index 8153864..0000000
--- a/app/src/test/java/org/apache/roller/weblogger/util/LinkbackExtractorTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.util;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-/**
- * Test linkback extractor.
- */
-public class LinkbackExtractorTest  {
-
-    public void testLinkbackExtractor() throws Exception {
-        String[][] testrefs = new String[][]
-        {
-            // Second URL contains a link to the first
-            {
-                "http://cassandra.apache.org/",
-                        "https://rollerweblogger.org/roller/entry/composite_keys_in_cassandra"
-            },
-            {
-                "http://roller.apache.org/downloads/downloads.html",
-                        "https://rollerweblogger.org/project/date/20140627"
-            }
-        };
-
-        LinkbackExtractor le = new LinkbackExtractor(testrefs[0][0],testrefs[0][1]);
-        assertEquals("Apache Cassandra", le.getTitle());
-        
-        le = new LinkbackExtractor(testrefs[1][0],testrefs[1][1]);
-        assertEquals("Apache Roller", le.getTitle());
-
-        // todo: le.getPermalink() and le.getExcerpt() working
-    }
-
-    
-}
diff --git a/docs/examples/configs/tomcat/mod_jk.conf b/docs/examples/configs/tomcat/mod_jk.conf
index b2dfe97..6f6572a 100644
--- a/docs/examples/configs/tomcat/mod_jk.conf
+++ b/docs/examples/configs/tomcat/mod_jk.conf
@@ -7,7 +7,6 @@
 JkMount /roller/page/* ajp13
 JkMount /roller/comments/* ajp13
 JkMount /roller/preview/* ajp13
-JkMount /roller/trackback/* ajp13
 JkMount /roller/ExportServlet ajp13
 JkMount /roller/auth/* ajp13
 JkMount /roller/rss/* ajp13
diff --git a/docs/roller-template-guide.adoc b/docs/roller-template-guide.adoc
index b121dd0..4caeeeb 100644
--- a/docs/roller-template-guide.adoc
+++ b/docs/roller-template-guide.adoc
@@ -843,9 +843,6 @@
 |String
 |Absolute URL of a resource within a Roller theme.
 
-|$url.trackback(String anchor)
-|String
-|Trackback URL for entry specified by anchor.
 |===
 
 === $utils
@@ -1858,21 +1855,6 @@
 
 No style-able markup is produced.
 
-`#showTrackbackAutodiscovery($entry)`
-
-Arguments:
-
-*$entry:* WeblogEntry object
-
-Synopsis:
-
-Show trackback autodiscovery code for a specified weblog entry, suitable
-for use within a day template.
-
-Generated HTML and CSS classes used
-
-No style-able markup is produced.
-
 `#showAtomFeedsList($weblog)`
 
 Arguments:
diff --git a/docs/roller-user-guide.adoc b/docs/roller-user-guide.adoc
index e047748..050b4ce 100644
--- a/docs/roller-user-guide.adoc
+++ b/docs/roller-user-guide.adoc
@@ -54,8 +54,6 @@
 * *Comment*. A comment posted by a visitor to a weblog and regarding one
 specific weblog post. A comment has an email address, a publication
 timestamp and some content.
-* *Trackback*. A comment posted by a remote weblog regarding one
-specific weblog post. Trackbacks are stored as comments by Roller.
 * *Templates*. Each Roller weblog is defined by a set of HTML and CSS
 templates that provide the layout and styles for the weblog. Normally
 templates are authored using Velocity template language, but other
@@ -611,13 +609,10 @@
 * *BlogID*: fredsblog
 * *URL*: http://jroller.com/roller-services/xmlrpc
 
-== Working with comments and trackbacks
+== Working with comments
 
-Roller supports weblog comments and _trackbacks_, which provide a way
-for other bloggers to add comments to your blog remotely. By default
-comments and trackbacks are enabled, but you can turn them off on your
-weblog’s Weblog Settings page of your weblog. Note that turning off
-comments will disable both comments and trackbacks.
+Roller supports weblog comments, which are enabled by default. You can
+turn them off on your weblog’s Weblog Settings page.
 
 === Comment notification via email
 
@@ -705,21 +700,20 @@
 
 === *Preventing weblog spam*
 
-There are two forms of comment spam that can affect your weblog:
+Comment spam can affect your weblog:
 
 ** _Comment spam_: spam that arrives via the comment form on your
 weblog. Sometimes spam comments are added by a human and sometimes by a
 computer program known as a _spambot._
-** _Trackback spam_: spam that arrives via trackbacks sent by a spambot.
 
-Fortunately, there are counter-measures for each type of spam. Here are
+Fortunately, there are counter-measures. Here are
 Roller’s built in spam prevention measures:
 
 * _Pluggable comment authentication_. By default, Roller asks each
 commenter a simple math question to ensure that they are a person and
 not a spam robot. Your site administrator can turn this off or replace
 it with another form of authentication.
-* _Pluggable comment validation_. Roller includes five comment
+* _Pluggable comment validation_. Roller includes four comment
 validators below. Your site administrator can adjust the settings for
 these validators and can enable/disable them as needed by overriding
 Roller’s configuration properties (see the Installation Guide for more
@@ -730,8 +724,6 @@
 characters as spam (default: on)
 ** Bannedwordslist validator marks comments containing any of your site’s
 designated bad words as spam (default: on)
-** Trackback verification validator will check incoming trackbacks to
-ensure that they link to you.
 ** Akismet validator allows you to use the Akismet.com spam prevention
 service.
 * _Comment throttling_. If your site is being abused by a spam robot
@@ -747,11 +739,11 @@
 are really concerned about displaying offensive content on your weblog
 even for a short time, then enable comment moderation on your weblog.
 
-Roller uses a _bannedwordslist_, a lists of words which are used to check
-incoming comments, trackbacks and requestors for spam URLs. If the name,
-URL or content of a comment or trackback includes one of the bannedwordslist
-words or matches one of the expressions then that comment or trackback
-is marked as spam and is not displayed on your weblog, unless you use
+Roller uses a _bannedwordslist_, a list of words used to check incoming
+comments and requestors for spam URLs. If the name, URL or content of a
+comment includes one of the bannedwordslist words or matches one of the
+expressions, then that comment is marked as spam and is not displayed on your
+weblog, unless you use
 the comment management page to unmark it.
 
 Actually, there are three levels of bannedwordslist:
@@ -764,8 +756,8 @@
 ** Level 3 bannedwordslist: Weblog specific bannedwordslist, which you control in
 the Weblog Settings page of your weblog.
 
-Incoming comments and trackbacks are checked against all three levels of
-bannedwordslist. Incoming web page requests, however, are only checked against
+Incoming comments are checked against all three levels of bannedwordslist.
+Incoming web page requests, however, are only checked against
 the levels 2 and 3 bannedwordslist and will receive a 403 (forbidden) message
 if found.
 
@@ -936,7 +928,7 @@
 image::user-guide-spam.png[]
 
 * **Ignore incoming URLs that contain any of these words - **you can use
-this to filter out what commentors, trackbacks, and referrers (web page
+this to filter out what commenters and referrers (web page
 requestors) are accepted. See Section
 [link:#5.5.Preventing%20weblog%20spam%20%7Coutline[5.5]]for more
 information on spam prevention.
@@ -1254,8 +1246,6 @@
 
 * *Allow weblog comments*: By un-setting this you can turn off weblog
 comments on all weblogs in the system.
-* *Allow trackbacks*: By un-setting this you can turn off incoming
-trackbacks on all weblogs in the system.
 * *Autoformat comments*: If this is on, Roller will auto-format comments
 by adding in line-breaks where appropriate.
 * *Escape comment HTML*: By setting this, you can disallow HTML in
@@ -1265,11 +1255,6 @@
 notification of new comments. This won’t work unless you configured
 Roller properly for sending email as described in the Roller
 installation guide.
-* *Enable verification of trackback links*: Trackback verification
-checks each incoming trackback to verify that the site sending the
-trackback actually links to the specific weblog entry that is the target
-of the trackback.
-
 image::user-guide-28-feed.png[]
 
 * *Default number of entries*: default number of entries to appear in