added sane sanitizer (Google Cajole) that is much more robust than old one ... yay for smilies

added sane way to do $LAB includes - pattern to be expanded
people keep on messing structure.sql
This commit is contained in:
Sam Saffron 2013-02-20 15:37:42 +11:00
parent a1099ed74e
commit 0c085059c9
9 changed files with 2592 additions and 2080 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,92 @@
/* jQuery sanitizer for html, used by our previewer */
// Sam: I wrote this but it is totally unsafe so I ported Google Cajole
// Thing is Cajole is old and complex (albeit super duper fast)
//
// I would like this ported to: https://github.com/tautologistics/node-htmlparser , perf tested
// and move off cajole
//
// See also: http://stackoverflow.com/questions/14971083/is-jquerys-safe-from-xss
//
(function( $ ) {
$.fn.sanitize = function() {
$.find('*').each(function(){
});
};
})( jQuery );
// (function( $ ) {
//
// var elements = ["a", "abbr", "aside", "b", "bdo", "blockquote", "br",
// "caption", "cite", "code", "col", "colgroup", "dd", "div",
// "del", "dfn", "dl", "dt", "em", "hr", "figcaption", "figure",
// "h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "i", "img", "ins",
// "kbd", "li", "mark", "ol", "p", "pre", "q", "rp", "rt", "ruby",
// "s", "samp", "small", "span", "strike", "strong", "sub", "sup",
// "table", "tbody", "td", "tfoot", "th", "thead", "time", "tr", "u",
// "ul", "var", "wbr"];
//
// var attributes = {
// 'all' : ['dir', 'lang', 'title', 'class'],
// 'aside' : ['data-post', 'data-full', 'data-topic'],
// 'a' : ['href'],
// 'blockquote' : ['cite'],
// 'col' : ['span', 'width'],
// 'colgroup' : ['span', 'width'],
// 'del' : ['cite', 'datetime'],
// 'img' : ['align', 'alt', 'height', 'src', 'width'],
// 'ins' : ['cite', 'datetime'],
// 'ol' : ['start', 'reversed', 'type'],
// 'q' : ['cite'],
// 'span' : ['style'],
// 'table' : ['summary', 'width', 'style', 'cellpadding', 'cellspacing'],
// 'td' : ['abbr', 'axis', 'colspan', 'rowspan', 'width', 'style'],
// 'th' : ['abbr', 'axis', 'colspan', 'rowspan', 'scope', 'width', 'style'],
// 'time' : ['datetime', 'pubdate'],
// 'ul' : ['type']
//
// };
//
// var elementMap = {};
// $.each(elements, function(idx,e){
// elementMap[e] = true;
// });
//
// var scrubAttributes = function(e){
// $.each(e.attributes, function(idx, attr){
//
// if($.inArray(attr.name, attributes.all) === -1 &&
// $.inArray(attr.name, attributes[e.tagName.toLowerCase()]) === -1) {
// e.removeAttribute(attr.name);
// }
// });
// return(e);
// };
//
// var scrubNode = function(e){
// if (!e.tagName) { return(e); }
// if(elementMap[e.tagName.toLowerCase()]){
// return scrubAttributes(e);
// }
// else
// {
// return null;
// }
// };
//
// var scrubTree = function(e) {
// if (!e) { return; }
//
// var clean = scrubNode(e);
// if(!clean){
// e.parentNode.removeChild(e);
// }
// else {
// $.each(clean.children, function(idx, inner){
// scrubTree(inner);
// });
// }
// };
//
// $.fn.sanitize = function() {
// clean = this.filter(function(){
// return scrubNode(this);
// }).each(function(){
// scrubTree(this);
// });
//
// return clean;
// };
// })( jQuery );

View file

@ -97,10 +97,7 @@ Discourse.Utilities =
range.select()
markdownConverter: (opts)->
if opts.sanitize
converter = new Markdown.getSanitizingConverter()
else
converter = new Markdown.Converter()
converter = new Markdown.Converter()
mentionLookup = opts.mentionLookup if opts
mentionLookup = mentionLookup || Discourse.Mention.lookupCache
@ -157,6 +154,12 @@ Discourse.Utilities =
converter.hooks.chain "postConversion", (text) =>
Discourse.BBCode.format(text, opts)
if opts.sanitize
converter.hooks.chain "postConversion", (text) =>
return "" unless window.sanitizeHtml
sanitizeHtml(text)
converter

View file

@ -60,11 +60,11 @@ window.Discourse.ComposerView = window.Discourse.View.extend
fetchNewUserEducation: (->
# If creating a topic, use topic_count, otherwise post_count
count = if @get('content.creatingTopic') then Discourse.get('currentUser.topic_count') else Discourse.get('currentUser.reply_count')
count = if @get('content.creatingTopic') then Discourse.get('currentUser.topic_count') else Discourse.get('currentUser.reply_count')
if (count >= Discourse.SiteSettings.educate_until_posts)
@set('educationClosed', true)
@set('educationContents', '')
return
return
return unless @get('controller.hasReply')
@ -106,7 +106,7 @@ window.Discourse.ComposerView = window.Discourse.View.extend
$('#new-user-education').css('bottom', sizePx)
).observes('content.composeState')
keyUp: (e) ->
keyUp: (e) ->
controller = @get('controller')
controller.checkReplyLength()
controller.hitEsc() if e.which == 27
@ -139,6 +139,8 @@ window.Discourse.ComposerView = window.Discourse.View.extend
@wmdInput = $wmdInput = $('#wmd-input')
return if $wmdInput.length == 0 || $wmdInput.data('init') == true
$LAB.script(assetPath('defer/html-sanitizer-bundle'))
Discourse.ComposerView.trigger("initWmdEditor")
template = Handlebars.compile("<div class='autocomplete'>

View file

@ -1,25 +1,41 @@
<%- if mini_profiler_enabled? %>
<%- Rack::MiniProfiler.step "application" do %>
<%= javascript_include_tag "application" %>
<%-end%>
<%- Rack::MiniProfiler.step "admin" do %>
<%= javascript_include_tag "admin"%>
<%-end%>
<%- else %>
<%= javascript_include_tag "application" %>
<%- if admin? %>
<%= javascript_include_tag "admin"%>
<%- end %>
<%- end%>
<script>
Discourse.CDN = '<%= Rails.configuration.action_controller.asset_host %>';
Discourse.BaseUrl = '<%= RailsMultisite::ConnectionManagement.current_hostname %>';
Discourse.Environment = '<%= Rails.env %>';
window.Discourse.Router.map(function() {
return Discourse.routeBuilder.call(this);
});
Discourse.start()
Discourse.initialize()
</script>
<script>
window.assetPath = (function(){
// TODO: automate this to grab from the manifest, Rails voodoo should be able to get it
var map = {
'defer/html-sanitizer-bundle': <%= asset_path('defer/html-sanitizer-bundle').inspect.html_safe %>
};
var assetPath = function(asset){
return map[asset];
};
return assetPath;
})();
</script>
<%- if mini_profiler_enabled? %>
<%- Rack::MiniProfiler.step "application" do %>
<%= javascript_include_tag "application" %>
<%-end%>
<%- Rack::MiniProfiler.step "admin" do %>
<%= javascript_include_tag "admin"%>
<%-end%>
<%- else %>
<%= javascript_include_tag "application" %>
<%- if admin? %>
<%= javascript_include_tag "admin"%>
<%- end %>
<%- end%>
<script>
Discourse.CDN = '<%= Rails.configuration.action_controller.asset_host %>';
Discourse.BaseUrl = '<%= RailsMultisite::ConnectionManagement.current_hostname %>';
Discourse.Environment = '<%= Rails.env %>';
window.Discourse.Router.map(function() {
return Discourse.routeBuilder.call(this);
});
Discourse.start()
Discourse.initialize()
</script>

View file

@ -29,7 +29,9 @@ module Discourse
config.assets.paths += %W(#{config.root}/config/locales)
config.assets.precompile += ['admin.js', 'admin.css', 'shiny/shiny.css', 'preload_store.js', 'jquery.js']
config.assets.precompile += [
'admin.js', 'admin.css', 'shiny/shiny.css', 'preload_store.js', 'jquery.js', 'defer/html-sanitizer-bundle.js'
]
# Activate observers that should always be running.
config.active_record.observers = [

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
describe("sanitize", function(){
it("strips all script tags", function(){
sanitized = sanitizeHtml("<div><script>alert('hi');</script></div>");
expect(sanitized)
.toBe("<div></div>");
});
it("strips disallowed attributes", function(){
sanitized = sanitizeHtml("<div><p class=\"funky\" wrong='1'>hello</p></div>");
expect(sanitized)
.toBe("<div><p class=\"funky\">hello</p></div>");
});
});

View file

@ -40,6 +40,7 @@
//= require_tree ../../app/assets/javascripts/discourse/templates
//= require_tree ../../app/assets/javascripts/discourse/routes
//= require_tree ../../app/assets/javascripts/defer
//= require_tree .