駒形電産

PHP 4.4.0 Released

The PHP Development Team would like to announce the immediate release of PHP 4.4.0. This is a maintenance release that addresses a serious memory corruption problem within PHP concerning references. If references were used in a wrong way, PHP would often create memory corruptions which would not always surface and be visible. The increased middle digit was required because the fix that corrected the problem with references changed PHP’s internal API. PHP 4.4.0 does not have any new features, and is solely a bugfix release.

PHP 4.4.0がリリース。バグ修正だそうです。

PHP: PHP 4 ChangeLog

Fixed bug #32685, Fixed bug #29423 (Segfault when using assignment by reference within function).

ChangeLogではこの「関数内で参照代入するとsegfault」ってやつが一番でかそうです。

・・・ぐらいに思ってたのにMojavi2動かすと、

PHP Notice:  Only variable references should be returned by reference(略
PHP Notice:  Only variable references should be returned by reference(略
PHP Notice:  Only variable references should be returned by reference(略
PHP Notice:  Only variable references should be returned by reference(略

mojavi-all-classes.phpでNotice出まくり。原因はまさにコレ。参照返ししてるのに値で代入したり、値返ししてるのに参照代入すると注意されるようになったためです。たしかに注意してくれるとありがたいですが、Mojavi2のエラーハンドリングの仕組みが動く前(Controller::dispatchの中でset_error_handler)なので止めようが無し。

とにかく困ってる人はこうすればとりあえずは凌げるっぽいです。

mojavi-all-classes.php

// this is an uncommented, auto-generated file
// for full source, please view the Mojavi lib directory

//error_reporting(E_ALL);
error_reporting(E_ALL & ~E_NOTICE);

あとでがんばってNOTICE元を全部修正したパッチを作りたいです。

2006/03/07追記: 修正版はこちら

引き続きセッションIDかぶっちゃう問題

p0t: PHPのセッションID3

一度session_id()を取得するんじゃなくて、

session_id(md5($_SERVER[‘SERVER_ADDR’].uniqid(rand(),1)));

とかやるのはどうですか?
某所でこんなコードを書いたことがあって、正解かどうか激しく気になります。正解でしょうか?教えてください。

あと、$_SERVERの値って、php.iniやらの設定で取れないことあるよね。そんな時どうしよう。どうしよう・・・

投稿者 kかべ : 2005年07月12日 14:16

kかべさんのこの案が素敵な感じがします。
調べてみるとセッションID発行md5(uniqid(””””,1))はほぼ等価っぽいので正解なのではないでしょうか!

php-4.3.11/ext/session/session.c

sprintf(buf, "%ld%ld%0.8f", tv.tv_sec, tv.tv_usec, php_combined_lcg(TSRMLS_C) * 10);
PHP_MD5Update(&context, buf, strlen(buf));

php-4.3.11/ext/standard/uniqid.c

if (more_entropy) {
    sprintf(uniqid, "%s%08x%05x%.8f", prefix, sec, usec, php_combined_lcg(TSRMLS_C) * 10);
} else {
    sprintf(uniqid, "%s%08x%05x", prefix, sec, usec);
}

$_SERVERはどういうとき使えないんでしたっけ。その場合は、どうしよう・・・。

ref: PHPのセッションID3

複数Webサーバ、単一セッション管理でセッションかぶっちゃう問題ですが、調べたら5人日ぐらいかかりそう・・・とか思ってましたが、この間の勉強会で聞いたら、

「session_id()でセッションID設定できる。」

一瞬で解決しました。

解決策をまとめると、

「セッション維持してくれるロードバランサーを使う」 OR 「セッションIDにアドレスをくっ付ける」

となりました。
実装はこんな感じか!?

session_id(str_replace(".", "a", $_SERVER["SERVER_ADDR"]).session_id());

Mojavi2で使うためにIPでユニークなセッションIDを使うコンテナ、IpUniqueSessionContainerを作ってみました。

<?php
require_once (USER_DIR."SessionContainer.class.php");

class IpUniqueSessionContainer extends SessionContainer {
    function load(& $authenticated, & $attributes, & $secure) {
        if (ini_get("session.auto_start") != 1) session_start();
        session_id(str_replace(".", "a", $_SERVER["SERVER_ADDR"]).session_id());
        if (!isset($_SESSION["attributes"])) {
            $authenticated = false;
            $attributes = array();
            $secure = array();
        } else {
            $authenticated = $_SESSION["authenticated"];
            $attributes = $_SESSION["attributes"];
            $secure = $_SESSION["secure"];
        }
        $_SESSION["authenticated"] =& $authenticated;
        $_SESSION["attributes"] =& $attributes;
        $_SESSION["secure"] =& $secure;
    }
}
?>

IpUniqueSessionContainer-0.1.zip

index.phpのUSER CONTAINER SETTINGSのところでこんな感じで使う。

require_once(BASE_DIR."opt/user/IpUniqueSessionContainer.class.php");
$user =& $controller->getUser();
$user->setContainer(new IpUniqueSessionContainer());

ref: PHPのセッションID2

この間のおざきさんの話がすごい気になってたんですが、対策できました!

Mojaviのすべての基本となるSCRIPT_PATH定数をこんな感じで設定してやれば、この間つくったURIUtil::redirect()もSmartyプラグインのmojavi_uriも絶対URIを吐いてくれます。

/**
* An absolute web path to the index.php script.
*/
define('SCRIPT_PATH', strtolower(trim(array_shift(split("/", $_SERVER["SERVER_PROTOCOL"]))))."://".$_SERVER["HTTP_HOST"].dirname($_SERVER["PHP_SELF"])."/index.php");

きもちー。

ref: リダイレクト面倒

2005/07/08追記:

こっちの方がよい(via MugeSo)

/**
* An absolute web path to the index.php script.
*/
define('SCRIPT_PATH', strtolower(trim(array_shift(split("/", $_SERVER["SERVER_PROTOCOL"]))))."://".$_SERVER["HTTP_HOST"].$_SERVER["PHP_SELF"]);

CSRF(クロスサイトリクエストフォージェリ)の対策を自分の中で決めてみましたが、根本的に間違った理解をしてることがわかりました

CSRFはそのユーザ自身(のクライアント)がアクセスする(させる)ことを利用する攻撃なんですな!

高木浩光さんの記事(via ファイさん)を元にPHPでのサンプルを作ってみました。

ログインページ(index.php):

<?php
session_start();
?>
<?="<?xml version=\"1.0\" encoding=\"utf-8\"?>"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<title>ログイン</title>
</head>
<body>
<h1>ログイン</h1>
<p>ログインしました。</p>
<p><a href="confirm.php">退会</a></p>
</body>
</html>

確認ページ(confirm.php):

<php
session_start();
?>
<?="<?xml version=\"1.0\" encoding=\"utf-8\"?>"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<title>退会処理 確認画面</title>
</head>
<body>
<h1>退会処理 確認画面</h1>
<p>本当に退会しますか?</p>
<form method="post" action="resign.php">
<strong>&lt;input type="hidden" name="sessionid" value="&lt;?=session_id()?&gt;" /&gt;</strong>
&lt;input type="submit" value="退会する" /&gt;
&lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;

処理ページ(resign.php):

&lt;?php
session_start();
if (<strong>session_id() == $_POST["sessionid"]</strong>) {
    $message = "退会しました。";
} else {
    $message = "退会・・・するかよ!";    
}
?&gt;
&lt;?="&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;"?&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;meta http-equiv="Content-Style-Type" content="text/css" /&gt;
&lt;meta http-equiv="Content-Script-Type" content="text/javascript" /&gt;
&lt;title&gt;退会処理 完了画面&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;退会処理 完了画面&lt;/h1&gt;
&lt;p&gt;&lt;?=$message?&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;

動いてるサンプル:http://p0t.jp/anti-csrf/

ソース:anti-csrf-0.2.zip

外部サイトでは(本人がアクセスしたとしても)対象サイトのクッキーを取ることができないっていうのがミソ・・・だと思う。
あってるのかどうか気になります!

それほど面倒ではないのでコンシューマサイトは全部これで行くことにします!

ref: CSRF対策俺ルール

2005/07/08追記:

$_COOKIE[””PHPSESSID”“] から session_id() に変更しました。(via ishinao

PEAR :: PEPr :: Details :: Linux_Debian_Apt

This package works as a wrapper of the famous apt utilities under Debian. It lets user search for packages or files, it uses packages.debian.org to fetch the content and return it in nice array structs.

PEPrにaptのラッパ。
素敵だけどProposal通らなそう!

PECL :: Package :: ssh2

Provides bindings to the functions of libssh2 which implements the SSH2 protocol. libssh2 is available from http://www.sourceforge.net/projects/libssh2

libssh2のPHPバインディング。これ知っておくと仕事で要件を意外な方法で片付けたりできる場面がある・・・気がする!

Trackbackサーバを書いたんですがXMLがちょっと面倒くさい。
そこでRESTより簡単なRPCを考えてみました。

サーバ:

&lt;?php
echo var_export(array("foo", "bar", "buz"));
?&gt;

クライアント:

&lt;?php
function var_export_rpc($uri) { return eval('return '.file_get_contents($uri).';'); }
var_dump(var_export_rpc("http://localhost/workspace/samples/var_export_rpc_server.php"));
?&gt;

VAR_EXPORT_RPC

XMLじゃなくてPHPコードを返す。
クライアントがPHPじゃないと死ぬほど面倒なことになる方式です。(Webサービスの利点台無し)
あぁ、しかもオブジェクト渡せない・・・。

この間やった内部勉強会の資料をアップしました。

基礎Mojavi(2) ~ValidationとLogging~

基礎Mojavi(2)サンプルアプリ

この間の続きを面倒臭いながら実装中。

Accept-Languageヘッダフィールドの書式はRFC2616のSection14で定義されてるようです。

[RFC 2616] Hypertext Transfer Protocol—HTTP/1.1

Each language-range MAY be given an associated quality value which represents an estimate of the user’s preference for the languages specified by that range. The quality value defaults to ””q=1””. For example,

Accept-Language: da, en-gb;q=0.8, en;q=0.7

なるほど。これにそってgetLocales, getLocaleを実装してみました。

&lt;?php
class RequestUtil {
    function getLocale() {
        return array_shift(RequestUtil::getLocales());
    }

    function getLocales() {
        foreach (split(",", $_SERVER["HTTP_ACCEPT_LANGUAGE"]) as $value) {
            list($name, $pri) = split(";", trim($value));
            $num = preg_replace("/^q=/", "", $pri);
            $langs[($num ? $num : 1)] = $name;
        }
        arsort($langs);
        foreach ($langs as $value) {
            $locales[] = $value;
        }
        return $locales;
    }    
}
?&gt;

Firefoxでは仕様通りこんなのが来るのでちゃんと取れます。

ja,en-us;q=0.7,en;q=0.3

IEではこんな感じ。

ja

quality valueを省略した場合は1ということになっているのでこれも問題なさそう。

でもOperaのこれは間違ってないのかな

ja, en

両方1だとどちらを選んでいいのかわからない!
現在の実装だと上記の場合、getLocale()はenが返って来てしまいます。かといって左側優先なんてルールを勝手に実装したくない。

・・・と書いていたらyoshukiさんから情報が。

[RFC 3282] Content Language Headers

The syntax and semantics of language-range is defined in [TAGS]. The Accept-Language header may list several language-ranges in a comma- separated list, and each may include a quality value Q. If no Q values are given, the language-ranges are given in priority order, with the leftmost language-range being the most preferred language; this is an extension to the HTTP/1.1 rules, but matches current practice.

If Q values are given, refer to HTTP/1.1 [RFC 2616] for the details on how to evaluate it.

Accept-LanguageヘッダはRFC3282で再定義されていてquality valueが無い場合は左優先となっているそうです!

さっそくさっきのヤツを左優先に修正。

&lt;?php
class RequestUtil {
    function getLocale() {
        return array_shift(RequestUtil::getLocales());
    }

    function getLocales() {
        foreach (<strong>array_reverse(</strong>split(",", $_SERVER["HTTP_ACCEPT_LANGUAGE"])<strong>)</strong> as $value) {
            list($name, $pri) = split(";", trim($value));
            $num = preg_replace("/^q=/", "", $pri);
            $langs[($num ? $num : 1)] = $name;
        }
        arsort($langs);
        foreach ($langs as $value) {
            $locales[] = $value;
        }
        return $locales;
    }    
}
?&gt;

これで安心です。