<p>Jeff actually posted about this in <a href="http://refactormycode.com/codes/333-sanitize-html" rel="noreferrer">Sanitize HTML</a>. But his example is in C# and I'm actually more interested in a Java version. Does anyone have a better version for Java? Is his example good enough to just convert directly from C# to Java?</p>
<p>[Update] I have put a bounty on this question because SO wasn't as popular when I asked the question as it is today (*). As for anything related to security, the more people look into it, the better it is!</p>
<p>(*) In fact, I think it was still in closed beta</p>

[{'answer_id': 27705, 'author': 'svrist', 'author_id': 86, 'author_profile': 'https://Stackoverflow.com/users/86', 'pm_score': 0, 'selected': False, 'text': '<p>The biggest problem by using jeffs code is the @ which currently isnt available. </p>\n\n<p>I would probably just take the "raw" regexp from jeffs code if i needed it and paste it into </p>\n\n<p><a href="http://www.cis.upenn.edu/~matuszek/General/RegexTester/regex-tester.html" rel="nofollow noreferrer">http://www.cis.upenn.edu/~matuszek/General/RegexTester/regex-tester.html</a> </p>\n\n<p>and see the things needing escape get escaped and then use it.</p>\n\n<hr>\n\n<p>Taking the usage of this regex in mind I would personally make sure I understood exactly what I was doing, why and what consequences would be if I didnt succeed, before copy/pasting anything, like the other answers try to help you with.</p>\n\n<p><em>(Thats propbably pretty sound advice for any copy/paste)</em></p>\n'}, {'answer_id': 533695, 'author': 'Emmanuel Rodriguez', 'author_id': 59223, 'author_profile': 'https://Stackoverflow.com/users/59223', 'pm_score': 2, 'selected': False, 'text': '<p>I\'m not to convinced that using a regular expression is the best way for finding all suspect code. Regular expressions are quite easy to trick specially when dealing with broken HTML. For example, the regular expression listed in the Sanitize HTML link will fail to remove all \'a\' elements that have an attribute between the element name and the attribute \'href\':</p>\n\n<p>&lt; a alt="xss injection" href="http://www.malicous.com/bad.php" ></p>\n\n<p>A more robust way of removing malicious code is to rely on a XML Parser that can handle all kind of HTML documents (Tidy, TagSoup, etc) and to select the elements to remove with an XPath expression. Once the HTML document is parsed into a DOM document the elements to revome can be found easily and safely. This is even easy to do with XSLT.</p>\n'}, {'answer_id': 535022, 'author': 'Chase Seibert', 'author_id': 7679, 'author_profile': 'https://Stackoverflow.com/users/7679', 'pm_score': 7, 'selected': True, 'text': '<p>Don\'t do this with regular expressions. Remember, you\'re not protecting just against valid HTML; you\'re protecting against the DOM that web browsers create. Browsers can be tricked into producing valid DOM from invalid HTML quite easily. </p>\n\n<p>For example, see this list of <a href="http://ha.ckers.org/xss.html" rel="noreferrer">obfuscated XSS attacks</a>. Are you prepared to tailor a regex to prevent this real world attack on <a href="http://www.greymagic.com/security/advisories/gm005-mc/" rel="noreferrer">Yahoo and Hotmail</a> on IE6/7/8?</p>\n\n<pre><code>&lt;HTML&gt;&lt;BODY&gt;\n&lt;?xml:namespace prefix="t" ns="urn:schemas-microsoft-com:time"&gt;\n&lt;?import namespace="t" implementation="#default#time2"&gt;\n&lt;t:set attributeName="innerHTML" to="XSS&amp;lt;SCRIPT DEFER&amp;gt;alert(&amp;quot;XSS&amp;quot;)&amp;lt;/SCRIPT&amp;gt;"&gt;\n&lt;/BODY&gt;&lt;/HTML&gt;\n</code></pre>\n\n<p>How about this attack that works on IE6? </p>\n\n<pre><code>&lt;TABLE BACKGROUND="javascript:alert(\'XSS\')"&gt;\n</code></pre>\n\n<p>How about attacks that are not listed on this site? The problem with Jeff\'s approach is that it\'s not a whitelist, as claimed. As someone on <a href="http://refactormycode.com/codes/333-sanitize-html#refactor_13642" rel="noreferrer">that page</a> adeptly notes:</p>\n\n<blockquote>\n  <p>The problem with it, is that the html\n  must be clean. There are cases where\n  you can pass in hacked html, and it\n  won\'t match it, in which case it\'ll\n  return the hacked html string as it\n  won\'t match anything to replace. This\n  isn\'t strictly whitelisting.</p>\n</blockquote>\n\n<p>I would suggest a purpose built tool like <a href="http://www.owasp.org/index.php/Category:OWASP_AntiSamy_Project" rel="noreferrer">AntiSamy</a>. It works by actually parsing the HTML, and then traversing the DOM and removing anything that\'s not in the <em>configurable</em> whitelist. The major difference is the ability to gracefully handle malformed HTML. </p>\n\n<p>The best part is that it actually unit tests for all the XSS attacks on the above site. Besides, what could be easier than this API call:</p>\n\n<pre><code>public String toSafeHtml(String html) throws ScanException, PolicyException {\n\n    Policy policy = Policy.getInstance(POLICY_FILE);\n    AntiSamy antiSamy = new AntiSamy();\n    CleanResults cleanResults = antiSamy.scan(html, policy);\n    return cleanResults.getCleanHTML().trim();\n}\n</code></pre>\n'}, {'answer_id': 538379, 'author': 'Brian', 'author_id': 18192, 'author_profile': 'https://Stackoverflow.com/users/18192', 'pm_score': 0, 'selected': False, 'text': "<p><code>[\\s\\w\\.]*</code>.  If it doesn't match, you've got XSS.  Maybe.  Take note that this expression only allows letters, numbers,  and periods.  It avoids all symbols, even useful ones, out of fear of XSS.  Once you allow &amp;, you've got worries.  And merely replacing all instances of &amp; with <code>&amp;amp;</code> is not sufficient.  Too complicated to trust :P.  Obviously this will disallow a lot of legitimate text (You can just replace all nonmatching characters with a ! or something), but I think it will kill XSS.</p>\n\n<p>The idea to just parse it as html and generate new html is probably better.</p>\n"}, {'answer_id': 937158, 'author': 'Community', 'author_id': -1, 'author_profile': 'https://Stackoverflow.com/users/-1', 'pm_score': 2, 'selected': False, 'text': '<pre><code>^(\\s|\\w|\\d|&lt;br&gt;)*?$ \n</code></pre>\n\n<p>This  will validate characters, digits, whitespaces and also the <code>&lt;br&gt;</code>  tag. \nIf you want more risk you can add more tags like</p>\n\n<pre><code>^(\\s|\\w|\\d|&lt;br&gt;|&lt;ul&gt;|&lt;\\ul&gt;)*?$\n</code></pre>\n'}, {'answer_id': 24094764, 'author': 'user3709489', 'author_id': 3709489, 'author_profile': 'https://Stackoverflow.com/users/3709489', 'pm_score': 3, 'selected': False, 'text': '<p>I extracted from NoScript best Anti-XSS addon, here is its Regex:\nWork flawless:</p>\n\n<pre><code>&lt;[^\\w&lt;&gt;]*(?:[^&lt;&gt;"\'\\s]*:)?[^\\w&lt;&gt;]*(?:\\W*s\\W*c\\W*r\\W*i\\W*p\\W*t|\\W*f\\W*o\\W*r\\W*m|\\W*s\\W*t\\W*y\\W*l\\W*e|\\W*s\\W*v\\W*g|\\W*m\\W*a\\W*r\\W*q\\W*u\\W*e\\W*e|(?:\\W*l\\W*i\\W*n\\W*k|\\W*o\\W*b\\W*j\\W*e\\W*c\\W*t|\\W*e\\W*m\\W*b\\W*e\\W*d|\\W*a\\W*p\\W*p\\W*l\\W*e\\W*t|\\W*p\\W*a\\W*r\\W*a\\W*m|\\W*i?\\W*f\\W*r\\W*a\\W*m\\W*e|\\W*b\\W*a\\W*s\\W*e|\\W*b\\W*o\\W*d\\W*y|\\W*m\\W*e\\W*t\\W*a|\\W*i\\W*m\\W*a?\\W*g\\W*e?|\\W*v\\W*i\\W*d\\W*e\\W*o|\\W*a\\W*u\\W*d\\W*i\\W*o|\\W*b\\W*i\\W*n\\W*d\\W*i\\W*n\\W*g\\W*s|\\W*s\\W*e\\W*t|\\W*i\\W*s\\W*i\\W*n\\W*d\\W*e\\W*x|\\W*a\\W*n\\W*i\\W*m\\W*a\\W*t\\W*e)[^&gt;\\w])|(?:&lt;\\w[\\s\\S]*[\\s\\0\\/]|[\'"])(?:formaction|style|background|src|lowsrc|ping|on(?:d(?:e(?:vice(?:(?:orienta|mo)tion|proximity|found|light)|livery(?:success|error)|activate)|r(?:ag(?:e(?:n(?:ter|d)|xit)|(?:gestur|leav)e|start|drop|over)?|op)|i(?:s(?:c(?:hargingtimechange|onnect(?:ing|ed))|abled)|aling)|ata(?:setc(?:omplete|hanged)|(?:availabl|chang)e|error)|urationchange|ownloading|blclick)|Moz(?:M(?:agnifyGesture(?:Update|Start)?|ouse(?:PixelScroll|Hittest))|S(?:wipeGesture(?:Update|Start|End)?|crolledAreaChanged)|(?:(?:Press)?TapGestur|BeforeResiz)e|EdgeUI(?:C(?:omplet|ancel)|Start)ed|RotateGesture(?:Update|Start)?|A(?:udioAvailable|fterPaint))|c(?:o(?:m(?:p(?:osition(?:update|start|end)|lete)|mand(?:update)?)|n(?:t(?:rolselect|extmenu)|nect(?:ing|ed))|py)|a(?:(?:llschang|ch)ed|nplay(?:through)?|rdstatechange)|h(?:(?:arging(?:time)?ch)?ange|ecking)|(?:fstate|ell)change|u(?:echange|t)|l(?:ick|ose))|m(?:o(?:z(?:pointerlock(?:change|error)|(?:orientation|time)change|fullscreen(?:change|error)|network(?:down|up)load)|use(?:(?:lea|mo)ve|o(?:ver|ut)|enter|wheel|down|up)|ve(?:start|end)?)|essage|ark)|s(?:t(?:a(?:t(?:uschanged|echange)|lled|rt)|k(?:sessione|comma)nd|op)|e(?:ek(?:complete|ing|ed)|(?:lec(?:tstar)?)?t|n(?:ding|t))|u(?:ccess|spend|bmit)|peech(?:start|end)|ound(?:start|end)|croll|how)|b(?:e(?:for(?:e(?:(?:scriptexecu|activa)te|u(?:nload|pdate)|p(?:aste|rint)|c(?:opy|ut)|editfocus)|deactivate)|gin(?:Event)?)|oun(?:dary|ce)|l(?:ocked|ur)|roadcast|usy)|a(?:n(?:imation(?:iteration|start|end)|tennastatechange)|fter(?:(?:scriptexecu|upda)te|print)|udio(?:process|start|end)|d(?:apteradded|dtrack)|ctivate|lerting|bort)|DOM(?:Node(?:Inserted(?:IntoDocument)?|Removed(?:FromDocument)?)|(?:CharacterData|Subtree)Modified|A(?:ttrModified|ctivate)|Focus(?:Out|In)|MouseScroll)|r(?:e(?:s(?:u(?:m(?:ing|e)|lt)|ize|et)|adystatechange|pea(?:tEven)?t|movetrack|trieving|ceived)|ow(?:s(?:inserted|delete)|e(?:nter|xit))|atechange)|p(?:op(?:up(?:hid(?:den|ing)|show(?:ing|n))|state)|a(?:ge(?:hide|show)|(?:st|us)e|int)|ro(?:pertychange|gress)|lay(?:ing)?)|t(?:ouch(?:(?:lea|mo)ve|en(?:ter|d)|cancel|start)|ime(?:update|out)|ransitionend|ext)|u(?:s(?:erproximity|sdreceived)|p(?:gradeneeded|dateready)|n(?:derflow|load))|f(?:o(?:rm(?:change|input)|cus(?:out|in)?)|i(?:lterchange|nish)|ailed)|l(?:o(?:ad(?:e(?:d(?:meta)?data|nd)|start)?|secapture)|evelchange|y)|g(?:amepad(?:(?:dis)?connected|button(?:down|up)|axismove)|et)|e(?:n(?:d(?:Event|ed)?|abled|ter)|rror(?:update)?|mptied|xit)|i(?:cc(?:cardlockerror|infochange)|n(?:coming|valid|put))|o(?:(?:(?:ff|n)lin|bsolet)e|verflow(?:changed)?|pen)|SVG(?:(?:Unl|L)oad|Resize|Scroll|Abort|Error|Zoom)|h(?:e(?:adphoneschange|l[dp])|ashchange|olding)|v(?:o(?:lum|ic)e|ersion)change|w(?:a(?:it|rn)ing|heel)|key(?:press|down|up)|(?:AppComman|Loa)d|no(?:update|match)|Request|zoom))[\\s\\0]*=\n</code></pre>\n\n<p>Test: <a href="http://regex101.com/r/rV7zK8" rel="noreferrer">http://regex101.com/r/rV7zK8</a></p>\n\n<p>I think it block 99% XSS because it is a part of NoScript, a addon that get updated regularly</p>\n'}, {'answer_id': 25745835, 'author': 'KIC', 'author_id': 1298461, 'author_profile': 'https://Stackoverflow.com/users/1298461', 'pm_score': 0, 'selected': False, 'text': '<p>An old thread but maybe this will be useful for other users. There is a maintained security layer tool for php: <a href="https://github.com/PHPIDS/" rel="nofollow">https://github.com/PHPIDS/</a> It is based on a set of regex which you can find here:</p>\n\n<p><a href="https://github.com/PHPIDS/PHPIDS/blob/master/lib/IDS/default_filter.xml" rel="nofollow">https://github.com/PHPIDS/PHPIDS/blob/master/lib/IDS/default_filter.xml</a></p>\n'}, {'answer_id': 53899242, 'author': 'Philip DiSarro', 'author_id': 9915320, 'author_profile': 'https://Stackoverflow.com/users/9915320', 'pm_score': 2, 'selected': False, 'text': '<p>This question perfectly illustrates a great application of the study of Theory of Computation. Theory of Computation is a field that focuses on producing and studying mathematical representations for computation.</p>\n<p>Some of the most profound research in computation theory includes the proofs that illustrate the relationships of various languages.</p>\n<p>Some of the language relationships that computation theorists have proven include:</p>\n<p><a href="https://i.stack.imgur.com/LSpaw.gif" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/LSpaw.gif" alt="enter image description here" /></a></p>\n<p>This  shows that context free languages are strictly more powerful than regular languages. Thus if a language is explicitly context-free (context-free and not regular), then it is impossible for <strong>any</strong> regular expression to recognize it.</p>\n<p>JavaScript is at the very least context-free, thus we know with one-hundred percent certainty that designing a regular expression (regex) capable of catching all XSS is a mathematically impossible task.</p>\n'}, {'answer_id': 63775847, 'author': 'NoNaMe', 'author_id': 1410342, 'author_profile': 'https://Stackoverflow.com/users/1410342', 'pm_score': 0, 'selected': False, 'text': '<p>For java, I used the following regular expression with replaceAll, and worked for me</p>\n<pre><code>value.replaceAll(&quot;(?i)(\\\\b)(on\\\\S+)(\\\\s*)=|javascript:|(&lt;\\\\s*)(\\\\/*)script|style(\\\\s*)=|(&lt;\\\\s*)meta&quot;, &quot;&quot;);\n</code></pre>\n<p>Added (?i) to ignore case for alphabets.</p>\n'}, {'answer_id': 74359104, 'author': 'Ashhh', 'author_id': 15397385, 'author_profile': 'https://Stackoverflow.com/users/15397385', 'pm_score': 0, 'selected': False, 'text': "<pre><code>public String validate(String value) {\n\n\n    // Avoid anything between script tags\n    Pattern scriptPattern = Pattern.compile(&quot;&lt;script&gt;(.*?)&lt;/script&gt;&quot;, Pattern.CASE_INSENSITIVE);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid anything in a src='...' type of expression\n    scriptPattern = Pattern.compile(&quot;src[\\r\\n]*=[\\r\\n]*\\\\\\'(.*?)\\\\\\'&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid anything in a src=&quot;...&quot; type of expression\n    scriptPattern = Pattern.compile(&quot;src[\\r\\n]*=[\\r\\n]*\\\\\\&quot;(.*?)\\\\\\&quot;&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid anything in a src=... type of expression added because quotes are not necessary\n    scriptPattern = Pattern.compile(&quot;src[\\r\\n]*=[\\r\\n]*(.*?)&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Remove any lonesome &lt;/script&gt; tag\n    scriptPattern = Pattern.compile(&quot;&lt;/script&gt;&quot;, Pattern.CASE_INSENSITIVE);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Remove any lonesome &lt;script ...&gt; tag\n    scriptPattern = Pattern.compile(&quot;&lt;script(.*?)&gt;&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid eval(...) expressions\n    scriptPattern = Pattern.compile(&quot;eval\\\\((.*?)\\\\)&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid expression(...) expressions\n    scriptPattern = Pattern.compile(&quot;expression\\\\((.*?)\\\\)&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid javascript:... expressions\n    scriptPattern = Pattern.compile(&quot;javascript:&quot;, Pattern.CASE_INSENSITIVE);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid vbscript:... expressions\n    scriptPattern = Pattern.compile(&quot;vbscript:&quot;, Pattern.CASE_INSENSITIVE);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid onload= expressions\n    scriptPattern = Pattern.compile(&quot;onload(.*?)=&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid anything between script tags  added - paranoid regex.  note:  if testing local PREP this must be commented\n    scriptPattern = Pattern.compile(&quot;&lt;(.*?)[\\r\\n]*(.*?)&gt;&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    //  Avoid anything between script tags  added - paranoid regex\n    scriptPattern = Pattern.compile(&quot;&lt;script(.*?)[\\r\\n]*(.*?)/script&gt;&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Avoid anything between * tags like *(alert)*  added\n    scriptPattern = Pattern.compile(&quot;\\\\*(.*?)[\\r\\n]*(.*?)\\\\*&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n    \n    // Avoid anything between + tags like +(alert)+  added\n    scriptPattern = Pattern.compile(&quot;\\\\+(.*?)[\\r\\n]*(.*?)\\\\+&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n    // Prohibit lines containing = (...) added\n    scriptPattern = Pattern.compile(&quot;=(.*?)\\\\((.*?)\\\\)&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n    \n    //  removing href link \n    scriptPattern = Pattern.compile(&quot;(?i)&lt;[\\\\s]*[/]?[\\\\s]*a[^&gt;]*&gt;&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n    //Avoid alert\n    scriptPattern = Pattern.compile(&quot;alert&quot;, Pattern.CASE_INSENSITIVE);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n\n\n    scriptPattern = Pattern.compile(&quot;[^\\\\dA-Za-z ]&quot;, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);\n    value = scriptPattern.matcher(value).replaceAll(&quot;&quot;);\n    \n    \n    return value;\n            \n}\n</code></pre>\n"}]
['https://Stackoverflow.com/questions/24723', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/1406/']