SafeHtml.java
Upload User: gdxydsw
Upload Date: 2019-01-29
Package Size: 16721k
Code Size: 9k
Category:

Java Develop

Development Platform:

Java

  1. /*
  2.  * Copyright (c) JForum Team
  3.  * All rights reserved.
  4.  * 
  5.  * Redistribution and use in source and binary forms, 
  6.  * with or without modification, are permitted provided 
  7.  * that the following conditions are met:
  8.  * 
  9.  * 1) Redistributions of source code must retain the above 
  10.  * copyright notice, this list of conditions and the 
  11.  * following  disclaimer.
  12.  * 2)  Redistributions in binary form must reproduce the 
  13.  * above copyright notice, this list of conditions and 
  14.  * the following disclaimer in the documentation and/or 
  15.  * other materials provided with the distribution.
  16.  * 3) Neither the name of "Rafael Steil" nor 
  17.  * the names of its contributors may be used to endorse 
  18.  * or promote products derived from this software without 
  19.  * specific prior written permission.
  20.  * 
  21.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 
  22.  * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
  23.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
  24.  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
  25.  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  26.  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
  27.  * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
  28.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  29.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
  30.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
  31.  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
  32.  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
  33.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
  34.  * IN CONTRACT, STRICT LIABILITY, OR TORT 
  35.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
  36.  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
  37.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
  38.  * 
  39.  * This file creation date: 27/09/2004 23:59:10
  40.  * The JForum Project
  41.  * http://www.jforum.net
  42.  */
  43. package net.jforum.util;
  44. import java.util.HashSet;
  45. import java.util.Iterator;
  46. import java.util.Set;
  47. import java.util.Vector;
  48. import net.jforum.exceptions.ForumException;
  49. import net.jforum.util.preferences.ConfigKeys;
  50. import net.jforum.util.preferences.SystemGlobals;
  51. import net.jforum.view.forum.common.ViewCommon;
  52. import org.htmlparser.Attribute;
  53. import org.htmlparser.Node;
  54. import org.htmlparser.Tag;
  55. import org.htmlparser.lexer.Lexer;
  56. import org.htmlparser.nodes.TextNode;
  57. /**
  58.  * Process text with html and remove possible malicious tags and attributes.
  59.  * Work based on tips from Amit Klein and the following documents:
  60.  * <br>
  61.  * <li>http://ha.ckers.org/xss.html
  62.  * <li>http://quickwired.com/kallahar/smallprojects/php_xss_filter_function.php
  63.  * <br>
  64.  * @author Rafael Steil
  65.  * @version $Id: SafeHtml.java,v 1.25 2007/09/19 14:08:57 rafaelsteil Exp $
  66.  */
  67. public class SafeHtml 
  68. {
  69. private static Set welcomeTags;
  70. private static Set welcomeAttributes;
  71. private static Set allowedProtocols;
  72. static {
  73. welcomeTags = new HashSet();
  74. welcomeAttributes = new HashSet();
  75. allowedProtocols = new HashSet();
  76. splitAndTrim(ConfigKeys.HTML_TAGS_WELCOME, welcomeTags);
  77. splitAndTrim(ConfigKeys.HTML_ATTRIBUTES_WELCOME, welcomeAttributes);
  78. splitAndTrim(ConfigKeys.HTML_LINKS_ALLOW_PROTOCOLS, allowedProtocols);
  79. }
  80. private static void splitAndTrim(String s, Set data)
  81. {
  82. String s1 = SystemGlobals.getValue(s);
  83. if (s1 == null) {
  84. return;
  85. }
  86. String[] tags = s1.toUpperCase().split(",");
  87. for (int i = 0; i < tags.length; i++) {
  88. data.add(tags[i].trim());
  89. }
  90. }
  91. /**
  92.  * Given an input, analyze each HTML tag and remove unsecure attributes from them. 
  93.  * @param contents The content to verify
  94.  * @return the content, secure. 
  95.  */
  96. public String ensureAllAttributesAreSafe(String contents) 
  97. {
  98. StringBuffer sb = new StringBuffer(contents.length());
  99. try {
  100. Lexer lexer = new Lexer(contents);
  101. Node node;
  102. while ((node = lexer.nextNode()) != null) {
  103. if (node instanceof Tag) {
  104. Tag tag = (Tag)node;
  105. this.checkAndValidateAttributes(tag, false);
  106. sb.append(tag.toHtml());
  107. }
  108. else {
  109. sb.append(node.toHtml());
  110. }
  111. }
  112. }
  113. catch (Exception e) {
  114. throw new ForumException("Problems while parsing HTML: " + e, e);
  115. }
  116. return sb.toString();
  117. }
  118. /**
  119.  * Given an input, makes it safe for HTML displaying. 
  120.  * Removes any not allowed HTML tag or attribute, as well
  121.  * unwanted Javascript statements inside the tags. 
  122.  * @param contents the input to analyze
  123.  * @return the modified and safe string
  124.  */
  125. public String makeSafe(String contents)
  126. {
  127. if (contents == null || contents.length() == 0) {
  128. return contents;
  129. }
  130. StringBuffer sb = new StringBuffer(contents.length());
  131. try {
  132. Lexer lexer = new Lexer(contents);
  133. Node node;
  134. while ((node = lexer.nextNode()) != null) {
  135. boolean isTextNode = node instanceof TextNode;
  136. if (isTextNode) {
  137. // Text nodes are raw data, so we just
  138. // strip off all possible html content
  139. String text = node.toHtml();
  140. if (text.indexOf('>') > -1 || text.indexOf('<') > -1) {
  141. StringBuffer tmp = new StringBuffer(text);
  142. ViewCommon.replaceAll(tmp, "<", "&lt;");
  143. ViewCommon.replaceAll(tmp, ">", "&gt;");
  144. ViewCommon.replaceAll(tmp, """, "&quot;");
  145. node.setText(tmp.toString());
  146. }
  147. }
  148. if (isTextNode || (node instanceof Tag && this.isTagWelcome(node))) {
  149. sb.append(node.toHtml());
  150. }
  151. else {
  152. StringBuffer tmp = new StringBuffer(node.toHtml());
  153. ViewCommon.replaceAll(tmp, "<", "&lt;");
  154. ViewCommon.replaceAll(tmp, ">", "&gt;");
  155. sb.append(tmp.toString());
  156. }
  157. }
  158. }
  159. catch (Exception e) {
  160. throw new ForumException("Error while parsing HTML: " + e, e);
  161. }
  162. return sb.toString();
  163. }
  164. /**
  165.  * Returns true if a given tag is allowed. 
  166.  * Also, it checks and removes any unwanted attribute the tag may contain. 
  167.  * @param node The tag node to analyze
  168.  * @return true if it is a valid tag. 
  169.  */
  170. private boolean isTagWelcome(Node node)
  171. {
  172. Tag tag = (Tag)node;
  173. if (!welcomeTags.contains(tag.getTagName())) {
  174. return false;
  175. }
  176. this.checkAndValidateAttributes(tag, true);
  177. return true;
  178. }
  179. /**
  180.  * Given a tag, check its attributes, removing those unwanted or not secure 
  181.  * @param tag The tag to analyze
  182.  * @param checkIfAttributeIsWelcome true if the attribute name should be matched
  183.  * against the list of welcome attributes, set in the main configuration file. 
  184.  */
  185. private void checkAndValidateAttributes(Tag tag, boolean checkIfAttributeIsWelcome)
  186. {
  187. Vector newAttributes = new Vector();
  188. for (Iterator iter = tag.getAttributesEx().iterator(); iter.hasNext(); ) {
  189. Attribute a = (Attribute)iter.next();
  190. String name = a.getName();
  191. if (name == null) {
  192. newAttributes.add(a);
  193. }
  194. else {
  195. name = name.toUpperCase();
  196. if (a.getValue() == null) {
  197. newAttributes.add(a);
  198. continue;
  199. }
  200. String value = a.getValue().toLowerCase();
  201. if (checkIfAttributeIsWelcome && !this.isAttributeWelcome(name)) {
  202. continue;
  203. }
  204. if (!this.isAttributeSafe(name, value)) {
  205. continue;
  206. }
  207. if (a.getValue().indexOf("&#") > -1) {
  208. a.setValue(a.getValue().replaceAll("&#", "&amp;#"));
  209. }
  210. newAttributes.add(a);
  211. }
  212. }
  213. tag.setAttributesEx(newAttributes);
  214. }
  215. /**
  216.  * Check if the given attribute name is in the list of allowed attributes
  217.  * @param name the attribute name
  218.  * @return true if it is an allowed attribute name
  219.  */
  220. private boolean isAttributeWelcome(String name)
  221. {
  222. return welcomeAttributes.contains(name);
  223. }
  224. /**
  225.  * Check if the attribute is safe, checking either its name and value. 
  226.  * @param name the attribute name
  227.  * @param value the attribute value
  228.  * @return true if it is a safe attribute
  229.  */
  230. private boolean isAttributeSafe(String name, String value)
  231. {
  232. if (name.length() >= 2 && name.charAt(0) == 'O' && name.charAt(1) == 'N') {
  233. return false;
  234. }
  235. if (value.indexOf('n') > -1 || value.indexOf('r') > -1 || value.indexOf('') > -1) {
  236. return false;
  237. }
  238. if (("HREF".equals(name) || "SRC".equals(name))) {
  239. if (!this.isHrefValid(value)) {
  240. return false;
  241. }
  242. }
  243. else if ("STYLE".equals(name)) {
  244. // It is much more a try to not allow constructions
  245. // like style="background-color: url(javascript:xxxx)" than anything else
  246. if (value.indexOf('(') > -1) {
  247. return false;
  248. }
  249. }
  250. return true;
  251. }
  252. /**
  253.  * Checks if a given address is valid
  254.  * @param href The address to check
  255.  * @return true if it is valid
  256.  */
  257. private boolean isHrefValid(String href) 
  258. {
  259. if (SystemGlobals.getBoolValue(ConfigKeys.HTML_LINKS_ALLOW_RELATIVE)
  260. && href.length() > 0 
  261. && href.charAt(0) == '/') {
  262. return true;
  263. }
  264. for (Iterator iter = allowedProtocols.iterator(); iter.hasNext(); ) {
  265. String protocol = iter.next().toString().toLowerCase();
  266. if (href.startsWith(protocol)) {
  267. return true;
  268. }
  269. }
  270. return false;
  271. }
  272. }