PHP 5.4 以上でも register_globals を再現するライブラリ MercifulPolluter

![php54_obsolute.png](https://qiita-image-store.s3.amazonaws.com/0/6635/f80e7c4d-4a37-c92e-42a0-34c5c5afacd1.png)

## あらすじ

PHP と呼ばれる言語では、かつて [register_globals](http://php.net/register_globals) という機能が猛威を奮っていました。簡単に言うと、リクエストパラメータが自動的にグローバル変数にセットされるというものです。

```php
// http://example.com/?foo=123&bar=baz

var_dump($_GET['foo'], $_GET['bar']);
// string(3) "123"
// string(3) "baz"

var_dump($foo, $bar);
// string(3) "123"
// string(3) "baz"
```

この機能が全盛期だった頃、残念ながら私は PHP を書いていませんでしが、おそらくこの機能が ON になっていることで「うわ、何も考えなくても勝手に変数として使える!便利だ!!」みたいな、 **よしなにやってくれる** という PHP 特有の機能でもダントツのものだったと思います。

しかし、上のコードを見ていただけるとわかるとおり、ある程度複雑化したアプリケーションがこの `register_globals` 前提の設計をしているとどうなるでしょう。「この変数 Where from?」としか思えないわけです。修正が困難になり、ソウルジェムが濁り、飯が食えなくなる。つらい。

もちろんセキュリティ的にもよろしくないということで有名です。

> register_globalsをonとした場合、この機能により、HTMLフォームから投稿される変数と同時に、あらゆる種類の変数がスクリプトに注入されることになります。これは、PHPにおいては変数の初期化が不要であるということにも関係し、安全でないコードを書くことが極めて容易になるということを意味します。
>
> [PHP: グローバル変数の登録機能の使用法 - Manual](http://php.net/manual/ja/security.globals.php)

公式サイトにもはっきりと「**安全でないコードを書くことが極めて容易になる**」と書かれている機能として名を馳せていました。

ですがこの機能、[PHP 5.4 で消え去りました](http://php.net/manual/ja/migration54.incompatible.php) 。悪魔は去ったのだ。平和は戻りました。

しかしみんな知っていました。PHP 5.4 から `register_globals` が消え去ったからといって、**世の中に存在する PHP アプリケーションが全て 5.4 以上になったわけじゃない**。現実は厳しいのだ。むしろ `register_globals` 前提で書かれたソースコードの改修に絶望し、PHP 5.3 以下から脱出できない方々も居るでしょう。[5.3 の公式サポートは既に EOL](http://php.net/archive/2014.php#id2014-08-14-1) 。どうすればいいんだ…

## 救済

- 将来的には全て作り直す必要がある
- でも俺達は人間だ。少しずつ少しずつ歩くしかないのだ
- かといってサポート切れの PHP 5.3 を使い続けるのは…
- PHP 5.4 にすぐにでも移行しつつ、且つ少しずつ修正していく時間を稼ぎたい

そんな方々の為に救済ライブラリとして `MercifulPolluter` をご紹介します。

- Packagist https://packagist.org/packages/gongo/merciful-polluter
- GitHub https://github.com/gongo/merciful-polluter

直訳すると **慈悲深い汚染**。あくまでも一時しのぎ。汚染していることに代わりはない。そういった覚悟を忘れない為のライブラリ名となっています。

## Installation

Composer をお使いの方は、すぐにお使い頂けます。
それ以外の方はがんばってください。

```json
{
    "require": {
        "gongo/merciful-polluter": "*"
    }
}
```

## Usage

### 1. Basic

本来の `register_globals` の機能になるべく近づける為に、下記コードをアプリケーションのエントリーポイント[^1]、その中でもなるべく早い段階にセットすることをお勧めします。

[^1]: 例えば [auto_prepend_file](http://php.net/auto_prepend_file) とか。各フレームワークにそういった類のものがあればそこでも大丈夫だと思います。

```php
<?php
(new Gongo\MercifulPolluter\Request)->pollute();

// main routine
```

`Gongo\MercifulPolluter\Request::pollute()` を呼び出した時点で、`$_GET['foo']` は `$foo` に、`$_POST['bar']` は `$bar` に値を注入します。

### 2. Session

ご存知の方も居らっしゃると思いますが、`register_globals = On` の場合、実は `$_SESSION` もグローバル変数に注入されます。

もちろん `MecifurlPolluter` は Session にも対応していますが、先程の `Gongo\MercifulPolluter\Request::pollute()` だけでは `$_SESSION` が注入されるようにはなっていません。別途下記のコードが必要となります。

```php
// session_start() の後に実行する
session_start();
(new Gongo\MercifulPolluter\Session)->pollute();
```

そしてもう一つ、`$_GET` や `$_POST` などのリクエスト系と違って、`$_SESSION` が `register_globals` の機能でグローバルに注入された時、**グローバル変数と $_SESSION の間でリファレンスが貼られます**。

```php
session_start();

var_dump($_SESSION['user_id'], $user_id);
// string(5) "gongo"
// string(5) "gongo"

$user_id = 'qiita';

var_dump($_SESSION['user_id'], $user_id);
// string(5) "qiita" <= !!!???
// string(5) "qiita"
```

`MercifulPolluter` はもちろんこの挙動も再現しております。

### 3. magic_quotes_gpc

`register_globals` と並んで、PHP が誇る **よしなに機能** として [magic_quotes_gpc](http://php.net/magic_quotes_gpc) のことも避けては通れません。

簡単に言うと `$_GET`、`$_POST` および `$_COOKIE` のそれぞれの要素に対して [addslashes()](http://php.net/addslashes) をかけていくものです。凄まじい威力だ。

この機能も PHP 5.4 で廃止となっていましたが、もしかしたらこの機能に依存しているアプリケーションもまだ数多くあることでしょう。

`magic_quotes_gpc` の機能は `register_globals` 同様、エントリーポイントに入る前に処理が行われているため、2つ併せて On にしている可能性も考え、`MercifulPolluter` に `magic_quotes_gpc` の機能を持たせました。

```php
// $_GET['body'] = "L'Arc-en-Ciel";

$request = new Gongo\MercifulPolluter\Request;
$request->enableMagicQuotesGpc();
$request->pollute();

var_dump($_GET['body'], $body);
// string(14) "L\'Arc-en-Ciel"
// string(14) "L\'Arc-en-Ciel"
```

`pollute()` を呼び出す前に `enableMagicQuotesGpc()` を実行しておくと、`$_GET`、`$_POST`、`$_COOKIE` およびそれらが注入されたグローバル変数に `addslashes()` が適用されます。

## おまけ

MercifulPolluter というよりは、本家 `register_globals` がどういった仕様かについて軽く補足します。

### :pizza: 同じキーが使われている場合どれが優先されるのか

例えば `$_COOKIE['foo']` と `$_GET['foo']` があったとして、どの値が `$foo` になるのか(どれが優先されるのか)ということについては、本家 `register_globals` と同じく [variables_order](http://php.net/variables_order) に影響されます。

> 非推奨のディレクティブ register_globals が on になっていると、 variables_order の設定は、 ENV、 GET、POST、 COOKIE および SERVER の各変数がグローバルスコープに取り込まれる順番も左右します。

### :pizza: $_FILES がグローバル変数に注入される時の注意

フォームからファイルアップロードした時に生成される `$_FILES` というスーパーグローバル変数も、もちろん `register_globals` の効果でグローバルに注入されます。ですが `$_FILES` だけは他のスーパーグローバル変数と少し違う名前付けが行われます。

ファイルアップロードされた時の `$_FILES` は、基本的にこんな形になっていると思います。

```php
$_FILES['upload']['tmp_name'] = '/tmp/qwsedrftgyhuji';
$_FILES['upload']['name'] = 'memo.txt';
$_FILES['upload']['size'] = 123;
$_FILES['upload']['type'] = 'text/plain';
```

これをグローバル変数に注入する時、

- `$_FILES['upload']['tmp_name']` → `$upload`
- `$_FILES['upload'][xxx]` → `$upload_xxx`
    - 例えば `$_FILES['upload']['size']` であれば `$upload_size`

というグローバス変数名になります。`tmp_name` だけ特別扱いです。注意しましょう。

## まとめ

[PHP 5.3.29 のソースコード](https://github.com/php/php-src/tree/php-5.3.29) を読んだりしてなるべく本来の機能と同等な効果が得られるようになっていると思いますが、やはり抜けはあるかもしれません。その際は [issue](https://github.com/gongo/merciful-polluter/issues) にでもご報告頂ければと思います。

できればこんなライブラリが誰にも使われない方がいい。そんな未来にしていきましょう。

## 参考

- http://php.net/manual/ja/ini.core.php#ini.register-globals
- [PHP における register_globals と $_FILES の関係について - Thanks Driven Life](http://gongo.hatenablog.com/entry/2014/10/02/211520)
- [PHP 5.4 以上でも register_globals と生きていくために $_SESSION のことも忘れてはいけない - Thanks Driven Life](http://gongo.hatenablog.com/entry/2014/12/24/132841)
- [php-5.3.29/main/php_variables.c](https://github.com/php/php-src/blob/php-5.3.29/main/php_variables.c)
    - `register_globals` のメインというべき場所