Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions app/helpers/better_together/content_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require 'nokogiri'
require 'uri'

module BetterTogether
# Helper methods for content rendering
module ContentHelper
ALLOWED_TAGS = %w[
a abbr b blockquote br cite code dd dl dt em i li ol p pre q s small strong sub sup u ul
h1 h2 h3 h4 h5 h6 img span div iframe
].freeze

ALLOWED_ATTRIBUTES = %w[
href title target rel src alt class id width height frameborder allow allowfullscreen
].freeze

YOUTUBE_DOMAINS = %w[
youtube.com www.youtube.com m.youtube.com youtu.be
youtube-nocookie.com www.youtube-nocookie.com
].freeze

def safe_html(html)
sanitized = sanitize(html.to_s, tags: ALLOWED_TAGS, attributes: ALLOWED_ATTRIBUTES)
fragment = Nokogiri::HTML::DocumentFragment.parse(sanitized)
fragment.css('iframe').each do |iframe|
src = iframe['src']
next unless src

uri = URI.parse(src) rescue nil
iframe.remove unless uri && YOUTUBE_DOMAINS.include?(uri.host)
end
fragment.to_html.html_safe
end
end
end
2 changes: 1 addition & 1 deletion app/views/better_together/content/blocks/_html.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

<%= render layout: 'better_together/content/blocks/block', locals: { block: html } do %>
<%= cache html.cache_key_with_version do %>
<%= sanitize_block_html(html.content) %>
<%= safe_html(html.content) %>
<% end %>
<% end %>
43 changes: 43 additions & 0 deletions spec/helpers/better_together/content_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

require 'rails_helper'

module BetterTogether
RSpec.describe ContentHelper, type: :helper do
describe '#safe_html' do
it 'escapes dangerous tags' do
input = "<p>Hello</p><script>alert('xss')</script>"
output = helper.safe_html(input)
expect(output).to include('<p>Hello</p>')
expect(output).not_to include('<script')
end

it 'allows permitted markup' do
input = '<p><strong>Bold</strong> and <a href="https://example.com">link</a></p>'
output = helper.safe_html(input)
expect(output).to include('<strong>Bold</strong>')
expect(output).to include('<a href="https://example.com">link</a>')
end

it 'allows youtube iframes' do
input = '<iframe width="560" height="315" src="https://www.youtube.com/embed/xyz" frameborder="0" allowfullscreen></iframe>'
output = helper.safe_html(input)
expect(output).to include('<iframe')
expect(output).to include('src="https://www.youtube.com/embed/xyz"')
end

it 'allows youtube-nocookie iframes' do
input = '<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/xyz" frameborder="0" allowfullscreen></iframe>'
output = helper.safe_html(input)
expect(output).to include('<iframe')
expect(output).to include('src="https://www.youtube-nocookie.com/embed/xyz"')
end

it 'strips non-youtube iframes' do
input = '<iframe src="https://evil.com/video"></iframe>'
output = helper.safe_html(input)
expect(output).not_to include('<iframe')
end
end
end
end
Loading