June 2008
June 30, 2008
Apache - Basic Authentication
簡単に済ませたいとき、たまに使うBasic認証。「たまに」なのでいつも忘れる。
パスワードファイル作成。
パスワードファイル作成。
$ htpasswd -c /usr/local/www/data/myapp/config/.htpasswd username New password: [enter password] Re-type new password: [enter password] Adding password for user usernamehttp.confに設定を追加する。
<Directory "/usr/local/www/data/myapp/">
AuthType Basic
AuthName "Private Area"
AuthUserFile /usr/local/www/data/myapp/config/.htpasswd
Require valid-user
</Directory>
June 26, 2008
Quoted-Printable Encode/Decode
phpにquoted_printable_decode()関数はあるけど、quoted_printable_encode()関数はないので、エンコードは次のように行う(mb拡張があればmb_encode_mimeheader()でQエンコーディング使えば良い)。
変換したりしなかったりなので、ヘッダでもボディでも、とにかくアンダースコアは=5Fに変換しておけば良い。
$fp = fopen("php://temp", "r+");
stream_filter_appand($fp, "convert.quoted-printable-encode",
STREAM_FILTER_READ,
array("line-length" => 74,
"line-break-chars" => "\r\n")
);
fputs($fp, $str);
rewind($fp);
$encoded = stream_get_contents($fp);
fclose($fp);
多くのMUAがヘッダに含まれるアンダースコアをスペースに変換する(ボディのアンダースコアはそのまま)。変換するMUAは、確認したところで、Outlook Express6, Thunderbird, Mail, Becky, Sylpheed, Eudora, Entourage。変換しないMUAはShuriken。変換したりしなかったりなので、ヘッダでもボディでも、とにかくアンダースコアは=5Fに変換しておけば良い。
$encoded = str_replace("_", "=5F", $encoded);
デコード時は、先程挙げた多くのMUAと同様、ヘッダの場合にのみアンダースコアをスペースに変換する。=5Fはquoted_printable_decode()関数がアンダースコアに変換するので、気にしなくて良い。
if ($isHeader) {
$str = str_replace("_", " ", $str);
}
$decoded = quoted_printable_decode($str);
June 25, 2008
response headers in the download script
日本語ファイル名のファイルをダウンロードさせる場合、少しブラウザ(ユーザエージェント)を気にする必要がある。
Safariはサーバ側でどうにもできないため、ダウンロードリンクの最後(ファイル名として扱われる)をファイル名をURLデコードしたものにする。
Safariはサーバ側でどうにもできないため、ダウンロードリンクの最後(ファイル名として扱われる)をファイル名をURLデコードしたものにする。
<a href="/dlscript/<?php echo urlencode($filename) ?>"> <?php echo htmlentities($filename, ENT_QUOTES) ?> </a>サーバ側では、IEの場合にヘッダに付加するファイル名をShift-JISに変換する。Safariの場合はヘッダにファイル名を付加しない(付加すると文字化けする)。
$filename = urldecode($filename);
$path = "/path/to/" . $filename;
header("Content-Type: application/octet-stream");
header("Content-Length: " . filesize($path));
$ua = (isset($_SERVER["HTTP_USER_AGENT"])) ? $_SERVER["HTTP_USER_AGENT"] : "unknown";
if (strpos($ua, "MSIE") !== false) {
$filename = mb_convert_encoding($filename, "SJIS", "UTF-8");
}
if (strpos($ua, "Safari") === false) {
header("Content-Disposition: attachment; filename="' . $filename . '"');
} else {
header("Content-Disposition: attachment");
}
echo file_get_contents($path);
以下のブラウザで文字化けすることなくダウンロードできることを確認。
- Windows: IE6, IE7, Firefox2, Opera9.?, Safari3
- Linux: Firefox2, Opera9.?
- Mac: Firefox3, Opera9.?, Safari3
June 20, 2008
mailtoを使用する際のスパム対策
HTMLでメールアドレスを素で書くとスパムロボットに拾われるため、JavaScriptを使ったスパム対策をする。
今回利用したスクリプトがこちらで、ほんの少し改造して使いやすくした。
使い方はCryptMailto.jsを読み込み、idを振った空のタグを用意し、popup_mailer()関数にアカウント名・ドメイン名・idを渡す。
例えばHTMLは次のようになる(ソース上にメールアドレスが現れない)。
今回利用したスクリプトがこちらで、ほんの少し改造して使いやすくした。
使い方はCryptMailto.jsを読み込み、idを振った空のタグを用意し、popup_mailer()関数にアカウント名・ドメイン名・idを渡す。
例えばHTMLは次のようになる(ソース上にメールアドレスが現れない)。
<p>
お問い合わせ先: <span id="info"></span>
</p>
<script type="text/javascript" src="/js/CryptMailto.js"></script>
<script type="text/javascript">
popup_mailer("info", "example.com", "info");
</script>
サンプルはこちらで。
June 15, 2008
Javascript - mouseover, mouseout
Javascriptでdivなどのmouseoverやmouseoutイベントを扱う際、そのdivが他の要素を内包していると厄介な挙動をする。
IEでは以前からonmouseenterやonmouseleaveがあるようで、これを使うとbやcは関係なく、aに入った時と出た時だけイベントが発生してくれる。
他のブラウザでも同様の振る舞いを実現するにはそれ用の実装をする必要があるが、Sabel JS(Sabel 1.1以降)だと以下のようにできる。
<div id="a" onmouseover="alert('over');" onmouseout="alert('out');">
<div id="b">
<div id="c"></div>
</div>
</div>
例えば上記のように、a内にbがあり、b内にcがある状態で、aのmouseover, mouseoutイベントでアラートを表示すると、マウスがb上に移動すると一度outし直後に再びoverする。c上に移動した時も同様。IEでは以前からonmouseenterやonmouseleaveがあるようで、これを使うとbやcは関係なく、aに入った時と出た時だけイベントが発生してくれる。
他のブラウザでも同様の振る舞いを実現するにはそれ用の実装をする必要があるが、Sabel JS(Sabel 1.1以降)だと以下のようにできる。
<div id="a">
<div id="b">
<div id="c"></div>
</div>
</div>
<script type="text/javascript">
var a = Sabel.get("a");
a.observe("mouseenter", function(){ alert('over'); });
a.observe("mouseleave", function(){ alert('out'); });
</script>
動作の様子はこちらで。June 13, 2008
mysql - skip-show-database
MySQLでのSHOW DATABASESコマンド権限の管理。
バージョンの表示と、テスト用データベース'sample'の作成。
SHOW PROCESSLISTやSHOW VARIABLESとかも禁止にするにはどうするんだろう。小一時間情報を探してみたけれど、わからなかった。できないのかもしれない。
バージョンの表示と、テスト用データベース'sample'の作成。
$ mysql -u root mysql> SELECT VERSION(); +------------+ | VERSION() | +------------+ | 5.0.51-log | +------------+ 1 row in set (0.00 sec) mysql> CREATE DATABASE sample; Query OK, 1 row affected (0.00 sec)fooユーザを作成し、SELECTのみ許可する。
$ mysql -u root
mysql> GRANT Select ON *.* TO foo@localhost;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM mysql.user WHERE `User` = 'foo'\G
*************************** 1. row ***************************
Host: localhost
User: foo
Password:
Select_priv: Y
Insert_priv: N
Update_priv: N
...
Show_db_priv: N
Super_priv: N
...
1 row in set (0.00 sec)
fooユーザにShow_dbの権限は無いはずなのに、SHOW DATABASESコマンドが実行でき、全てのデータベースが見れてしまう。
$ mysql -u foo mysql> SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | sample | | test | +--------------------+ 4 rows in set (0.00 sec)fooユーザを再作成し、'sample'データベースでのSELECTのみ許可する。
$ mysql -u root mysql> DELETE FROM mysql.user WHERE `User` = 'foo'; Query OK, 1 row affected (0.00 sec) mysql> FLUSH PRIVILEGES; Query OK, 0 rows affected (0.00 sec) mysql> GRANT Select ON sample.* TO foo@localhost; Query OK, 0 rows affected (0.00 sec)先程と同様fooユーザにShow_dbの権限は無く、SHOW DATABASESコマンドも実行できる。しかし、権限の無い'mysql'データベースは見えなくなる。('information_schema'や'test'が見えるのはよくわからない。)
$ mysql -u foo mysql> SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | sample | | test | +--------------------+ 3 rows in set (0.00 sec)次に、my.cnfに'skip-show-database'を追加し、mysqlを再起動する。
$ vi /path/to/my.cnf [mysqld] ... skip-show-database ...fooユーザでSHOW DATABASESコマンドを実行すると、拒否するようになったことが分かる。'skip-show-database'がONで、Show_db権限がない場合に、SHOW DATABASESコマンドは拒否される模様。
$ mysql -u foo mysql> SHOW DATABASES; ERROR 1227 (42000): Access denied; you need the SHOW DATABASES privilege for this operation確認のために、Show_db権限をfooユーザに与える。
$ mysql -u root mysql> UPDATE mysql.user SET Show_db_priv = 'Y' WHERE `User` = 'foo'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> FLUSH PRIVILEGES; Query OK, 0 rows affected (0.00 sec)SHOW DATABASESコマンドを実行すると、全データベースが表示される。
$ mysql -u foo mysql> SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | sample | | test | +--------------------+ 4 rows in set (0.00 sec)一般ユーザはSHOW DATABASESコマンドは必要ない(自分が使用するデータベースだけ知っていればいい)ので、'skip-show-database'は常にONにしておいたほうが良さそう。で、Show_db権限は与えないこと。
SHOW PROCESSLISTやSHOW VARIABLESとかも禁止にするにはどうするんだろう。小一時間情報を探してみたけれど、わからなかった。できないのかもしれない。
June 11, 2008
Thunderbird - Sorting messages by Thread
Thunderbirdでスレッド表示すると、同じ件名だと全く関係ないメールでもスレッド化してしまうので、それの対処。
以下の作業をする前に、Thunderbirdを閉じておく。
ホームディレクトリ以下のどこかにある、prefs.jsを変更する。
Windowsだと次のようなパスにある。
以下の作業をする前に、Thunderbirdを閉じておく。
ホームディレクトリ以下のどこかにある、prefs.jsを変更する。
Windowsだと次のようなパスにある。
C:\Documents and Settings\user\Application Data\Thunderbird\Profiles\xxxxx.default\prefs.jsLinuxだと次のようなパスにある。
/home/user/.thunderbird/xxxxx.default/prefs.jsprefs.jsに以下の一行を追加。
user_pref("mail.thread_without_re", false);
この"xxxxx.default"ディレクトリの下の、MailやImapMailディレクトリ内の*.msfファイル(インデックスファイル)を削除する。June 10, 2008
Postfix - pass a mail to the php script
ある特定のユーザ、もしくはドメイン宛のメールをPHPに渡す場合のPostfixの設定。
まず、transportの設定をする。全ユーザの場合はドメイン名だけで良い。
まず、transportの設定をする。全ユーザの場合はドメイン名だけで良い。
$ vi /etc/postfix/transport user@example.com phpscript: # example.com phpscript:main.cfでこのファイルを指定する。
$ vi /etc/postfix/transport transport_maps = hash:/etc/postfix/transportpostmapコマンドでtransport.dbを更新する。
$ /usr/sbin/postmap /etc/postfix/transportmaster.cfで"phpscript"サービスの設定をする。
$ vi /etc/postfix/master.cf phpscript unix - n n - - pipe flags= user=nobody argv=/path/to/php /path/to/script.phppostfixを再起動する。
$ /etc/init.d/postfix restartphpではstdinからメールのソースを取得できる。
$ vi /path/to/script.php
<?php
$mail = file_get_contents("php://stdin");
June 09, 2008
Use sequence in each database
MySQLはカラムをAUTO_INCREMENTで定義すれば、新規連番を振ってくれる。振られた連番はLAST_INSERT_ID()関数で取得できる。
CREATE TABLE foo
(
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(24) NOT NULL
)
-------------------------------------------
mysql_query("INSERT INTO foo(name) VALUES('test')", $conn);
$res = mysql_query("SELECT LAST_INSERT_ID() AS id", $conn);
$row = mysql_fetch_assoc($res);
var_dump($row["id"]);
PostgreSQLはカラムをSERIAL、またはBIGSERIALで定義すれば、新規連番を振ってくれる。振られた連番はLASTVAL()関数で取得できる。
CREATE TABLE foo
(
id SERIAL NOT NULL,
name VARCHAR(24) NOT NULL
)
-------------------------------------------
pg_query($conn, "INSERT INTO foo(name) VALUES('test')");
$res = pg_query($conn, "SELECT LASTVAL() AS id");
$row = pg_fetch_assoc($res);
var_dump($row["id"]);
SQLiteはカラムを"INTEGER PRIMARY KEY"または"INTEGER NOT NULL PRIMARY KEY"で定義すれば、新規連番を振ってくれる。振られた連番はPDOのlastInsertId()メソッドで取得できる。
CREATE TABLE foo
(
id INTEGER PRIMARY KEY,
name VARCHAR(24) NOT NULL
)
-------------------------------------------
$stmt = $pdo->prepare("INSERT INTO foo(name) VALUES('test')");
$stmt->execute();
var_dump($pdo->lastInsertId());
Firebirdではシーケンス(旧ジェネレータ)を作成し、GEN_ID()関数にそのシーケンス名を渡し新規連番を取得する。その値でINSERTする。
CREATE TABLE FOO
(
ID INTEGER NOT NULL PRIMARY KEY,
NAME VARCHAR(24) NOT NULL
)
CREATE SEQUENCE FOO_ID_SEQ;
-------------------------------------------
$res = ibase_query($conn, 'SELECT GEN_ID(FOO_ID_SEQ, 1) AS ID FROM RDB$DATABASE');
$row = ibase_fetch_assoc($res);
$lastId = $row["ID"];
ibase_query($conn, "INSERT INTO FOO(ID, NAME) VALUES({$lastId}, 'test')");
var_dump($lastId);
Oracleではシーケンスオブジェクトを作成し、NEXTVALにより新規連番を取得する。その値でINSERTする。
CREATE TABLE FOO
(
ID INTEGER NOT NULL PRIMARY KEY,
NAME VARCHAR(24) NOT NULL
)
CREATE SEQUENCE FOO_ID_SEQ;
-------------------------------------------
$stmt = oci_parse($conn, "SELECT FOO_ID_SEQ.NEXTVAL AS ID FROM DUAL");
oci_execute($stmt, OCI_COMMIT_ON_SUCCESS);
$row = oci_fetch_assoc($stmt);
$lastId = $row["ID"];
$stmt = oci_parse($conn, "INSERT INTO FOO(ID, NAME) VALUES({$lastId}, 'test')");
oci_execute($stmt, OCI_COMMIT_ON_SUCCESS);
var_dump($lastId);
SQL ServerはカラムをIDENTIFYで定義すれば、新規連番を振ってくれる。振られた連番はSCOPE_IDENTITY()関数で取得できる。
CREATE TABLE foo
(
id INTEGER IDENTITY(1, 1) NOT NULL,
name VARCHAR(24) NOT NULL
)
-------------------------------------------
mssql_query("INSERT INTO foo(name) VALUES('test')", $conn);
$res = mssql_query("SELECT SCOPE_IDENTITY() AS id", $conn);
$row = mssql_fetch_assoc($res);
var_dump($row["id"]);
June 08, 2008
php - cloning an object
あるクラスの多くのインスタンスを必要とする時、newでそれぞれのインスタンスを生成するよりも、一つのインスタンスを複製(クローン)した方がパフォーマンスが良い。
ただ、「使えるところ」はあまりないかも。
class Foo
{
public $obj = null;
public $var = "foo";
public function __construct()
{
$obj = new stdClass();
$obj->a = 10;
$obj->b = 20;
$this->obj = $obj;
}
}
まずは一つ一つ、それぞれnewでインスタンスを生成する。
$objs = array();
$start = microtime();
for ($i = 0; $i < 10000; $i++) {
$objs[] = new Foo();
}
var_dump(microtime() - $start);
var_dump(memory_get_peak_usage(true));
上記コードの所要時間と使用メモリ量。
float(0.0505634) // 50.6 msec int(6553600) // 6554 KB次は最初にインスタンスを生成し、それを複製する。
$objs = array();
$start = microtime();
$foo = new Foo();
for ($i = 0; $i < 10000; $i++) {
$objs[] = clone $foo;
}
var_dump(microtime() - $start);
var_dump(memory_get_peak_usage(true));
結果、所要時間は先程の半分以下、メモリ使用量は約半分になる。
float(0.019881) // 19.9 msec int(3407872) // 3408 KBメモリ使用量が減るのは、それぞれのインスタンスが保持する$objが同じオブジェクトを参照しているため。以下のコードを実行することでそれを確認できる。
$foo = new Foo(); $foo2 = clone $foo; $foo2->var = "bar"; $foo2->obj->a = 100; $foo2->obj->b = 200; var_dump($foo->var); // "foo" var_dump($foo2->var); // "bar" var_dump($foo->obj->a); // 100 var_dump($foo2->obj->a); // 100 var_dump($foo->obj->b); // 200 var_dump($foo2->obj->b); // 200この挙動だと困る場合は__clone()メソッド(クローンの時にコールされるマジックメソッド)を実装し、内部のオブジェクトも複製する。
class Foo
{
...
public function __clone()
{
$this->obj = clone $this->obj;
}
}
$foo = new Foo(); $foo2 = clone $foo; $foo2->var = "bar"; $foo2->obj->a = 100; $foo2->obj->b = 200; var_dump($foo->var); // "foo" var_dump($foo2->var); // "bar" var_dump($foo->obj->a); // 10 var_dump($foo2->obj->a); // 100 var_dump($foo->obj->b); // 20 var_dump($foo2->obj->b); // 200ただ、これだと一つ一つインスタンスを生成するのとそう変わらない。
float(0.0442957) // 44.3 msec int(6029312) // 6030 KB使えるところでは使ったほうが吉。
ただ、「使えるところ」はあまりないかも。