Weitere ähnliche Inhalte Ähnlich wie お前は PHP の歴史的な理由の数を覚えているのか (20) Mehr von Kousuke Ebihara (9) Kürzlich hochgeladen (11) お前は PHP の歴史的な理由の数を覚えているのか5. 自己紹介
• Kousuke Ebihara (海老原昂輔) a.k.a. @co3k
• 株式会社 VOYAGE GROUP (2014/02 より)
• スマホコミュケーション事業室で Python 書いてます
• そういえば PHP 全然書いてないです
• セキュリティ周り
• 以前は某 OSS の SNS エンジンとかその辺やってました
7. 調査方法
• PHP 4 以降については普通に Git で潜っていく
• php-src : 公式の Git リポジトリ
• ドキュメント: git-svn で公式のリポジトリを変換
• PHP 3 以前
• museum.php.net (かなり重いので注意)
• ML
• 1996/07 - 1998/01: PHP/FI Mailing List
• 1996/12 以降: php-internals (marc.info なら旧 php-dev 時代も追える)
10. implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
********************** SNIP *********************!
} else {!
if (Z_TYPE_PP(arg1) == IS_ARRAY) {!
arr = *arg1;!
convert_to_string_ex(arg2);!
delim = *arg2;!
} else if (Z_TYPE_PP(arg2) == IS_ARRAY) {!
arr = *arg2;!
convert_to_string_ex(arg1);!
delim = *arg1;!
} else {!
********************** SNIP *********************!
}!
}!
!
php_implode(delim, arr, return_value TSRMLS_CC);
11. implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
********************** SNIP *********************!
} else {!
if (Z_TYPE_PP(arg1) == IS_ARRAY) {!
arr = *arg1;!
convert_to_string_ex(arg2);!
delim = *arg2;!
} else if (Z_TYPE_PP(arg2) == IS_ARRAY) {!
arr = *arg2;!
convert_to_string_ex(arg1);!
delim = *arg1;!
} else {!
********************** SNIP *********************!
}!
}!
!
php_implode(delim, arr, return_value TSRMLS_CC);
第 2 引数が指定されている
12. implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
********************** SNIP *********************!
} else {!
if (Z_TYPE_PP(arg1) == IS_ARRAY) {!
arr = *arg1;!
convert_to_string_ex(arg2);!
delim = *arg2;!
} else if (Z_TYPE_PP(arg2) == IS_ARRAY) {!
arr = *arg2;!
convert_to_string_ex(arg1);!
delim = *arg1;!
} else {!
********************** SNIP *********************!
}!
}!
!
php_implode(delim, arr, return_value TSRMLS_CC);
第 2 引数が指定されている
配列が第 1 引数に指定され
ていればデリミタは第 2 引数
13. implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
********************** SNIP *********************!
} else {!
if (Z_TYPE_PP(arg1) == IS_ARRAY) {!
arr = *arg1;!
convert_to_string_ex(arg2);!
delim = *arg2;!
} else if (Z_TYPE_PP(arg2) == IS_ARRAY) {!
arr = *arg2;!
convert_to_string_ex(arg1);!
delim = *arg1;!
} else {!
********************** SNIP *********************!
}!
}!
!
php_implode(delim, arr, return_value TSRMLS_CC);
第 2 引数が指定されている
配列が第 1 引数に指定され
ていればデリミタは第 2 引数
配列が第 2 引数に指定され
ていればデリミタは第 1 引数
14. implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
********************** SNIP *********************!
} else {!
if (Z_TYPE_PP(arg1) == IS_ARRAY) {!
arr = *arg1;!
convert_to_string_ex(arg2);!
delim = *arg2;!
} else if (Z_TYPE_PP(arg2) == IS_ARRAY) {!
arr = *arg2;!
convert_to_string_ex(arg1);!
delim = *arg1;!
} else {!
********************** SNIP *********************!
}!
}!
!
php_implode(delim, arr, return_value TSRMLS_CC);
第 2 引数が指定されている
配列が第 1 引数に指定され
ていればデリミタは第 2 引数
配列が第 2 引数に指定され
ていればデリミタは第 1 引数
配列が指定されていなければ
エラー
15. implode() の歴史
• PHP/FI 2 には implode() も explode() も存在しない
• PHP 3 にはある
• PHP 3 時点では implode() は現在と同じ挙動に
• つまりこの「歴史的な理由」は PHP/FI 2 → PHP 3 の開
発中に生まれたものと思われる
16. PHP 3.0a3
(November 23 1997)
• Switched between the 1st and 2nd parameters to
explode(), so that it acts like split()
(拙訳: explode() の第 1 引数と第 2 引数を交換したの
で、 split() と同じように動作するようになりました)
19. PHP 3.0b5
(February 24 1998)
• Made implode() accept arguments in the order
used by explode() as well
(拙訳: implode() が explode() で使われているような引
数順も受け付けるようにしました)
20. explode() と implode() に
何が起こったか
• PHP 3.0 開発中に explode(), implode(), split() が追加された
• このタイミングで追加、変更された機能は多いので当時の CHANGELOG を眺めているだけでも結構楽しい
• PHP 3.0a3 にて、 explode() の引数順を split() に合わせた
• この結果、 implode() との統一性が取れなくなったので、PHP
3.0b5 にて、 implode() では両方の引数順を受け付けるようにした
• explode() が据え置きだったのは、引数が両方とも文字列型だから?
• わずか 3 ヶ月の「歴史」
22. URL エンコード用の 2 つの関数
• urlencode()
• 文字列を URL エンコード
• rawurlencode()
• 文字列を URL エンコード
23. URL エンコード用の 2 つの関数
• urlencode()
• 文字列を URL エンコード
• rawurlencode()
• 文字列を URL エンコード
(RFC 3986 に基づかない)
24. URL エンコード用の 2 つの関数
• urlencode()
• 文字列を URL エンコード
• rawurlencode()
• 文字列を URL エンコード
(RFC 3986 に基づかない)
(RFC 3986 に基づく)
25. どのような違いがあるか?
• urlencode()
• 空白 (U+0020) を + (U+003B) に置き換える
• ~ (U+007E) をエンコードする (RFC 1738 に基づく)
• rawurlencode()
• 空白 (U+0020) をパーセントエンコードする
• ~ (U+007E) をエンコードしない (RFC 3986 に基づく)
26. どのような違いがあるか?
• urlencode()
• 空白 (U+0020) を + (U+003B) に置き換える
• ~ (U+007E) をエンコードする (RFC 1738 に基づく)
• rawurlencode()
• 空白 (U+0020) をパーセントエンコードする
• ~ (U+007E) をエンコードしない (RFC 3986 に基づく)
追従漏れじゃね……?
28. urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
********************** SNIP *********************!
while (from < end) {!
c = *from++;!
!
if (c == ' ') {!
*to++ = '+';!
} else if ((c < '0' && c != '-' && c != '.') ||!
(c < 'A' && c > '9') ||!
(c > 'Z' && c < 'a' && c != '_') ||!
(c > 'z')) {!
to[0] = '%';!
to[1] = hexchars[c >> 4];!
to[2] = hexchars[c & 15];!
to += 3;!
} else {!
*to++ = c;!
}!
}
29. urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
********************** SNIP *********************!
while (from < end) {!
c = *from++;!
!
if (c == ' ') {!
*to++ = '+';!
} else if ((c < '0' && c != '-' && c != '.') ||!
(c < 'A' && c > '9') ||!
(c > 'Z' && c < 'a' && c != '_') ||!
(c > 'z')) {!
to[0] = '%';!
to[1] = hexchars[c >> 4];!
to[2] = hexchars[c & 15];!
to += 3;!
} else {!
*to++ = c;!
}!
}
while ループで文字列終端まで from
を 1 文字ずつ走査
30. urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
********************** SNIP *********************!
while (from < end) {!
c = *from++;!
!
if (c == ' ') {!
*to++ = '+';!
} else if ((c < '0' && c != '-' && c != '.') ||!
(c < 'A' && c > '9') ||!
(c > 'Z' && c < 'a' && c != '_') ||!
(c > 'z')) {!
to[0] = '%';!
to[1] = hexchars[c >> 4];!
to[2] = hexchars[c & 15];!
to += 3;!
} else {!
*to++ = c;!
}!
}
while ループで文字列終端まで from
を 1 文字ずつ走査
スペースを + に置換して to に格納
31. urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
********************** SNIP *********************!
while (from < end) {!
c = *from++;!
!
if (c == ' ') {!
*to++ = '+';!
} else if ((c < '0' && c != '-' && c != '.') ||!
(c < 'A' && c > '9') ||!
(c > 'Z' && c < 'a' && c != '_') ||!
(c > 'z')) {!
to[0] = '%';!
to[1] = hexchars[c >> 4];!
to[2] = hexchars[c & 15];!
to += 3;!
} else {!
*to++ = c;!
}!
}
while ループで文字列終端まで from
を 1 文字ずつ走査
スペースを + に置換して to に格納
それ以外のエンコード対象の文字は
パーセントエンコードして to に格納
32. urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
********************** SNIP *********************!
while (from < end) {!
c = *from++;!
!
if (c == ' ') {!
*to++ = '+';!
} else if ((c < '0' && c != '-' && c != '.') ||!
(c < 'A' && c > '9') ||!
(c > 'Z' && c < 'a' && c != '_') ||!
(c > 'z')) {!
to[0] = '%';!
to[1] = hexchars[c >> 4];!
to[2] = hexchars[c & 15];!
to += 3;!
} else {!
*to++ = c;!
}!
}
while ループで文字列終端まで from
を 1 文字ずつ走査
スペースを + に置換して to に格納
それ以外のエンコード対象の文字は
パーセントエンコードして to に格納
エンコードしない文字はそのまま
to に格納
33. PHPAPI char *php_raw_url_encode(char const *s, int len, int
*new_length)!
{!
register int x, y;!
unsigned char *str;!
!
str = (unsigned char *) safe_emalloc(3, len, 1);!
for (x = 0, y = 0; len--; x++, y++) {!
str[y] = (unsigned char) s[x];!
if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||!
(str[y] < 'A' && str[y] > '9') ||!
(str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||!
(str[y] > 'z' && str[y] != '~')) {!
str[y++] = '%';!
str[y++] = hexchars[(unsigned char) s[x] >> 4];!
str[y] = hexchars[(unsigned char) s[x] & 15];!
}!
}
rawurlencode() の実装
(EBCDIC モード時の処理は省略)
34. PHPAPI char *php_raw_url_encode(char const *s, int len, int
*new_length)!
{!
register int x, y;!
unsigned char *str;!
!
str = (unsigned char *) safe_emalloc(3, len, 1);!
for (x = 0, y = 0; len--; x++, y++) {!
str[y] = (unsigned char) s[x];!
if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||!
(str[y] < 'A' && str[y] > '9') ||!
(str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||!
(str[y] > 'z' && str[y] != '~')) {!
str[y++] = '%';!
str[y++] = hexchars[(unsigned char) s[x] >> 4];!
str[y] = hexchars[(unsigned char) s[x] & 15];!
}!
}
rawurlencode() の実装
(EBCDIC モード時の処理は省略)
for ループで文字列終端まで 1 文字
ずつ走査
35. PHPAPI char *php_raw_url_encode(char const *s, int len, int
*new_length)!
{!
register int x, y;!
unsigned char *str;!
!
str = (unsigned char *) safe_emalloc(3, len, 1);!
for (x = 0, y = 0; len--; x++, y++) {!
str[y] = (unsigned char) s[x];!
if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||!
(str[y] < 'A' && str[y] > '9') ||!
(str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||!
(str[y] > 'z' && str[y] != '~')) {!
str[y++] = '%';!
str[y++] = hexchars[(unsigned char) s[x] >> 4];!
str[y] = hexchars[(unsigned char) s[x] & 15];!
}!
}
rawurlencode() の実装
(EBCDIC モード時の処理は省略)
for ループで文字列終端まで 1 文字
ずつ走査
とりあえず str に文字を格納
36. PHPAPI char *php_raw_url_encode(char const *s, int len, int
*new_length)!
{!
register int x, y;!
unsigned char *str;!
!
str = (unsigned char *) safe_emalloc(3, len, 1);!
for (x = 0, y = 0; len--; x++, y++) {!
str[y] = (unsigned char) s[x];!
if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||!
(str[y] < 'A' && str[y] > '9') ||!
(str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||!
(str[y] > 'z' && str[y] != '~')) {!
str[y++] = '%';!
str[y++] = hexchars[(unsigned char) s[x] >> 4];!
str[y] = hexchars[(unsigned char) s[x] & 15];!
}!
}
rawurlencode() の実装
(EBCDIC モード時の処理は省略)
for ループで文字列終端まで 1 文字
ずつ走査
エンコード対象の文字ならパーセントエンコード
とりあえず str に文字を格納
39. なぜ空白を + に置き換えるか
技術/HTTP/URLエンコードで 0x20(スペース) を "+" にすべきか "%20" にすべきか - Glamenv-Septzen.net
http://www.glamenv-septzen.net/view/1170
※PHP に関する記述 (rawurlencode() が用意された経緯など) は若干事実と異なる部分がある。本スライドで詳述
40. なぜ空白を + に置き換えるか
• application/x-www-form-urlencoded のため
• W3C の規格 (たとえば HTML 5) などに含まれる (単独の
規格は存在しない)
• form が submit された場合のレスポンスボディのエンコー
ド方式
• 空白を + に置き換えるほかはだいたいパーセントエンコード
41. 他の言語の状況
• Python 2 (Python 3 では urllib.parse)
• RFC 3986 の URL エンコード: urllib.quote()
• application/x-www-form-urlencoded: urllib.quote_plus()
• Ruby
• RFC 3986 の URL エンコード: ERB::Util.u(), URI.encode()
• application/x-www-form-urlencoded: URI.encode_www_form(),
CGI.escape()
• ただし URI.encode() は obsolete で、 ERB::Util.u() とかが代替となっている模
様
42. urlencode() の歴史
• PHP/FI 2.0 から存在
• この当時から空白 (U+0020) を + (U+003B) に置き
換える実装になっていた
char *php_urlencode(char *s) {!
********************** SNIP *********************!
for(x=0,y=0; s[x]; x++,y++) {!
str[y] = s[x];!
if(str[y]==' ') {!
str[y]='+';
43. rawurlencode() の歴史
• PHP 3.0b3 から存在 (当時は RFC 1738 ベース)
• まーた PHP 3.0 か!
• RFC 3986 は 2005 年 1 月
• PHP 3.0 は 1999 年 (PHP 3.0b3 は 1998 年)
• PHP 5.0 のタイミングで RFC 3986 ベースに変更
44. rawurlencode() 誕生秘話
(序章)
• 1997/06/16 (PHP/FI 2.0b12)
• UrlEncode() がスペースを + に置換するようになる
• 1997/11/12 (PHP/FI 2.0)
• UrlEncode() が / をエンコードしないようになる (後に撤
回)
• URL 文字列全体のエンコードを意図した
議論のスレッドは http://marc.info/?t=90279138700001&r=1&w=4 http://marc.info/?t=90279138700002&r=1&w=2
45. rawurlencode() 誕生秘話
(事件篇)
• 1998/01/15 頃 (PHP 3.0b3 開発中)
• Jaakko Hyvätti が urlencode() で & をエンコードしないように変
更? (意図の説明を user ML にポストしたようだが入手できず)
• PHP/FI 2.0 での変更の意図に合わせたもの
• おそらくここで rawurlencode() と formencode() (おそらくリリー
ス前に削除) が入ったと思われる ([PHP-DEV] ML で my
rawurlencode() などと説明しているので)
• PHP 3.0b3-dev の urlencode() が壊れたと報告がくる
議論のスレッドは http://marc.info/?t=90279138700001&r=1&w=4 http://marc.info/?t=90279138700002&r=1&w=2
46. rawurlencode() 誕生秘話
(解決篇)
• 1998/01/16
• PHP 2.0 の変更が不適切ということになり、スペースを + に置換する版まで戻される
• ちなみに Jaakko は「rawurl*code() っていい名前ない?�urlpath*code() とか?」
とも言ってるが Rasmus 華麗にこれをスルー
• スペースを + にする件も戻した方がいいのでは、という Jaakko の提案に Rasmus
は反対
• 「スペースが + になっていれば urldecode() なしで POST data が使える」、
「URL 中の + は自動的にスペースにデコードされる」(= つまり + なら URL と
POST data どっちもいける)
• 既存のコードが壊れる
議論のスレッドは http://marc.info/?t=90279138700001&r=1&w=4 http://marc.info/?t=90279138700002&r=1&w=2
47. urlencode() と rawurlencode()
についてのまとめ
• URL 中の文字列のエンコードをおこなう際は、
rawurlencode() を使ったほうがよい
• urlencode() を使うべき場面は滅多にない
• 名前的に urlencode() の方が正しそうなので多用されがち
だが……
• 自力で POST データのエンコードをしたい場合くらい
• そもそも RFC�3986 にも追従していない
48. 問題のあった事例
• Bug(バグ) #3383: mail_to 関数を用いるときに空白が
+ に変換されてしまう - OpenPNE 3
https://redmine.openpne.jp/issues/3383
• ここで気がついた (symfony の link_to() は
urlencode() を使っている)
• 空白を意図して渡していても + をスペースに展開しない
メーラがあったと思われる
52. float / double 型の歴史
• PHP/FI 2.0 (1997/11/12) : double
• PHP 3.0 (1998/06/06) : double (float, real でもキャスト
できるように)
• PHP 4.1.0 (2001/12/10): float……?
• 2c275bf793f70ad2a38bbf4a0f7ad12fecaca095,
03f7406711d3706af0f237e1ea03974616dd2139
など
53. なんで double -> float に
なったか
• 不明……
• ML では議論されてない?
• 突如として float 派が出現したように見える [要出典]
• 無理に double を float に置換する必要があったのかどう
か疑問
54. float 派の闘いの記録
• Hartmut Holzgraefe
• double -> float への置換をおこなった最初の人物
• 多くの double -> float の置換に貢献
• Jeroen van Wolffelaar
• ドキュメントに存在するほとんどの double を float に置換した
• Gabor Hojtsy
55. float 派の登場
• Hartmut Holzgraefe による 2001/09/21 のコミット
(2c275bf7) で、 floatval(), is_float() のエイリアスとし
て doubleval(), is_double() を使うように変更 (それまで
は逆)
• さらに (03f74067) で関数定義部分のコメント (返り値や
引数型などが書かれている) の double を float に置換
60. ドキュメントの置換も忘れない
• r53773 (2001/08/07) by Jeroen van Wolffelaar
• r54456 (2001/08/12) by Jeroen van Wolffelaar
• r54918 (2001/08/14) by Jeroen van Wolffelaar
• r57972 (2001/09/21) by Hartmut Holzgraefe
• r57997 (2001/09/21) by Jeroen van Wolffelaar
• r57999 (2001/09/21) by Jeroen van Wolffelaar
73. Phar アーカイブのマニフェスト
(composer.phar (1.0.0-alpha8) の例)
0x17F-82 : マニフェストの長さ (26489)
0x183-86 : ファイル数 (322)
0x187-88: API バージョン (1.1.0)
※最後の 4bit は未使用
0x189-8C : ビットマップフラグ
(0x00010000 : この Phar には検証用シグネチャが含まれる)
0x18D-90 : この Phar のエイリアスの長さ (13)
0x191-9D : エイリアス (composer.phar)
74. バージョン情報のみ
ビッグエンディアンである理由
• 3842b67 には元になった PHP_Archive に由来する理由とある
• PHP_Archive の最新の実装もバージョン情報だけビッグエンディアンで格納している
(PHP_Archive_Creator::serializeManifest())
• PHP_Archive の 2f41f8f48 ではアーカイブ作成時にビッグエンディアンで格納
しているが、展開時にはリトルエンディアンでパースしようとしている
• PHP_Archive の 8931abf6 で展開時にもビッグエンディアンでパースするよう修
正された
• つまり、間違えてビッグエンディアンで格納してしまったために後に引けなくなった
のでは……
76. Zend Engine の HashTable
API における歴史的な理由
• 「hash exists for historical reasons and is always
ignored」
(拙訳: 引数 hash は歴史的な理由のために存在し、常に
無視される)
77. Zend Engine の HashTable
API における歴史的な理由
• 時間切れで追い切れず
• まあなんとなくわかる
78. まとめ
• 身近な機能とかを深追いしていくのは結構楽しい
• PHP 3.0 時代のチェンジログと ML は本当にオススメ
• たとえば「昔の PHP は + 演算子で文字列結合できたんだよー」と
か無駄知識を披露してドヤ顔できる
• 5 個がっつり深追いしていくだけでも結構時間が埋まるので助かった
• Phar とか Zend Engine 周りの歴史的な理由が出てきたおかげで割
と闇 PHP っぽくなった気がする