@ledsun blog

Hのキーがhellで、Sのキーがslaveだ、と彼は思った。そしてYのキーがyouだ。

OSSライブラリからの学びかた npmからJavaScriptのライブラリを探してソースコードを見つける編

はじまり

blog.magnolia.tech

CPANに上がってるモジュール、一つ一つの粒度が小さいから読みやすいし、ドキュメントもテストもしっかり揃ってて挙動を把握しやすくて、自分にとっては最高の教科書だった

OSSで公開されているソースコードは、最高の教科書ですよね。 Perlにはなじみがないので、卑近な例としてJavaScriptの場合を考えてみます。

npmからJavaScriptのライブラリを探してソースコードを読む

例えばHTMLエスケープの実装が知りたいとき

HTML文字列を生成する際に、ユーザー入力をそのまま出力するとタグが生成されてHTMLが壊れることがあります。 というかXSS脆弱性です。そこでHTMLエスケープをしたいなと思うのですが・・・どう実装したら漏れなく対応できるかわからない状況を仮定しましょう。

npmリポジトリを検索

JavaScriptではnpm | build amazing thingsというパッケージリポジトリが主流です。 まずはここで検索します。

HTMLエスケープしたいので、雑にhtmlescapeで検索してみましょう。 https://www.npmjs.com/search?q=html%20escape

f:id:ledsun:20210108134104p:plain
npmjs.orgでhtmlとescapeで検索した結果

224パッケージも出てきて、どれを見たらいいか迷います。 まずは一番うえのhtml-escaperから見てみましょう。

https://www.npmjs.com/package/html-escaper

f:id:ledsun:20210108134346p:plain
html-escaperのダウンロード数

まずダウンロード数を確認します。 700万とか異次元の数値が出ています。 広く使われているようです。

参考までに2番目のxssパッケージも見てみましょう。

f:id:ledsun:20210108134559p:plain
xssのダウンロード数

90万件と十分に使われています。 が、html-escaperは一桁多いので、HTMLエスケープ用のパッケージとしては、html-escaperがもっとも広く使われていると推測できます。

つぎにhtml-escaperの実装を見てみましょう。 右側のHomepageの欄にGitHubへのリンクがあります。

f:id:ledsun:20210108134921p:plain
html-escaperのGitHubへのリンク

GitHubで実装を探す

https://github.com/WebReflection/html-escaper

f:id:ledsun:20210108135901p:plain
html-escaperのGitHubリポジトリ

JavaScriptのライブラリはindex.jsがエントリポイントになっていることが多いので、inedx.jsを見てみましょう。 一番下までスクロールすると、次のようにexportsescapeunescapeを代入している箇所が見つかります。

f:id:ledsun:20210108140143p:plain
html-escaperのexport

JavaScriptのCommonJSというパッケージでは、exportsオブジェクトを使って、定義した関数を公開します。 このescapeの実装が見つかれば、HTMLエスケープの実装がわかりそうです。

少し上にスクロールすると、次のように41行目でescapeが定義されている場所が見つかりました。

f:id:ledsun:20210108141012p:plain
escapeは41行目で定義されている

escape関数の実装を読む

次の定義からescapeはreplace関数を呼んでいることがわかります。

const escape = es => replace.call(es, ca, pe)

このreplaceは何でしょうか?

24行目でconst {replace} = '';と定義されています。 これはつまりString.prototype.replaceです。 分割代入を使って、組み込み関数への参照を取得する方法を初めて見ました。驚きです。勉強になりますね。

replacecallで呼び出しているので、es.replace(ca, pe)と同じです。 es => replace.call(es, ca, pe)とアロー関数で定義されているので、次の関数定義と同じです。

function espace(es) {
  return es.replace(ca, pe)
}

第一引数caは何でしょうか?28行目で定義されていいます。

const ca = /[&<>'"]/g;

HTMLエスケープで、置き換えたい文字を表す正規表現です。

第二引数peは何でしょうか?30〜37行目を見てみましょう。

const esca = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  "'": '&#39;',
  '"': '&quot;'
};
const pe = m => esca[m];

置き換えたい文字列をキーにして、置き換え後の文字列を返す関数です。 String.prototype.replaceは第二引数に関数を指定できます。 MDNでは次のように説明されています。

新しい部分文字列を生成するために実行される関数で、regexp や substr でマッチしたものを置き換えるのに使われます。この関数に渡される引数は下記の「引数としての関数の指定」で述べられています。

つまり、escape関数は引数で与えられた文字列で、escaマップに定義された文字の組み合わせで置き換えることがわかりました。 &, <,>, ", 'を置換できれば、十分有効にHTMLエスケープとして動きそうだと推測できます。

やりました。 ここまでで、HTMLエスケープの仕様を調べずに、HTMLエスケープの仕様をなんとなく把握しました。

オチ

いい感じにOSSのライブラリから実装を見つけられないときは、 大体「HTMLエスケープの実装が知りたい」みたいないい感じのゴールを設定できていない時なんですよね・・・。