Skip to content

Commit 1c861ff

Browse files
committed
Merge branch 'release-0.2.1'
2 parents 4d283eb + 5938198 commit 1c861ff

File tree

13 files changed

+303
-16
lines changed

13 files changed

+303
-16
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
<a name="0.2.1"></a>
2+
## [0.2.1](https://github.com/JimLiu/bbcode-to-react/compare/0.1.3...v0.2.1) (2016-10-29)
3+
4+
* More tests
5+
* Fixed security issue
6+
* Customize tag
7+
8+
19
<a name="0.1.3"></a>
210
## [0.1.3](https://github.com/JimLiu/bbcode-to-react/compare/0.1.1...v0.1.3) (2016-10-28)
311

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,49 @@ const Example = (props) => {
2525

2626
```
2727

28+
## Add new tag example
29+
30+
```js
31+
import React from 'react';
32+
import parser, { Tag } from 'bbcode-to-react';
33+
34+
class YoutubeTag extends Tag {
35+
toReact() {
36+
// using this.getContent(true) to get it's inner raw text.
37+
const attributes = {
38+
src: this.getContent(true),
39+
width: this.params.width || 420,
40+
height: this.params.height || 315,
41+
};
42+
return (
43+
<iframe
44+
{...attributes}
45+
frameBorder="0"
46+
allowFullScreen
47+
/>
48+
);
49+
}
50+
}
51+
52+
class BoldTag extends Tag {
53+
toReact() {
54+
// using this.getComponents() to get children components.
55+
return (
56+
<b>{this.getComponents()}</b>
57+
);
58+
}
59+
}
60+
61+
parser.registerTag('youtube', YoutubeTag); // add new tag
62+
parser.registerTag('b', BoldTag); // replace exists tag
63+
64+
const Example = (props) => {
65+
return (
66+
<p>{parser.toReact('[youtube width="400"]https://www.youtube.com/watch?v=AB6RjNeDII0[/youtube]')}</p>
67+
);
68+
}
69+
70+
```
2871

2972
## Development
3073

@@ -51,3 +94,7 @@ Watch tests:
5194
```sh
5295
npm run test-watch
5396
```
97+
98+
# Credits
99+
100+
`bbcode-to-react` uses the parser from [bbcodejs](https://github.com/vishnevskiy/bbcodejs), so much of the credit is due there.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bbcode-to-react",
3-
"version": "0.1.3",
3+
"version": "0.2.1",
44
"description": "A utility for turning raw BBCode into React elements. ",
55
"main": "lib/index.js",
66
"scripts": {

src/__tests__/customize.test.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
4+
import parser, { Tag } from '../';
5+
6+
class YoutubeTag extends Tag {
7+
toReact() {
8+
// using this.getContent(true) to get it's inner raw text.
9+
const attributes = {
10+
src: this.getContent(true),
11+
width: this.params.width || 420,
12+
height: this.params.height || 315,
13+
};
14+
return (
15+
<iframe
16+
{...attributes}
17+
frameBorder="0"
18+
allowFullScreen
19+
/>
20+
);
21+
}
22+
}
23+
24+
class BoldTag extends Tag {
25+
toReact() {
26+
// using this.getComponents() to get children components.
27+
return (
28+
<b>{this.getComponents()}</b>
29+
);
30+
}
31+
}
32+
33+
parser.registerTag('youtube', YoutubeTag); // add new tag
34+
parser.registerTag('b', BoldTag); // replace exists tag
35+
36+
describe('customize tag', () => {
37+
it('should parse customize youtube tag to react', () => {
38+
const bbcode = '[youtube width="400"]https://www.youtube.com/watch?v=AB6RjNeDII0[/youtube]';
39+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
40+
41+
expect(wrapper.text()).toBe('');
42+
expect(wrapper.type()).toBe('iframe');
43+
expect(wrapper.prop('src')).toBe('https://www.youtube.com/watch?v=AB6RjNeDII0');
44+
expect(wrapper.prop('width').toString()).toBe('400');
45+
expect(wrapper.prop('height').toString()).toBe('315');
46+
expect(wrapper.prop('frameBorder').toString()).toBe('0');
47+
expect(wrapper.prop('allowFullScreen')).toBe(true);
48+
});
49+
50+
it('should replace the exist tag', () => {
51+
const bbcode = '[b]strong[/b]';
52+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
53+
54+
expect(wrapper.text()).toBe('strong');
55+
expect(wrapper.type()).toBe('b');
56+
expect(wrapper.html()).toBe('<b>strong</b>');
57+
});
58+
});

src/__tests__/list.test.js

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,75 @@ describe('[list]', () => {
1313
});
1414

1515
it('should parse [list=a] to react', () => {
16-
const bbcode = '[list=a]list[/list]';
16+
const bbcode = `[list=a]
17+
[*]The first possible answer
18+
[*]The second possible answer
19+
[*]The third possible answer
20+
[/list]`;
1721
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
1822

19-
expect(wrapper.text()).toBe('list');
2023
expect(wrapper.type()).toBe('ol');
24+
expect(wrapper.find('li').length).toBe(3);
25+
expect(wrapper.find('li').first().text().trim()).toBe('The first possible answer');
2126
expect(wrapper.prop('style').listStyleType).toBe('lower-alpha');
2227
});
2328

2429

2530
it('should parse [list=A] to react', () => {
26-
const bbcode = '[list=A]list[/list]';
31+
const bbcode = `[list=A]
32+
[*]The first possible answer
33+
[*]The second possible answer
34+
[*]The third possible answer
35+
[/list]`;
2736
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
2837

29-
expect(wrapper.text()).toBe('list');
3038
expect(wrapper.type()).toBe('ol');
39+
expect(wrapper.find('li').length).toBe(3);
40+
expect(wrapper.find('li').first().text().trim()).toBe('The first possible answer');
3141
expect(wrapper.prop('style').listStyleType).toBe('upper-alpha');
3242
});
3343

44+
it('should parse [list=i] to react', () => {
45+
const bbcode = `[list=i]
46+
[*]The first possible answer
47+
[*]The second possible answer
48+
[*]The third possible answer
49+
[/list]`;
50+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
51+
52+
expect(wrapper.type()).toBe('ol');
53+
expect(wrapper.find('li').length).toBe(3);
54+
expect(wrapper.find('li').first().text().trim()).toBe('The first possible answer');
55+
expect(wrapper.prop('style').listStyleType).toBe('lower-roman');
56+
});
57+
58+
59+
it('should parse [list=I] to react', () => {
60+
const bbcode = `[list=I]
61+
[*]The first possible answer
62+
[*]The second possible answer
63+
[*]The third possible answer
64+
[/list]`;
65+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
66+
67+
expect(wrapper.type()).toBe('ol');
68+
expect(wrapper.find('li').length).toBe(3);
69+
expect(wrapper.find('li').first().text().trim()).toBe('The first possible answer');
70+
expect(wrapper.prop('style').listStyleType).toBe('upper-roman');
71+
});
72+
3473
it('should parse [list=1] to react', () => {
35-
const bbcode = '[list=1]list[/list]';
74+
const bbcode = `[list=1]
75+
[*]Go to the shops
76+
[*]Buy a new computer
77+
[*]Swear at computer when it crashes
78+
[/list]`;
3679
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
3780

38-
expect(wrapper.text()).toBe('list');
3981
expect(wrapper.type()).toBe('ol');
82+
expect(wrapper.find('li').length).toBe(3);
83+
expect(wrapper.find('li').first().type()).toBe('li');
84+
expect(wrapper.prop('style').listStyleType).toBe('decimal');
4085
});
4186

4287
it('should parse [*]item[/*] to react', () => {

src/__tests__/parser.test.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1+
// http://www.react.org.au/forum/faq.php?mode=bbcode
2+
13
import React from 'react';
24
import { shallow } from 'enzyme';
35

46
import parser from '../';
57

68
describe('parser', () => {
79
it('should parse bbcode to react', () => {
8-
const wrapper = shallow(<div>{parser.toReact('[b]strong[/b]')}</div>);
10+
const bbcode = '[b]strong[/b]';
11+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
912

1013
expect(wrapper.text()).toBe('strong');
11-
expect(wrapper.html()).toBe('<div><strong>strong</strong></div>');
14+
expect(wrapper.html()).toBe('<strong>strong</strong>');
15+
});
16+
17+
it('should encode html', () => {
18+
const bbcode = '[b]<strong>strong</strong>[/b]';
19+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
20+
21+
expect(wrapper.text()).toBe('<strong>strong</strong>');
22+
expect(wrapper.html()).toBe('<strong>&lt;strong&gt;strong&lt;/strong&gt;</strong>');
1223
});
1324
});

src/__tests__/security.test.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// http://1nfosec4all.blogspot.com/2012/07/bulletin-board-code-bbcode-xss-exploit.html
2+
import React from 'react';
3+
import { shallow } from 'enzyme';
4+
5+
import parser from '../';
6+
7+
describe('security test', () => {
8+
it('should not allow [URL] Tag injection', () => {
9+
const bbcode = '[url]javascript:alert(0)[/url]';
10+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
11+
12+
expect(wrapper.text()).toBe('javascript:alert(0)');
13+
expect(wrapper.type()).toBeUndefined();
14+
});
15+
16+
it('should not allow [COLOR] Tag Injection', () => {
17+
const bbcode = '[color=#ff0000;font-size:100px;]Got You[/color]';
18+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
19+
20+
expect(wrapper.text()).toBe('Got You');
21+
expect(wrapper.prop('style').color).not.toBe('#ff0000;');
22+
expect(wrapper.prop('style').fontSize).toBeUndefined();
23+
});
24+
25+
it('should not allow [COLOR] Tag Injection', () => {
26+
const bbcode = '[color=#ff0000;You:expression(alert(String.fromCharCode(88,83,83)));]Got You[/color]';
27+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
28+
29+
expect(wrapper.text()).toBe('Got You');
30+
expect(wrapper.prop('style').color).not.toBe('#ff0000;');
31+
expect(wrapper.prop('style').color).toBe('#ff0000;You:expression(alert(String.fromCharCode(88,83,83)));');
32+
expect(wrapper.prop('style').expression).toBeUndefined();
33+
});
34+
35+
it('should not allow [FONT] Tag Injection', () => {
36+
const bbcode = '[font=Impact, Compacta, Chicago, sans-serif;color:red;]Got You[/font]';
37+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>);
38+
39+
expect(wrapper.html()).toBe('<div>[font=Impact, Compacta, Chicago, sans-serif;color:red;]Got You[/font]</div>');
40+
});
41+
42+
it('should not allow [FONT] Tag Injection', () => {
43+
const bbcode = '[font=Impact, Compacta, Chicago, sans-serif;You:expression(alert(String.fromCharCode(88,83,83)));]Got You[/font]';
44+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>);
45+
46+
expect(wrapper.html()).toBe('<div>[font=Impact, Compacta, Chicago, sans-serif;You:expression(alert(String.fromCharCode(88,83,83)));]Got You[/font]</div>');
47+
});
48+
49+
it('should not allow [IMG] Tag Injection', () => {
50+
const bbcode = '[img]NotExist.png" onerror="alert(String.fromCharCode(88,83,83))[/img]';
51+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
52+
53+
expect(wrapper.type()).toBe('img');
54+
expect(wrapper.prop('onerror')).toBeUndefined();
55+
});
56+
57+
it('should not allow [TABLE] Tag Injection', () => {
58+
const bbcode = '[table cellSpacing="0" cellPadding="0" width="100%"][tbody][tr][td width="*" onmouseover="alert(String.fromCharCode(88,83,83))"]Got You[/td][/tr][/tbody][/table]';
59+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>);
60+
61+
expect(wrapper.find('td').first().prop('width')).toBe('*');
62+
expect(wrapper.find('td').first().prop('onmouseover')).toBeUndefined();
63+
expect(wrapper.find('td').first().text()).toBe('Got You');
64+
});
65+
66+
it('should not allow Nested Tags Injection', () => {
67+
const bbcode = '[url]http://www.good.com?[url] onmousemove=javascript:alert(String.fromCharCode(88,83,83));//[/url][/url]';
68+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>);
69+
70+
expect(wrapper.find('a').length).toBe(0);
71+
});
72+
73+
it('should not allow Nested Tags Injection', () => {
74+
const bbcode = '[img]http://foo.com/NotExist.png [img] onerror=javascript:alert(String.fromCharCode(88,83,83)) [/img] [/img]';
75+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>);
76+
77+
expect(wrapper.find('img').length).toBe(1);
78+
expect(wrapper.find('img').first().type()).toBe('img');
79+
expect(wrapper.find('img').first().prop('onerror')).toBeUndefined();
80+
});
81+
});

src/__tests__/url.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,22 @@ describe('[url]', () => {
2020
expect(wrapper.text()).toBe('bbcode-to-react');
2121
expect(wrapper.prop('href')).toBe('https://github.com/JimLiu/bbcode-to-react');
2222
});
23+
24+
it('should parse [email]no.one@domain.adr[/email] to react', () => {
25+
const bbcode = '[email]no.one@domain.adr[/email]';
26+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
27+
28+
expect(wrapper.text()).toBe('no.one@domain.adr');
29+
expect(wrapper.prop('href')).toBe('mailto:no.one@domain.adr');
30+
});
31+
32+
it('should parse image link to react', () => {
33+
const bbcode = '[url=https://github.com/JimLiu/bbcode-to-react]bbcode-to-react][img]logo.png[/img][/url]';
34+
const wrapper = shallow(<div>{parser.toReact(bbcode)}</div>).children().first();
35+
36+
expect(wrapper.type()).toBe('a');
37+
expect(wrapper.prop('href')).toBe('https://github.com/JimLiu/bbcode-to-react');
38+
expect(wrapper.find('img').first().type()).toBe('img');
39+
expect(wrapper.find('img').first().prop('src')).toBe('logo.png');
40+
});
2341
});

src/parser.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ export default class Parser {
2929
parseParams(token) {
3030
const params = [];
3131

32+
function addParam(name, value) {
33+
if (name) {
34+
const n = name.trim();
35+
// ignore on* events attribute
36+
if (n.length && n.toLowerCase().indexOf('on') !== 0) {
37+
params.push([n, value]);
38+
}
39+
}
40+
}
41+
3242
if (token) {
3343
let key = [];
3444
let target = key;
@@ -48,7 +58,7 @@ export default class Parser {
4858
} else if (c !== terminate) {
4959
target.push(c);
5060
} else {
51-
params.push([key.join('').toLowerCase(), value.join('')]);
61+
addParam(key.join(''), value.join(''));
5262

5363
if (!SPACE_RE.test(terminate)) {
5464
skipNext = true;
@@ -60,7 +70,7 @@ export default class Parser {
6070
}
6171
});
6272

63-
params.push([key.join('').toLowerCase(), value.join('')]);
73+
addParam(key.join(''), value.join(''));
6474
}
6575

6676
return params;

src/tags/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ export default {
4242
quote: QuoteTag,
4343
url: LinkTag,
4444
link: LinkTag,
45+
email: LinkTag,
4546
};

0 commit comments

Comments
 (0)