「JavaScript文字列リテラル用のエスケープ」という表現は微妙だけど、要はサーバサイドでJavaScriptを動的生成する時に、文字列リテラルとして任意のパラメータを出力したい場合のエスケープ処理。
先日(というか先月)書いたHTMLエスケープ用のシェルスクリプトは、通常のHTMLエスケープ用だ。HTMLの文法にて特別扱いされる最小限の文字をエスケープするので、HTML要素の内容(Content)ないし属性値として使用する文字列に対して適用するのが正しい。
サーバサイドではJavaScriptを動的生成することも時々ある。大抵はJavaScriptの文字列リテラルとして任意のパラメータを出力するスタイルだ。このケースでのエスケープは一般的なHTMLエスケープとは異なる。
- 出力したJavaScriptの文字列リテラルをJavaScriptコード中で以下のプロパティ/メソッドに適用していて、かつHTML要素として解釈されたくない場合は、HTMLエスケープする。
- HTMLDocument.write()
- HTMLDocument.writeln()
- HTMLElement.innerHTML
- JavaScriptの文字列リテラルとして許容される内容にエスケープする。
- 「\」を「\\」に置換する。
- 「'」を「\'」に置換する。
- 「"」を「\"」に置換する。
- 制御文字をエスケープシーケンスに置換する(例えば改行を「\n」に置換する等)
- 追加の処理。
- JavaScriptのコードの出力先が何らかのタグの属性値の部分であるなら、属性値全体をHTMLエスケープする。
- JavaScriptのコードの出力先がscriptタグの中であるなら、「</script>」を含まないようにする。
- JavaScriptのコードの出力先がCDATAの中であるなら、「]]>」を含まないようにする。
「</script>」や「]]>」を含まないようにするにはどうするか? JavaScriptの文字列リテラルの範囲内で「</script>」や「]]>」と等価な別の表現にすればよい。例えばUnicodeエスケープシーケンスに置き換えるのだ。JavaScriptの文字列リテラルでは、 "]]>" と "\u005D\u005D\u003E" は等価だ。
変換元テキストがUTF-8であるという前提で、試しにシェルスクリプトでエスケープ処理を実装してみた。escape-jsstrという名前だ。
#!/bin/sh PATH=/bin:/usr/bin:/usr/local/bin readonly HEX2='[0-9A-F][0-9A-F]' readonly SPC='[[:blank:]][[:blank:]]*' exec iconv -f UTF-8 -t UTF-16BE ${@+"$@"} | od -A n -t x1 -v | tr '[a-z]' '[A-Z]' | sed "s/\\($SPC$HEX2\\)$SPC\\($HEX2\\)/\\1\\2/g s/$SPC/\\\\u/g" | tr -d '\n'
面倒なので全てUnicodeエスケープシーケンスに変換している。
hexdump(1)を使用できる環境なら、もう少し簡単に実現できる。
#!/bin/sh PATH=/bin:/usr/bin:/usr/local/bin exec iconv -f UTF-8 -t UTF-16BE ${@+"$@"} | hexdump -v -e '"\\" "u" 2/1 "%02X"'
とはいえ、いつもhexdump(1)を使えるとは限らない。LinuxやmacOS、FreeBSDなどを使う分には困らないが、Cygwinでは標準では含まれない(別途util-linuxを入れる必要がある)。商用UNIXは触ったことがないが、やはり怪しいようだ。
echo(1)ライクに引数の文字列をエスケープするシェルスクリプトはこんな感じ。
#!/bin/sh exec echo -n ${@+"$@"} | escape-jsstr
HTMLエスケープネタと同様にとりあえず作ってみたけど、需要あるのかなあ?