Skip to content

Commit 3b80ec0

Browse files
authored
Merge pull request velopert#68 from velopert/fix/sanitize-html
Sanitize html and allow embedding
2 parents 362c764 + 1006d7a commit 3b80ec0

File tree

7 files changed

+163
-17
lines changed

7 files changed

+163
-17
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@types/react-router-dom": "^5.1.3",
4545
"@types/react-textarea-autosize": "^4.3.5",
4646
"@types/react-toastify": "^4.1.0",
47+
"@types/sanitize-html": "^1.20.2",
4748
"@types/styled-components": "^4.4.1",
4849
"@types/throttle-debounce": "^2.1.0",
4950
"@typescript-eslint/eslint-plugin": "^2.8.0",
@@ -128,6 +129,7 @@
128129
"remark-slug": "^5.1.2",
129130
"resolve": "1.12.2",
130131
"resolve-url-loader": "3.1.1",
132+
"sanitize-html": "^1.21.1",
131133
"sass-loader": "8.0.0",
132134
"semver": "6.3.0",
133135
"serverless-webpack": "^5.3.1",

src/components/common/MarkdownRender.tsx

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import { loadScript, ssrEnabled } from '../../lib/utils';
1212
import media from '../../lib/styles/media';
1313
import parse from 'html-react-parser';
1414
import { throttle } from 'throttle-debounce';
15+
import sanitize from 'sanitize-html';
1516

1617
export interface MarkdownRenderProps {
1718
markdown: string;
1819
codeTheme?: string;
1920
onConvertFinish?: (html: string) => any;
21+
editing?: boolean;
2022
}
2123

2224
const MarkdownRenderBlock = styled.div`
@@ -59,26 +61,84 @@ const MarkdownRenderBlock = styled.div`
5961
margin-bottom: 1.5rem;
6062
}
6163
62-
.youtube {
64+
iframe {
6365
width: 768px;
6466
height: 430px;
6567
max-width: 100%;
6668
background: black;
6769
display: block;
6870
margin: auto;
71+
border: none;
72+
border-radius: 4px;
73+
overflow: hidden;
6974
}
7075
7176
.twitter-wrapper {
7277
display: flex;
7378
justify-content: center;
74-
height: 580px;
7579
align-items: center;
7680
blockquote {
7781
border-left: none;
7882
}
7983
}
8084
`;
8185

86+
function filter(html: string) {
87+
return sanitize(html, {
88+
allowedTags: [
89+
'h1',
90+
'h2',
91+
'h3',
92+
'h4',
93+
'h5',
94+
'h6',
95+
'blockquote',
96+
'p',
97+
'a',
98+
'ul',
99+
'ol',
100+
'nl',
101+
'li',
102+
'b',
103+
'i',
104+
'strong',
105+
'em',
106+
'strike',
107+
'code',
108+
'hr',
109+
'br',
110+
'div',
111+
'table',
112+
'thead',
113+
'caption',
114+
'tbody',
115+
'tr',
116+
'th',
117+
'td',
118+
'pre',
119+
'iframe',
120+
'span',
121+
],
122+
allowedAttributes: {
123+
a: ['href', 'name', 'target'],
124+
img: ['src'],
125+
iframe: ['src', 'allow', 'allowfullscreen', 'scrolling', 'class'],
126+
'*': ['class', 'id'],
127+
},
128+
allowedStyles: {
129+
'*': {
130+
// Match HEX and RGB
131+
color: [
132+
/^#(0x)?[0-9a-f]+$/i,
133+
/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/,
134+
],
135+
'text-align': [/^left$/, /^right$/, /^center$/],
136+
},
137+
},
138+
allowedIframeHostnames: ['www.youtube.com', 'codesandbox.io', 'codepen.io'],
139+
});
140+
}
141+
82142
const { useState, useEffect } = React;
83143

84144
type RenderedElement =
@@ -91,20 +151,23 @@ const MarkdownRender: React.FC<MarkdownRenderProps> = ({
91151
markdown,
92152
codeTheme = 'atom-one-light',
93153
onConvertFinish,
154+
editing,
94155
}) => {
95156
const initialHtml = ssrEnabled
96157
? remark()
97158
.use(breaks)
98159
.use(prismPlugin)
99-
.use(htmlPlugin)
160+
.use(htmlPlugin, {
161+
sanitize: true,
162+
})
100163
.use(embedPlugin)
101164
.use(slug)
102165
.processSync(markdown)
103166
.toString()
104167
: '';
105168

106169
const [element, setElement] = useState<RenderedElement>(
107-
ssrEnabled ? parse(initialHtml) : null,
170+
ssrEnabled ? parse(filter(initialHtml)) : null,
108171
);
109172

110173
const applyElement = React.useMemo(() => {
@@ -131,11 +194,11 @@ const MarkdownRender: React.FC<MarkdownRenderProps> = ({
131194
// if (window && (window as any).twttr) return;
132195
loadScript('https://platform.twitter.com/widgets.js');
133196
}
134-
const el = parse(html);
197+
const el = parse(editing ? html : filter(html));
135198

136199
applyElement(el);
137200
});
138-
}, [applyElement, markdown, onConvertFinish]);
201+
}, [applyElement, editing, markdown, onConvertFinish]);
139202

140203
return (
141204
<Typography>

src/components/write/MarkdownPreview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const MarkdownPreview: React.FC<MarkdownPreviewProps> = ({
3131
return (
3232
<MarkdownPreviewBlock id="preview">
3333
<Title>{title}</Title>
34-
<MarkdownRender markdown={markdown} />
34+
<MarkdownRender markdown={markdown} editing />
3535
</MarkdownPreviewBlock>
3636
);
3737
};

src/components/write/WriteMarkdownEditor.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,18 @@ const checker = {
170170
return pathMatch[1];
171171
},
172172
codesandbox: (text: string) => {
173-
const regex = /^<iframe src="https:\/\/codesandbox.io\/embed\/(.*?)".*<\/iframe>$/;
173+
const regex = /^<iframe.*src="https:\/\/codesandbox.io\/embed\/(.*?)".*<\/iframe>$/s;
174174
const result = regex.exec(text);
175175
if (!result) return null;
176176
return result[1];
177177
},
178+
codepen: (text: string) => {
179+
const regex = /^<iframe.*src="https:\/\/codepen.io\/(.*?)".*/;
180+
const result = regex.exec(text);
181+
console.log(result);
182+
if (!result) return null;
183+
return result[1];
184+
},
178185
};
179186

180187
type CheckerKey = keyof typeof checker;

src/lib/remark/embedPlugin.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import visit from 'unist-util-visit';
22

33
// const regex = /!(youtube|twitter|codesandbox)\[(.*?)\]/;
44

5-
const embedTypeRegex = /^!(youtube|twitter|codesandbox)$/;
5+
const embedTypeRegex = /^!(youtube|twitter|codesandbox|codepen)$/;
66
const converters = {
77
youtube: (code: string) =>
8-
`<iframe class="youtube" src="https://www.youtube.com/embed/%3Cspan%20class="pl-s1">${code}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`,
8+
`<iframe class="embed" src="https://www.youtube.com/embed/%3Cspan%20class="pl-s1">${code}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`,
99
twitter: (code: string) =>
1010
`<blockquote class="twitter-wrapper"><blockquote class="twitter-tweet" data-lang="ko"><a href="https://twitter.com/${code}"></a></blockquote></blockquote>`,
1111
codesandbox: (code: string) =>
12-
`<iframe src="https://codesandbox.io/embed/${code}" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>`,
12+
`<iframe class="embed" src="https://codesandbox.io/embed/${code}" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>`,
13+
codepen: (code: string) =>
14+
`<iframe class="embed" scrolling="no" title="Tab Menu Layout2" src="https://codepen.io/${code}" frameborder="no" allowtransparency="true" allowfullscreen="true"></iframe>`,
1315
};
1416

1517
type ConverterKey = keyof typeof converters;

src/lib/styles/prismThemes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ const prismThemes = {
316316
code[class*='language-'],
317317
pre[class*='language-'] {
318318
color: #f8f8f2;
319-
text-shadow: 0 1px rgba(0, 0, 0, 0.3); // direction: ltr;
319+
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
320320
}
321321
322322
:not(pre) > code[class*='language-'],
@@ -326,7 +326,7 @@ const prismThemes = {
326326
327327
pre {
328328
color: #f8f8f2;
329-
text-shadow: 0 1px rgba(0, 0, 0, 0.3); // direction: ltr;
329+
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
330330
background: #272822;
331331
}
332332
@@ -429,7 +429,7 @@ http://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascr
429429
code[class*='language-'],
430430
pre[class*='language-'] {
431431
color: #ccc;
432-
background: rgb(40, 41, 54); // text-shadow: none;
432+
background: rgb(40, 41, 54);
433433
}
434434
435435
pre[class*='language-']::-moz-selection,

yarn.lock

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,11 +1501,18 @@
15011501
dependencies:
15021502
date-fns "*"
15031503

1504-
1504+
"@types/domhandler@*", "@types/domhandler@2.4.1":
15051505
version "2.4.1"
15061506
resolved "https://registry.yarnpkg.com/@types/domhandler/-/domhandler-2.4.1.tgz#7b3b347f7762180fbcb1ece1ce3dd0ebbb8c64cf"
15071507
integrity sha512-cfBw6q6tT5sa1gSPFSRKzF/xxYrrmeiut7E0TxNBObiLSBTuFEHibcfEe3waQPEDbqBsq+ql/TOniw65EyDFMA==
15081508

1509+
"@types/domutils@*":
1510+
version "1.7.2"
1511+
resolved "https://registry.yarnpkg.com/@types/domutils/-/domutils-1.7.2.tgz#89422e579c165994ad5c09ce90325da596cc105d"
1512+
integrity sha512-Nnwy1Ztwq42SSNSZSh9EXBJGrOZPR+PQ2sRT4VZy8hnsFXfCil7YlKO2hd2360HyrtFz2qwnKQ13ENrgXNxJbw==
1513+
dependencies:
1514+
"@types/domhandler" "*"
1515+
15091516
"@types/eslint-visitor-keys@^1.0.0":
15101517
version "1.0.0"
15111518
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@@ -1560,6 +1567,15 @@
15601567
"@types/react" "*"
15611568
hoist-non-react-statics "^3.3.0"
15621569

1570+
"@types/htmlparser2@*":
1571+
version "3.10.1"
1572+
resolved "https://registry.yarnpkg.com/@types/htmlparser2/-/htmlparser2-3.10.1.tgz#1e65ba81401d53f425c1e2ba5a3d05c90ab742c7"
1573+
integrity sha512-fCxmHS4ryCUCfV9+CJZY1UjkbR+6Al/EQdX5Jh03qBj9gdlPG5q+7uNoDgE/ZNXb3XNWSAQgqKIWnbRCbOyyWA==
1574+
dependencies:
1575+
"@types/domhandler" "*"
1576+
"@types/domutils" "*"
1577+
"@types/node" "*"
1578+
15631579
"@types/http-assert@*":
15641580
version "1.5.1"
15651581
resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b"
@@ -1834,6 +1850,13 @@
18341850
"@types/prop-types" "*"
18351851
csstype "^2.2.0"
18361852

1853+
"@types/sanitize-html@^1.20.2":
1854+
version "1.20.2"
1855+
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-1.20.2.tgz#59777f79f015321334e3a9f28882f58c0a0d42b8"
1856+
integrity sha512-SrefiiBebGIhxEFkpbbYOwO1S6+zQLWAC4s4tipchlHq1aO9bp0xiapM7Zm0ml20MF+3OePWYdksB1xtneKPxg==
1857+
dependencies:
1858+
"@types/htmlparser2" "*"
1859+
18371860
"@types/serve-static@*":
18381861
version "1.13.3"
18391862
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
@@ -2514,7 +2537,7 @@ array-union@^1.0.1:
25142537
dependencies:
25152538
array-uniq "^1.0.1"
25162539

2517-
array-uniq@^1.0.1:
2540+
array-uniq@^1.0.1, array-uniq@^1.0.2:
25182541
version "1.0.3"
25192542
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
25202543
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
@@ -6362,7 +6385,7 @@ [email protected]:
63626385
tapable "^1.1.0"
63636386
util.promisify "1.0.0"
63646387

6365-
[email protected], htmlparser2@^3.3.0:
6388+
[email protected], htmlparser2@^3.10.0, htmlparser2@^3.3.0:
63666389
version "3.10.1"
63676390
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
63686391
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
@@ -8018,21 +8041,46 @@ lodash._reinterpolate@^3.0.0:
80188041
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
80198042
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
80208043

8044+
lodash.clonedeep@^4.5.0:
8045+
version "4.5.0"
8046+
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
8047+
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
8048+
80218049
lodash.defaults@^4.2.0:
80228050
version "4.2.0"
80238051
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
80248052
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
80258053

8054+
lodash.escaperegexp@^4.1.2:
8055+
version "4.1.2"
8056+
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
8057+
integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
8058+
80268059
lodash.flatten@^4.4.0:
80278060
version "4.4.0"
80288061
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
80298062
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
80308063

8064+
lodash.isplainobject@^4.0.6:
8065+
version "4.0.6"
8066+
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
8067+
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
8068+
8069+
lodash.isstring@^4.0.1:
8070+
version "4.0.1"
8071+
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
8072+
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
8073+
80318074
lodash.memoize@^4.1.2:
80328075
version "4.1.2"
80338076
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
80348077
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
80358078

8079+
lodash.mergewith@^4.6.1:
8080+
version "4.6.2"
8081+
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
8082+
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
8083+
80368084
lodash.sortby@^4.7.0:
80378085
version "4.7.0"
80388086
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@@ -11337,6 +11385,22 @@ sane@^4.0.3:
1133711385
minimist "^1.1.1"
1133811386
walker "~1.0.5"
1133911387

11388+
sanitize-html@^1.21.1:
11389+
version "1.21.1"
11390+
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.21.1.tgz#1647d15c0c672901aa41eac1b86d0c38146d30ce"
11391+
integrity sha512-W6enXSVphVaVbmVbzVngBthR5f5sMmhq3EfPfBlzBzp2WnX8Rnk7NGpP7KmHUc0Y3MVk9tv/+CbpdHchX9ai7g==
11392+
dependencies:
11393+
chalk "^2.4.1"
11394+
htmlparser2 "^3.10.0"
11395+
lodash.clonedeep "^4.5.0"
11396+
lodash.escaperegexp "^4.1.2"
11397+
lodash.isplainobject "^4.0.6"
11398+
lodash.isstring "^4.0.1"
11399+
lodash.mergewith "^4.6.1"
11400+
postcss "^7.0.5"
11401+
srcset "^1.0.0"
11402+
xtend "^4.0.1"
11403+
1134011404
sanitize.css@^10.0.0:
1134111405
version "10.0.0"
1134211406
resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-10.0.0.tgz#b5cb2547e96d8629a60947544665243b1dc3657a"
@@ -11834,6 +11898,14 @@ sprintf-js@~1.0.2:
1183411898
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
1183511899
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
1183611900

11901+
srcset@^1.0.0:
11902+
version "1.0.0"
11903+
resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
11904+
integrity sha1-pWad4StC87HV6D7QPHEEb8SPQe8=
11905+
dependencies:
11906+
array-uniq "^1.0.2"
11907+
number-is-nan "^1.0.0"
11908+
1183711909
sshpk@^1.7.0:
1183811910
version "1.16.1"
1183911911
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"

0 commit comments

Comments
 (0)