diff --git a/bristlecode.rb b/bristlecode.rb
index 48ab082..0ba755f 100644
--- a/bristlecode.rb
+++ b/bristlecode.rb
@@ -1,8 +1,24 @@
require 'parslet'
require 'sanitize'
+require 'uri'
module Bristlecode
+ class YoutubeFilter
+ def call(env)
+ node = env[:node]
+ node_name = env[:node_name]
+ return if env[:is_whitelisted] || !node.element?
+ return unless node_name == 'iframe'
+ return unless node['src'] =~ %r|\A(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/|
+ Sanitize.node!(node, {
+ :elements => %w[iframe],
+ :attributes => {'iframe' => %w[allowfullscreen frameborder height src width]}
+ })
+ {:node_whitelist => [node]}
+ end
+ end
+
Config = Sanitize::Config::freeze_config(
:elements => %w[b em i strong u a strike br img],
:attributes => {
@@ -14,7 +30,8 @@ module Bristlecode
},
:protocols => {
'a' => {'href' => ['http', 'https', :relative]}
- }
+ },
+ :transformers => [YoutubeFilter.new]
)
def Bristlecode.to_html(text)
@@ -29,6 +46,7 @@ module Bristlecode
Sanitize.fragment(html, Bristlecode::Config)
end
+
def Bristlecode.clean!(text)
text.gsub!('&', '&')
text.gsub!('<', '<')
@@ -64,16 +82,21 @@ module Bristlecode
}
rule(:url) { (simple_url | url_with_title).as(:url) }
+ rule(:youtube_open) { str('[youtube]') }
+ rule(:youtube_close) { str('[/youtube]') }
+ rule(:youtube_url) { (youtube_close.absent? >> any).repeat(1) }
+ rule(:youtube) { (youtube_open >> youtube_url.as(:src) >> youtube_close).as(:youtube) }
+
rule(:img_open) { str('[img]') }
rule(:img_close) { str('[/img]') }
rule(:img_src) { (img_close.absent? >> any).repeat(1) }
rule(:img) { (img_open >> img_src.as(:src) >> img_close).as(:img) }
rule(:eof) { any.absent? }
- rule(:tag) { bold | italic | url | linebreak | img }
+ rule(:tag) { bold | italic | url | linebreak | img | youtube }
rule(:elem) { text.as(:text) | tag }
- rule(:tag_open) { bold_open | italic_open | url_open | url_title_open | img_open }
- rule(:tag_close) { bold_close | italic_close | url_close | img_close }
+ rule(:tag_open) { bold_open | italic_open | url_open | url_title_open | img_open | youtube_open }
+ rule(:tag_close) { bold_close | italic_close | url_close | img_close | youtube_close }
rule(:tag_delim) { tag_open | tag_close | linebreak }
rule(:text) { (tag_delim.absent? >> any).repeat(1) }
@@ -90,6 +113,7 @@ module Bristlecode
rule(url: subtree(:url)) { Url.new(url) }
rule(br: simple(:br)) { Linebreak.new }
rule(img: subtree(:img)) { Img.new(img) }
+ rule(youtube: subtree(:youtube)) { Youtube.new(youtube) }
end
class Doc
@@ -180,11 +204,8 @@ module Bristlecode
end
def to_html
- if href_ok?
- "#{title.to_html}"
- else
- to_text
- end
+ return to_text unless href_ok?
+ "#{title.to_html}"
end
def to_text
@@ -220,11 +241,8 @@ module Bristlecode
end
def to_html
- if src_ok?
- ""
- else
- to_text
- end
+ return to_text unless src_ok?
+ ""
end
def to_text
@@ -233,4 +251,40 @@ module Bristlecode
text
end
end
+
+ class Youtube
+ attr_accessor :raw_url, :video_id
+
+ def initialize(args)
+ self.raw_url = args[:src].to_str.strip
+ self.video_id = parse_url
+ end
+
+ def parse_url
+ begin
+ uri = URI::parse(raw_url)
+ return false unless ['http', 'https'].include? uri.scheme
+ return false unless ['www.youtube.com', 'youtube.com', 'youtu.be'].include? uri.host
+ if uri.host == 'youtu.be'
+ return uri.path[1..-1]
+ else
+ URI::decode_www_form(uri.query).each{|key, value| return value if key == 'v'}
+ end
+ rescue URI::InvalidURIError
+ end
+
+ return false
+ end
+
+ def to_html
+ return to_text unless video_id
+ ""
+ end
+
+ def to_text
+ text = "[youtube]#{raw_url}[/youtube]"
+ Bristlecode.clean!(text)
+ text
+ end
+ end
end
diff --git a/spec/bristlecode/parser_spec.rb b/spec/bristlecode/parser_spec.rb
index 17c7aa6..1033850 100644
--- a/spec/bristlecode/parser_spec.rb
+++ b/spec/bristlecode/parser_spec.rb
@@ -123,6 +123,32 @@ module Bristlecode
input = '[url][url]x[/url][/url]'
expect(to_html(input)).to eq(input)
end
+
+ it 'can render a youtube video with a watch link' do
+ input = '[youtube]https://youtube.com/watch?v=uxpDa-c-4Mc[/youtube]'
+ output = ''
+ expect(to_html(input)).to eq(output)
+
+ input = '[youtube]https://www.youtube.com/watch?v=uxpDa-c-4Mc[/youtube]'
+ output = ''
+ expect(to_html(input)).to eq(output)
+ end
+
+ it 'can render a youtube video with a share link' do
+ input = '[youtube]https://youtu.be/uxpDa-c-4Mc[/youtube]'
+ output = ''
+ expect(to_html(input)).to eq(output)
+ end
+
+ it 'refuses bad youtube urls' do
+ input = '[youtube]http://example.com/cats.gif[/youtube]'
+ expect(to_html(input)).to eq(input)
+ end
+
+ it "requires full url for youtube vids" do
+ input = '[youtube]dQw4w9WgXcQ[/youtube]'
+ expect(to_html(input)).to eq(input)
+ end
end
describe Parser do