literal-string型
リテラルおよびクラス定数から構成される文字列を表すPsalmの型/PHPStanの型。基本的にはセキュリティ目的で利用される。
Scalar types #literal-string - Psalm Documentationでは以下の例が挙げられている。
"hello " . "world"
"hello " . Person::DEFAULT_NAME
implode(', ', ["one", "two"])
implode(', ', [1, 2, 3])
"hello " . <another literal-string>
コンセプトについてはPHP RFC: is_literalを参照(提案としては否決済み)。
基本的にはXSSやSQLインジェクションのようなインジェクション系の脆弱性を回避するために用いる
静的解析でも検査は可能だが実行時検査できた方が安全だとRFCは主張する
RFCにはPsalmの検出を迂回するための例が掲載されている
Rubyはこれに似たtaintという概念がかつて存在した(過去形)
Rubyにはオブジェクトを汚染する仕組みがあった - Eggshell
プログラミング言語が提供するセキュリティ機構としては難しい面がある
セキュアなプログラムを構築するための確実な基盤とはならない
一方で、確実ではなくとも静的な型として表明できると嬉しい場面はある
Psalmには4.8.0で実装された
PHPStanには0.12.97で実装された
使いかた
以下のような関数を考えてみる
code:php
/**
* @param literal-string $format
*/
function html_printf(string $format, mixed ...$args): void
{
vprintf($format, array_map(fn(string $s) => htmlspecialchars($s, ENT_QUOTES), $args));
}
呼び出しコード
code:php
<?php declare(strict_types = 1);
// 安全
html_printf('<title>%s</title>', 'test');
// 安全
html_printf('<title>%s</title>', $_GET'name');
// 外部から入力する文字列は % の数が不定で実行時エラーを起こす可能性があるので安全ではない
html_printf((string)$_GET'format', 'test');
この例においてはHTML組み立てに外部入力を用いることだけが危険なのではなく、フォーマット文字列に外部入力を用いると実行時エラーを起こせて危険だということには注意されたい。
外部入力値に関してはhtmlspecialchars()でエスケープされている
同様に、普通のprintf()やpreg_match()に対して外部入力文字列を渡すのも危険
PHPStan https://phpstan.org/r/898f4e6c-a62c-4e7d-b765-c7ee9d41ba33
Psalm https://psalm.dev/r/2e275b760d