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 username
http.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エンコーディング使えば良い)。
$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デコードしたものにする。
<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は次のようになる(ソース上にメールアドレスが現れない)。
<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が他の要素を内包していると厄介な挙動をする。

div3
<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'の作成。
$ 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だと次のようなパスにある。
C:\Documents and Settings\user\Application Data\Thunderbird\Profiles\xxxxx.default\prefs.js
Linuxだと次のようなパスにある。
/home/user/.thunderbird/xxxxx.default/prefs.js
prefs.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の設定をする。全ユーザの場合はドメイン名だけで良い。
$ vi /etc/postfix/transport

user@example.com phpscript:
# example.com phpscript:
main.cfでこのファイルを指定する。
$ vi /etc/postfix/transport

transport_maps = hash:/etc/postfix/transport
postmapコマンドでtransport.dbを更新する。
$ /usr/sbin/postmap /etc/postfix/transport
master.cfで"phpscript"サービスの設定をする。
$ vi /etc/postfix/master.cf

phpscript unix  -      n      n      -      -      pipe
  flags= user=nobody argv=/path/to/php /path/to/script.php
postfixを再起動する。
$ /etc/init.d/postfix restart
phpでは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
使えるところでは使ったほうが吉。
ただ、「使えるところ」はあまりないかも。

Sabel

Sabel PHP Frameworkを開発しています。
http://www.sabel.jp/

Search
Categories
Tags
Recent Articles
Archives