KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > blojsom > plugin > filter > XSSFilterPlugin


1 /**
2  * Copyright (c) 2003-2006, David A. Czarnecki
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * Redistributions of source code must retain the above copyright notice, this list of conditions and the
9  * following disclaimer.
10  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
11  * following disclaimer in the documentation and/or other materials provided with the distribution.
12  * Neither the name of "David A. Czarnecki" and "blojsom" nor the names of its contributors may be used to
13  * endorse or promote products derived from this software without specific prior written permission.
14  * Products derived from this software may not be called "blojsom", nor may "blojsom" appear in their name,
15  * without prior written permission of David A. Czarnecki.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
18  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
19  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
21  * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */

31 package org.blojsom.plugin.filter;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.blojsom.blog.Blog;
36 import org.blojsom.blog.Entry;
37 import org.blojsom.event.Event;
38 import org.blojsom.event.EventBroadcaster;
39 import org.blojsom.event.Listener;
40 import org.blojsom.plugin.Plugin;
41 import org.blojsom.plugin.PluginException;
42 import org.blojsom.plugin.admin.event.ProcessEntryEvent;
43 import org.blojsom.plugin.comment.event.CommentResponseSubmissionEvent;
44 import org.blojsom.util.BlojsomUtils;
45
46 import javax.servlet.http.HttpServletRequest JavaDoc;
47 import javax.servlet.http.HttpServletResponse JavaDoc;
48 import java.util.Map JavaDoc;
49 import java.util.regex.Matcher JavaDoc;
50 import java.util.regex.Pattern JavaDoc;
51
52 /**
53  * XSSFilterPlugin
54  *
55  * @author David Czarnecki
56  * @version $Id: XSSFilterPlugin.java,v 1.4 2006/08/30 13:37:57 czarneckid Exp $
57  * @since blojsom 3.0
58  */

59 public class XSSFilterPlugin implements Plugin, Listener {
60
61     private Log _logger = LogFactory.getLog(XSSFilterPlugin.class);
62
63     // Default set of balanced and unbalanced tags
64
private static final String JavaDoc [] DEFAULT_ALLOWED_BALANCED_TAGS = {"b", "strong", "i", "em", "u", "s", "blockquote", "pre", "ul", "li", "ol"};
65     private static final String JavaDoc [] DEFAULT_ALLOWED_UNBALANCED_TAGS = {"br", "img"};
66
67     // Initialization parameters
68
private static final String JavaDoc XSS_FILTER_ALLOWED_BALANCED_TAGS_IP = "plugin-xss-filter-allowed-balanced-tags";
69     private static final String JavaDoc XSS_FILTER_ALLOWED_UNBALANCED_TAGS_IP = "plugin-xss-filter-allowed-unbalanced-tags";
70     private static final String JavaDoc XSS_FILTER_ALLOW_LINKS_IP = "plugin-xss-filter-allow-links";
71     private static final String JavaDoc XSS_FILTER_PROCESS_ENTRIES_IP = "plugin-xss-filter-process-entries";
72
73     // Context variables
74
private static final String JavaDoc XSS_FILTER_ALLOWED_BALANCED_TAGS = "XSS_FILTER_ALLOWED_BALANCED_TAGS";
75     private static final String JavaDoc XSS_FILTER_ALLOWED_UNBALANCED_TAGS = "XSS_FILTER_ALLOWED_UNBALANCED_TAGS";
76     private static final String JavaDoc XSS_FILTER_ALLOW_LINKS = "XSS_FILTER_ALLOW_LINKS";
77
78     private EventBroadcaster _eventBroadcaster;
79
80     /**
81      * Set the {@link EventBroadcaster} to use
82      *
83      * @param eventBroadcaster {@link EventBroadcaster}
84      */

85     public void setEventBroadcaster(EventBroadcaster eventBroadcaster) {
86         _eventBroadcaster = eventBroadcaster;
87     }
88
89     /**
90      * Initialize this plugin. This method only called when the plugin is instantiated.
91      *
92      * @throws org.blojsom.plugin.PluginException
93      * If there is an error initializing the plugin
94      */

95     public void init() throws PluginException {
96         _eventBroadcaster.addListener(this);
97     }
98
99     /**
100      * Process the blog entries
101      *
102      * @param httpServletRequest Request
103      * @param httpServletResponse Response
104      * @param blog {@link Blog} instance
105      * @param context Context
106      * @param entries Blog entries retrieved for the particular request
107      * @return Modified set of blog entries
108      * @throws PluginException If there is an error processing the blog entries
109      */

110     public Entry[] process(HttpServletRequest JavaDoc httpServletRequest, HttpServletResponse JavaDoc httpServletResponse, Blog blog, Map JavaDoc context, Entry[] entries) throws PluginException {
111         // Get the individual blog's initialization parameters
112
String JavaDoc allowedBalancedTagsIP = blog.getProperty(XSS_FILTER_ALLOWED_BALANCED_TAGS_IP);
113         String JavaDoc[] allowedBalancedTags = DEFAULT_ALLOWED_BALANCED_TAGS;
114         if (!BlojsomUtils.checkNullOrBlank(allowedBalancedTagsIP)) {
115             allowedBalancedTags = BlojsomUtils.parseCommaList(allowedBalancedTagsIP);
116         }
117         context.put(XSS_FILTER_ALLOWED_BALANCED_TAGS, allowedBalancedTags);
118
119         String JavaDoc allowedUnbalancedTagsIP = blog.getProperty(XSS_FILTER_ALLOWED_UNBALANCED_TAGS_IP);
120         String JavaDoc[] allowedUnbalancedTags = DEFAULT_ALLOWED_UNBALANCED_TAGS;
121         if (!BlojsomUtils.checkNullOrBlank(allowedUnbalancedTagsIP)) {
122             allowedUnbalancedTags = BlojsomUtils.parseCommaList(allowedUnbalancedTagsIP);
123         }
124         context.put(XSS_FILTER_ALLOWED_UNBALANCED_TAGS, allowedUnbalancedTags);
125
126         String JavaDoc allowLinksIP = blog.getProperty(XSS_FILTER_ALLOW_LINKS_IP);
127         Boolean JavaDoc allowLinks = Boolean.TRUE;
128         if (!BlojsomUtils.checkNullOrBlank(allowLinksIP)) {
129             allowLinks = Boolean.valueOf(allowLinksIP);
130         }
131         context.put(XSS_FILTER_ALLOW_LINKS, allowLinks);
132
133         return entries;
134     }
135
136     /**
137      * Perform any cleanup for the plugin. Called after {@link #process}.
138      *
139      * @throws org.blojsom.plugin.PluginException
140      * If there is an error performing cleanup for this plugin
141      */

142     public void cleanup() throws PluginException {
143     }
144
145     /**
146      * Called when BlojsomServlet is taken out of service
147      *
148      * @throws org.blojsom.plugin.PluginException
149      * If there is an error in finalizing this plugin
150      */

151     public void destroy() throws PluginException {
152     }
153
154     /**
155      * Handle an event broadcast from another component
156      *
157      * @param event {@link org.blojsom.event.Event} to be handled
158      */

159     public void handleEvent(Event event) {
160     }
161
162     /**
163      * Process an event from another component
164      *
165      * @param event {@link org.blojsom.event.Event} to be handled
166      */

167     public void processEvent(Event event) {
168         if (event instanceof CommentResponseSubmissionEvent) {
169             if (_logger.isDebugEnabled()) {
170                 _logger.debug("Processing comment response submission event");
171             }
172
173             CommentResponseSubmissionEvent commentEvent = (CommentResponseSubmissionEvent) event;
174
175             String JavaDoc commentText = commentEvent.getContent();
176             commentText = processContent(commentText, commentEvent.getBlog());
177
178             // Save the processed comment text
179
commentEvent.setContent(commentText);
180         } else if (event instanceof ProcessEntryEvent) {
181             ProcessEntryEvent entryEvent = (ProcessEntryEvent) event;
182             Blog blog = entryEvent.getBlog();
183
184             // Check to see if we should process entries through the XSS filter
185
if (Boolean.valueOf(blog.getProperty(XSS_FILTER_PROCESS_ENTRIES_IP)).booleanValue()) {
186                 if (_logger.isDebugEnabled()) {
187                     _logger.debug("Processing process blog entry event");
188                 }
189
190                 if (entryEvent.getEntry() != null) {
191                     String JavaDoc entryText = entryEvent.getEntry().getDescription();
192                     entryText = processContent(entryText, entryEvent.getBlog());
193
194                     // Save the processed entry text
195
entryEvent.getEntry().setDescription(entryText);
196
197                     String JavaDoc entryTitle = entryEvent.getEntry().getTitle();
198                     entryTitle = processContent(entryTitle, entryEvent.getBlog());
199
200                     // Save the processed entry title
201
entryEvent.getEntry().setTitle(entryTitle);
202                 }
203             }
204         }
205     }
206
207     /**
208      * Internal method to process any string content through the various routines
209      *
210      * @param content Content
211      * @param blog {@link Blog} information}
212      * @return Processed content
213      */

214     protected String JavaDoc processContent(String JavaDoc content, Blog blog) {
215         // Get the individual blog's initialization parameters
216
String JavaDoc allowedBalancedTagsIP = blog.getProperty(XSS_FILTER_ALLOWED_BALANCED_TAGS_IP);
217         String JavaDoc[] allowedBalancedTags = DEFAULT_ALLOWED_BALANCED_TAGS;
218         if (!BlojsomUtils.checkNullOrBlank(allowedBalancedTagsIP)) {
219             allowedBalancedTags = BlojsomUtils.parseCommaList(allowedBalancedTagsIP);
220         }
221
222         String JavaDoc allowedUnbalancedTagsIP = blog.getProperty(XSS_FILTER_ALLOWED_UNBALANCED_TAGS_IP);
223         String JavaDoc[] allowedUnbalancedTags = DEFAULT_ALLOWED_UNBALANCED_TAGS;
224         if (!BlojsomUtils.checkNullOrBlank(allowedUnbalancedTagsIP)) {
225             allowedUnbalancedTags = BlojsomUtils.parseCommaList(allowedUnbalancedTagsIP);
226         }
227
228         String JavaDoc allowLinksIP = blog.getProperty(XSS_FILTER_ALLOW_LINKS_IP);
229         boolean allowLinks = true;
230         if (!BlojsomUtils.checkNullOrBlank(allowLinksIP)) {
231             allowLinks = Boolean.valueOf(allowLinksIP).booleanValue();
232         }
233
234         content = BlojsomUtils.escapeStringSimple(content);
235
236         if (content != null) {
237             // Process balanced tags
238
for (int i = 0; i < allowedBalancedTags.length; i++) {
239                 String JavaDoc allowedBalancedTag = allowedBalancedTags[i];
240
241                 content = replaceBalancedTag(content, allowedBalancedTag);
242             }
243
244             // Process unbalanced tags
245
for (int i = 0; i < allowedUnbalancedTags.length; i++) {
246                 String JavaDoc allowedUnbalancedTag = allowedUnbalancedTags[i];
247
248                 content = replaceUnbalancedTag(content, allowedUnbalancedTag);
249             }
250
251             // Process links
252
if (allowLinks) {
253                 content = processLinks(content);
254             }
255
256             content = processImgTags(content);
257
258             // Escaped brackets
259
content = content.replaceAll("&amp;lt;", "&lt;");
260             content = content.replaceAll("&amp;gt;", "&gt;");
261             content = content.replaceAll("&amp;#", "&#");
262         }
263
264         return content;
265     }
266
267     /**
268      * Replace balanced tags
269      *
270      * @param input Input
271      * @param tag Tag
272      * @return String where the &lt;<code>tag</code>&gt; and &lt;<code>/tag</code>&gt; have been replaced appropriately
273      */

274     private String JavaDoc replaceBalancedTag(String JavaDoc input, String JavaDoc tag) {
275         Pattern JavaDoc openingPattern = Pattern.compile("&lt;" + tag + "&gt;", Pattern.CASE_INSENSITIVE);
276         Pattern JavaDoc closingPattern = Pattern.compile("&lt;/" + tag + "&gt;", Pattern.CASE_INSENSITIVE);
277
278         Matcher JavaDoc openingMatcher = openingPattern.matcher(input);
279         input = openingMatcher.replaceAll("<" + tag + ">");
280
281         Matcher JavaDoc closingMatcher = closingPattern.matcher(input);
282         input = closingMatcher.replaceAll("</" + tag + ">");
283
284         return input;
285     }
286
287     /**
288      * Replace unbalanced tags
289      *
290      * @param input Input
291      * @param tag Tag
292      * @return String where the &lt;<code>tag /</code>&gt; have been replaced appropriately
293      */

294     private String JavaDoc replaceUnbalancedTag(String JavaDoc input, String JavaDoc tag) {
295         Pattern JavaDoc unbalancedPattern = Pattern.compile("&lt;" + tag + "\\s*/*&gt;", Pattern.CASE_INSENSITIVE);
296
297         Matcher JavaDoc unbalancedMatcher = unbalancedPattern.matcher(input);
298         input = unbalancedMatcher.replaceAll("<" + tag + " />");
299
300         return input;
301     }
302
303     /**
304      * Process &lt;a href .../&gt; links
305      *
306      * @param input Input
307      * @return String where the &lt;a href .../&gt; links have been processed appropriately
308      */

309     private String JavaDoc processLinks(String JavaDoc input) {
310         Pattern JavaDoc openingLinkPattern = Pattern.compile("&lt;a HREF=.*?&gt;", Pattern.CASE_INSENSITIVE);
311         Pattern JavaDoc closingLinkPattern = Pattern.compile("&lt;/a&gt;", Pattern.CASE_INSENSITIVE);
312
313         Matcher JavaDoc closingMatcher = closingLinkPattern.matcher(input);
314         input = closingMatcher.replaceAll("</a>");
315
316         Matcher JavaDoc openingMatcher = openingLinkPattern.matcher(input);
317         while (openingMatcher.find()) {
318             int start = openingMatcher.start();
319             int end = openingMatcher.end();
320             String JavaDoc link = input.substring(start, end);
321             link = "<" + link.substring(4, link.length() - 4) + ">";
322             input = input.substring(0, start) + link + input.substring(end, input.length());
323             openingMatcher = openingLinkPattern.matcher(input);
324         }
325
326         return input;
327     }
328
329     /**
330      * Process &lt;img ... /&gt; tags
331      *
332      * @param input Input
333      * @return String where the &lt;img ... /&gt; links have been processed appropriately
334      */

335     private String JavaDoc processImgTags(String JavaDoc input) {
336         Pattern JavaDoc imgPattern = Pattern.compile("(&lt;)(\\s*img\\s?.*?\\s*/*)(&gt;)", Pattern.CASE_INSENSITIVE);
337         Matcher JavaDoc imgMatcher = imgPattern.matcher(input);
338
339         // Replace all occurrences of pattern in input
340
StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
341         while (imgMatcher.find()) {
342             // Insert replacement
343
imgMatcher.appendReplacement(buffer, "<" + imgMatcher.group(2) + ">");
344         }
345
346         imgMatcher.appendTail(buffer);
347
348         return buffer.toString();
349     }
350 }
Popular Tags