前提・実現したいこと
私はquill.jsというWYSIWYGエディタのライブラリを利用して、ユーザーからリッチテキストエディタから入力された値を取得して出力するアプリケーションを検討しております。以下のコードが当該ライブラリに基づき作成されたコードです。
html
1<!--sample.html--> 2<!DOCTYPE html> 3<html lang="ja"> 4<head> 5 <meta charset="utf-8"> 6 <title>TexTile</title> 7 8 <script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script> 9 <!--quill.js--> 10 <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet"> 11 <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script> 12</head> 13 14<body> 15 <div id="textile"></div> 16 <form class="" action="#" method="post"> 17 <input type="hidden" id="textile_hidden" name="editor-input"> 18 <input type="submit" name="" value="投稿する"> 19 </form> 20 21 <script> 22 var toolbarOptions_textile = [ 23 ['bold', 'italic', 'underline', 'strike'], // toggled buttons 24 ['blockquote',], // custom button values 25 [{ 'list': 'ordered'}, { 'list': 'bullet' },{ 'align': [] }], //{ 'direction': 'rtl' } 26 [{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent 27 [{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript 28 [{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown 29 [{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme 30 [{ 'font': [] }], 31 ['link', 'image','video'], 32 // ['formula'], 33 ['clean'], // remove formatting button 34 ]; 35 36 var quill_textile = new Quill('#textile', { 37 theme: 'snow', 38 placeholder: 'テキストを作成する', 39 modules: { 40 toolbar: toolbarOptions_textile 41 }, 42 }); 43 44 var editor = document.getElementById('textile'); 45 var editorInput = document.getElementById('textile_hidden'); 46 47 quill_textile.on('text-change', function(delta, oldDelta, source) { 48 var editorHtml = editor.querySelector('.ql-editor').innerHTML; 49 editorInput.value = editorHtml; //html形式で取得する 50 /*editorInput.value = JSON.stringify(quill_textile.getContents()) //JSON形式で取得 */ 51 }); 52 </script> 53 </body> 54</html>
ここで上記のsample.htmlにおいて、2通りのエディタの入力値の取得方法があります。
0. htmlでそのまま出力して値を取得する editorInput.value = editorHtml; //html形式で取得する
- JSON形式で取得する(ライブラリが提供)
editorInput.value = JSON.stringify(quill_textile.getContents()) //JSON形式で取得
上記の1.html形式でそのまま取得した場合には以下のように取得されます。
html
1<p>今日の日記は<strong><u>以下の通り</u></strong>です。</p> 2<ol> 3 <li><a href="https://finance.yahoo.co.jp/" target="_blank">Yahoo!ファイナンス</a>をチェックした。</li> 4 <li>友達とご飯を食べに行った。</li> 5</ol> 6<p>(メモ)こんなコードを思いついた。<script>alert("danger")</script></p></p> 7<p><span class="ql-size-large" style="background-color: rgb(255, 255, 0);">良い一日</span>でした!</p>
他方、2.JSON形式で出力した場合、以下のように出力されます。
JSON
1{"ops":[ 2 {"insert":"今日の日記は"}, 3 {"attributes":{"underline":true,"bold":true},"insert":"以下の通り"}, 4 {"insert":"です。\n"}, 5 {"attributes":{"link":"https://finance.yahoo.co.jp/"},"insert":"Yahoo!ファイナンス"}, 6 {"insert":"をチェックした。"}, 7 {"attributes":{"list":"ordered"},"insert":"\n"}, 8 {"insert":"友達とご飯を食べに行った。"}, 9 {"attributes":{"list":"ordered"},"insert":"\n"}, 10 {"insert":"(メモ)こんなコードを思いついた。\n"}, 11 {"attributes":{"background":"#ffff00","size":"large"},"insert":"良い一日"}, 12 {"insert":"でした!\n"} 13]}
実現したいこととしてこれらの値をRDBに格納し、安全に出力することとなります。
検討1
そこで、まずライブラリが提供する、上記のJSON形式のデータをパースしてHTMLを再構築した上で、"insert"の値をhtmlspecialchars()で出力することを検討しました。hrefやsrc属性の中身もリスクがある認識ですが、そのチェックは別途するものとしてここでは割愛させていただきます。
ただ、実際の構造を見ていただくと分かる通り、<ol><li>
のネスト構造を識別し難いことや、attributes
が属性だけを属性だけを指すのではなく例えばsize
の指定があれば、<span>
を作った上で、特定のclass
属性を指定するなど、実に使いづらい印象で、直接HTMLを取得して、自ら許可するタグを置換してホワイトリスト方式のアプローチで値を格納して出力する方がより効率的で合理的であると考えました。
検討2
上記検討1を踏まえて例えば、以下のように、ホワイトリストタグをHTMLを変換すれば、あとで出力する際にもある程度安全にできるのではと考えております。
HTML
1[*p*]今日の日記は[*strong*][*u*]以下の通り[*/u*][*/strong*]です。[*/p*] 2[*ol*] 3 [*li*][*a href(https://finance.yahoo.co.jp/),target(_blank)*]Yahoo!ファイナンス[*/a*]をチェックした。[*/li*] 4 [*li*]友達とご飯を食べに行った。[*/li*] 5[*/ol*] 6[*p*](メモ)こんなコードを思いついた。<script>alert("danger")</script></p>[*/p>*] 7[*p*][*span class(ql-size-large) style(background-color: rgb(255, 255, 0))*]良い一日[*/span*]でした![*/p>*] 8 9<script> 10 /* 11whiteListTag = ["<p>","<b>","<u>","<em>","<strike>","<strong>","<ol>","<ul>","<li>","<span>"] 12whiteListPop = ["class","style","href","target"] 13*/ 14</script>
質問
- ここで、例えば正規表現を用いて検討2に対応するばあ、閉じタグや属性がない開始タグであれば
preg_replace
でそれぞれホワイトリストにあるものを置換していけば良いので分かるのですが、属性がある場合にはどのように考えれば良いのでしょうか?
例えば正規表現で/<span.*(class).*>/
といった表現で該当するタグを見つけることなどが思いつくのですが、属性の記載順や属性の組み合わせなどはかなりの量に及ぶため、preg_match_all
やpreg_replace
を利用しても、分解して上記のような安全なコードに変換する術が思いつかないため、アドバイスを頂きたいです。
- また、そもそも置換の考え方に重大な問題を想定しており、上記のように
<p>(メモ)こんなコードを思いついた。<script>alert("danger")</script></p></p>
意図的に</p>
の閉じタグを送られてきた場合、これまでも[*/p*]
に変換されてしまい、ホワイトリストが悪用され、意図しない値を得ることになってしまうことが想定されます。
このようなケースの一般的な対応方法や、そもそも異なるアプローチをとるべきというアドバイスがあれば合わせてお願い申し上げます。
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。