概要
みなさんこんにちはcandleです。今回はReactでコメントなどに投稿されたURLからリンクを生成する方法を紹介します。しかし、URLからリンクの自動生成は結構危険です。無尽蔵にリンク化を許可するとスパムやウイルス配布サイトへのリンクを貼られる可能性があります。また、十分にXSS対策をする必要があります。利用する際には十分に気をつけてください。
今回試すのは文字列をLinkifyを使う場合と自作の関数を使った場合の2通り試してみます。
前提
なし
準備
もしも、サンプルコードを動かしたい場合は下のコードをsrc/App.js
などに貼り付けて実行してみてください。
import React, { Component } from 'react' class App extends Component { constructor(props) { super(props) this.state = { content: '', } this.handleFormInputChanged = this.handleFormInputChanged.bind(this) } handleFormInputChanged(event) { this.setState({ [event.target.name]: event.target.value, }) } render() { return ( <div style={{ textAlign: 'center' }}> <div>サンプル</div> <textarea name="content" value={this.state.content} onChange={this.handleFormInputChanged} style={{ width: '400px', height: '300px', fontSize: '15px', }} /> <div style={{ marginTop: '50px' }}>{this.state.content}</div> </div> ) } } export default App
Linkifyを使って文字列中のURLをリンク化する
文字列の中に含まれているURLをリンク化する方法は大き加えて2つあります。Linkifyライブラリを利用する方法と自分でリンク化する方法です。最初にLinkfiyライブラリを使った方法を紹介します。Linkifyをインストールして
yarn add react-linkify
Linkifyをインポートします。
import Linkify from 'react-linkify'
もしも上のサンプルコードを利用しているならsrc/App.js
のrender関数内にこのように書けば文字列がリンクに変換されます。要はリンク化したい文字列を<Linkify>
コンポーネントで囲えば良いのです。
<div style={{ marginTop: '30px' }}> <Linkify>{this.state.content}</Linkify> </div>
上のフォームにこのように文字を打ち込んで見ましょう。ドメイン名からもリンク化できますね
URLからリンクを作ってみましょう。 https://www.google.com ドメイン名からもリンク化します。 yahoo.co.jp
別タブで開きたい場合などはpropsで指定してあげます。
<div style={{ marginTop: '10px' }}> <Linkify properties={{ target: '_blank', style: { color: 'red', fontWeight: 'bold' } }}> {this.state.content} </Linkify> </div>
詳しくは公式ドキュメントで
http://tasti.github.io/react-linkify/
自作のリンク作成コード
次に、自作のリンク生成コードを作ります。 外部ライブラリを使わない分ファイル容量が軽くなるのは間違い無いでしょう。
結構複雑になったので先に完成版のコードを書いておきます。
import React, { Component } from 'react' class App extends Component { constructor(props) { super(props) this.state = { content: '', } this.handleFormInputChanged = this.handleFormInputChanged.bind(this) } handleFormInputChanged(event) { this.setState({ [event.target.name]: event.target.value, }) } strip_tags(input) { const tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi return input.replace(tags, '') } url_converter(input) { const regExp = /(https?:\/\/\S+)/g return input.replace(regExp, '<a href="$1" target="_blank">$1</a>') } render() { return ( <div style={{ textAlign: 'center' }}> <div>サンプル</div> <textarea name="content" value={this.state.content} onChange={this.handleFormInputChanged} style={{ width: '400px', height: '300px', fontSize: '15px', }} /> <div style={{ marginTop: '50px' }}> <div dangerouslySetInnerHTML={{ __html: this.url_converter(this.strip_tags(this.state.content)), }} /> </div> </div> ) } } export default App
コードの解説をすると、strip_tags
でタグを全て削除します。
strip_tags(input) { const tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi return input.replace(tags, '') }
url_converter
でURLをリンクに変換します。
url_converter(input) { const regExp = /(https?:\/\/\S+)/g return input.replace(regExp, '<a href="$1" target="_blank">$1</a>') }
使うときはこの様に呼び出して使えばURLをリンクにすることができます。
<div dangerouslySetInnerHTML={{ __html: this.url_converter(this.strip_tags(this.state.content)), }} />
テキストエリアにこの書き込みを入れると、XSSに対処して、ちゃんとURLがリンクになっています。
<script>alert("1");</script> <a onmouseover="alert(1)">XSS</a> <iframe src="javascript:alert('1');"></iframe> こんにちは http://www.google.com これがグーグルのリンクです
問題なさそうですね。
まとめ
正直dangerouslySetInnerHTML
は使いたくないですけど、ちゃんと正規表現でタグを除外してあげれば、問題ないでしょう。