Reactでマークダウン記法を実装しよう！

# はじめに
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1547587/24bc682c-25f1-ecc9-18f5-915b02a89b0f.jpeg"  width="150" height="186" />.
こんにちは！[かほ](https://twitter.com/kaho_eng)です♪
現在、本業ではエンジニア業務と技術広報業務に従事しています。

エンジニア業務ではReact/Next.jsを用いたフロントエンド開発に関わる機会が多いですが、
今回は技術メモの中からマークダウン記法の実装方法、それに伴うXSS対策について試した方法を説明します☺️


# この記事の読者対象
・Reactでマークダウン記法を実装したい方
・XSS対策について知りたい方

# 出来上がったもの

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1547587/93125c83-b14c-f323-755f-3aa2187211ed.gif" width="400" height="400" />

メッセージのフォームにマークダウン記法で入力した値が、プレビューでリアルに反映されてるというものです。

# コードの全体像
```App.tsx
import './App.css';
import { useState } from 'react';
import { marked } from "marked";
import sanitizeHtml from 'sanitize-html';

function App() {
  const [markdown, setMarkdown] = useState('');
  const markedText = sanitizeHtml(markdown, {
    allowedTags: [],
    disallowedTagsMode: 'recursiveEscape',
  });

  marked.setOptions({
    gfm: true,
    breaks: true,
  });

  const htmlText = marked.parse(markedText);


  return (
    <>
      <div>
        <form>
          <label>
            メッセージ
          </label><br/>
          <textarea name="name" rows={10} cols={40} onChange={(e) => setMarkdown(e.target.value)}/>
        </form>
      </div>
      <div>
        <p>プレビュー</p>
        <div className='bg-gray-200 p-3 text-sm w-full prose prose-sm'>
          <div dangerouslySetInnerHTML={{ __html: htmlText }} className='w-3/4' />
        </div>
      </div>
    </>
  );
}

export default App;

```
ここから数カ所に分けて実装方法を確認します。

# メッセージフォーム内の値を管理しよう！
まずはフォーム内の値を保持、状態管理します。
下記のようにuseStateで入力した値を状態管理し、onChangeイベントでフォームの入力欄に変更があった場合、プレビュー欄に文字を起こせるよう設定します。

```
const [markdown, setMarkdown] = useState('');
```
```
<textarea name="name" rows={10} cols={40} onChange={(e) => setMarkdown(e.target.value)}/>
```

# マークダウンの表記をHTMLに変換しよう！
次はフォーム内でマークダウン表記した値をHTMLに変換するため、`marked`ライブラリを使用します。
```
$ npm i --save-dev @types/marked
```

また、markedを使用する際は複数のオプションが存在します。
わたしが今回使用したオプションは、文字を改行表示するものです。
上記の動画にもあるようにフォーム内で改行して文字を打った場合、プレビューでも文字が改行されます。
オプション実装方法は、[オプション](https://marked.js.org/using_advanced#options)を参考に下記のように書きました。
```
marked.setOptions({
    gfm: true,
    breaks: true,
  });
```


# HTMLを表示させよう！
入力した値をパース処理したものをmarkedを用いてHTMLに変換するため、下記のように記述します。
```
const htmlText = marked.parse(markedText);
```

次にReactにHTMLを挿入します。その場合は`dangerouslyuSetInnerHTML`属性に__htmlプロパティを持つObjectを渡してあげる必要があります。

また、メッセージフォームで入力したタグのスタイルがブラウザー上で可視化されるように、
[Typographyプラグイン](https://tailwindcss.com/docs/typography-plugin)を使用します。
```
$ npm install -D @tailwindcss/typography
```
```
 <div dangerouslySetInnerHTML={{ __html: htmlText }} className='w-3/4' />
```

これでおしまい！と思ったそこのあなた！！！
ちょっと待ってください！
dangerouslyuSetInnerHTMLは名前の通り、とても危険な操作です（泣）

XSS(クロスサイトスクリプティング)対策のために文字をサニタイズしましょう！！！

XSS対策...???

# XSSってそもそもなんぞや？
XSS=クロスサイトスクリプティングについて知らない人がいると思うので説明します。

XSSとは、Webアプリケーションへのサイバー攻撃手法の一つです。WEBアプリケーションの脆弱性を利用して悪意のあるコードを実行させるといった内容です。

言葉だけではわからないと思うので、実際の例を説明します。
まずは下記の画像を見ましょう。(画像が静止している場合はクリックして見てね)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1547587/f4689b4d-3e10-1198-00fd-e92fc9858bc2.gif" width="400" height="400" />

画像ではメッセージフォームに「イタズラしちゃうぞ」という言葉をaタグで囲んで入力しました。
XSS対策していない場合、タグはHTMLに変換されないまま、タグとしての機能を保持して出力されてしまいます。そうして上記のようにタグを使用して別サイトへの誘導も可能にするのです。

この特性を利用として、サイトで悪意のあるポップやリンクを踏ませることができてしまいます。

# XSS対策を実装しよう！
XSS対策をする必要性が分かったところで文字のサニタイズを実装するために、`sanitize-html`を使用します。
```
npm i --save-dev @types/sanitize-html
```
sanitize-htmlをしようとする場合も複数の[オプション](https://github.com/apostrophecms/sanitize-html)が存在します。第一引数に入力した値、第二引数にオプションを設定しましょう。
```
const markedText = sanitizeHtml(markdown, {
    allowedTags: [],
    disallowedTagsMode: 'recursiveEscape',
});
```
今回使用したオプションは`allowedTags`と`disallowedTagsMode`です。

`allowedTags`に設定する値は、xss対策しないタグ名となります。今回はすべてのタグ名に対策を加えたいため、空文字と設定しました。

`disallowedTagsMode`に設定する値は指定されていないタグ名に対しての動作です。今回は`allowedTags`にタグが設定されているいないに関わらず、すべてのタグをエスケープするために、`recursiveEscape`と設定しました。


# 最後に
これで実装はおしまいです！

今回はReactを使用したマークダウン記法の実装方法と、それに伴うXSS対策について説明しました。
今後は主にフロントエンド周辺、コミュニティ運営、Tech PR(技術広報)の記事を中心に書いていく予定ですので、気になる方はぜひフォローをよろしくお願いします♪ではでは〜

Twitterアカウント:https://twitter.com/kaho_eng

# 参考資料
@types/marked
https://www.npmjs.com/package/@types/marked

Marked Documentation
https://marked.js.org/using_advanced#options

Typographyプラグイン
https://tailwindcss.com/docs/typography-plugin

apostrophecms/sanitize-html
https://github.com/apostrophecms/sanitize-html

クロスサイトスクリプティング（XSS）とは？わかりやすく解説
https://www.shadan-kun.com/waf/xss/