From 13482a12a47f99df706edd1547720f7f5fde09a2 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Fri, 6 Nov 2015 09:22:57 -0500 Subject: [PATCH] support youtube videos --- bristlecode.rb | 82 +++++++++++++++++++++++++++------ spec/bristlecode/parser_spec.rb | 26 +++++++++++ 2 files changed, 94 insertions(+), 14 deletions(-) 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