<-- mermaid -->

プログラミングの原則:構造化テキストを文字列結合で作らない、置換でいじらない

こんにちは、ソーシャル経済メディア「NewsPicks」のむとうです。

先日から『Ghost of Tsushima』の開発者が書いた『ルールズ・オブ・プログラミング』という本をちょっとずつ読み進めていて、プログラミング熱が高まっています。この本は大きな指針を示すだけで具体の話をするものではないのですが、読み物として面白いので私も似たようなことをやってみたくなりました。

何年もこういう仕事をしているとバグが入るパターンというのが見えてきます。そしてだいたいどこに行っても何の仕事でも似たようなことをすることになるのですが、今回の話もその一つです。

構造化テキストを文字列結合で作らない、置換でいじらないというのはこれだけみると何のことか分かりづらいかも知れませんがSaaS Product Team セキュアコーディングの啓蒙 第2回 (SQL インジェクション編)の内容とある面では同じ話です。SQLという「構造をもったテキスト(=構造化テキスト、と呼ぶことにします)」を文字列結合で作るとSQLインジェクションのようなセキュリティ上の問題を引き起こすのでやってはいけません、ということは有名なので同意される人は多いかも知れませんが、構造化テキストはSQLだけではないため同じ話が他にも展開できます。

  • URLを文字列結合で作ってはいけない、置換でいじってはいけない
  • XMLを文字列結合で作ってはいけない、置換でいじってはいけない
  • HTMLを文字列結合で作ってはいけない、置換でいじってはいけない
  • JSONを文字列結合で作ってはいけない、置換でいじってはいけない
  • 正規表現を文字列結合で作ってはいけない、置換でいじってはいけない
  • CSVを文字列結合で、、

皆さんのお手元のコードに文字列結合しているコードはないでしょうか?この手のコードは大抵の場合バグっていて、正しく動かなかったりセキュリティ上の問題をはらんでいます。

ではどうすればいいか。アプローチは大きく2つあります。

  1. テンプレートに安全に値を埋め込む
  2. 対応するデータ構造を作り、データを加工し、再度文字列化する

テンプレートに安全に値を埋め込む

SQLインジェクションを防止するためのプレースホルダーの考え方です。SQLを扱うライブラリにはプレースホルダーに値をいれるための仕組みがあるので、これを利用すると安全にSQLを作ることができます。 (再掲: SaaS Product Team セキュアコーディングの啓蒙 第2回 (SQL インジェクション編))

Reactであればjsx(tsx)がテンプレートになっていて、JavaScriptの式を安全にHTMLの中に埋め込むことが出来ます。実際はjsxは単なるテンプレートではなくデータ構造を作るためのDSLとなっており、中身としては次の話と同じです。

対応するデータ構造を作り、データを加工し、再度文字列化する

文字列を直接操作するかわりに、対応するデータ構造を操作することで安全に構造化テキストをつくることができます。

文字列ではなく対応するデータ構造を加工する

構造化テキストは基本的に対応するデータ構造が存在します。URLであればURL型、JSONであればシリアライズ可能なオブジェクトといった形です。

あるURLにパラメータをつける処理を考えてみましょう。文字列処理でこれをやる場合、もしパラメータがすでに付いていたら & でつなぐ、そうでなければ?でつなぐ、パラメータ名と値をエスケープしたものを&でjoinし、フラグメントがあればその前にパラメータを、、のように処理が複雑になり、かつそこで何をやりたいのかも分かりづらくなります。バグがないかどうかを確かめるのも大変です。

JavaScriptであれば URL()コンストラクターを使うとこのような処理を簡単かつ意図が伝わりやすい表現にできます。

const url = new URL(location.href); // URLオブジェクトの作成
url.searchParams.append("param", value); // パラメータの追加
url.toString(); // 文字列化

valueがどのような値であれ、元のURLにクエリパラメータがついていようがいまいが、このコードは正しく動きます。

JSONでも同様です。文字列結合や置換で以下のようなJSONをうまく扱うのは難しいでしょう。(すでにhatenaブログの色付がバグっています、、)

{"hoge\":fuga{":"hoge\"fuga}"}

JSONを編集したい場合は一旦オブジェクトにデシリアライズし、JSONPathを扱うライブラリで加工したものを再度シリアライズするのが適切です。安全に加工できますし、コードの意図も明瞭です。

結論:文字列を加工しない

文字列に決まったフォーマットが存在して意味のあるデータになっている場合、それは文字列のまま加工してはいけません。文字列を結合して何かをやりたくなったら「これは構造化テキストではないか?対応するデータ構造があるのではないか?」と想像してみて下さい。もし一般的なデータ構造でなかったとしても、あなたの作りたいテキストが構造化テキストであるならば対応するデータ構造を定義してシリアライズ処理を書くことを検討してください。

Page top