-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
44 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,91 +1,54 @@ | ||
# PHP FTP Client | ||
|
||
[![Build Status](https://travis-ci.org/ngyuki/php-ftp-client.png)](https://travis-ci.org/ngyuki/php-ftp-client) | ||
[![Coverage Status](https://coveralls.io/repos/ngyuki/php-ftp-client/badge.png?branch=next)](https://coveralls.io/r/ngyuki/php-ftp-client?branch=next) | ||
|
||
概要 | ||
----- | ||
|
||
PHPのFTP拡張モジュールには下記の問題があり、拡張モジュールを修正するよりソケット関数で新たに実装した方が簡単そうだったので作りました。 | ||
|
||
PHPのFTP拡張モジュールの問題 | ||
----------------------------- | ||
|
||
ある環境のPHP(5.3.2)のftp関数で、パッシブモードを指定しているにも関わらずFTPサーバのログを見ると PORT コマンドが発行されていることがありました。 | ||
PHPのftp拡張のソースを確認したところ、下記のようなコードで PORT コマンドが発行されることがわかりました。 | ||
|
||
```php | ||
<?php | ||
`iptables -F`; | ||
`iptables -A INPUT -j REJECT -p tcp --sport 20`; | ||
|
||
$ftp = ftp_connect($addr, $port, 1); | ||
|
||
ftp_login($ftp, $id, $pw); | ||
|
||
ftp_pasv($ftp, true); | ||
/* (1) */ ftp_put($ftp, "0001.txt", __FILE__, FTP_BINARY); | ||
|
||
`iptables -A INPUT -j DROP -p tcp --sport 21`; | ||
FTP client library that does not depend on FTP extension. | ||
|
||
/* (2) */ ftp_put($ftp, "0002.txt", __FILE__, FTP_BINARY); | ||
## Install | ||
|
||
`iptables -R INPUT 2`; | ||
|
||
/* (3) */ ftp_put($ftp, "0003.txt", __FILE__, FTP_BINARY); | ||
/* (4) */ ftp_put($ftp, "0004.txt", __FILE__, FTP_BINARY); | ||
|
||
ftp_close($ftp); | ||
```console | ||
$ php composer.phar require "ngyuki/php-ftp-client:*" | ||
``` | ||
|
||
元々アクティブモードでは通信できないサーバに対して・・・ | ||
|
||
- (1)でファイルを正常にアップロード | ||
- (2)の`ftp_put`の応答が何らかの原因で DROP された | ||
- (3)と(4)でファイルのアップロードを試みた | ||
|
||
という動作をイメージしています。このとき (2)~(4) でそれぞれ次の通り PHP Warning が発生しています。 | ||
## Requirements | ||
|
||
- (2) Warning: ftp_put(): Transfer complete in /tmp/ftp.php on line 22 | ||
- (3) Warning: ftp_put(): Entering Passive Mode (192,168,1,100,225,80). in /tmp/ftp.php on line 26 | ||
- (4) Warning: ftp_put(): PORT command successful in /tmp/ftp.php on line 27 | ||
- PHP 5.3.0 or later | ||
|
||
FTPサーバ側では次の通りログが記録されています(コマンド 応答コード バイト数 の順)。 | ||
|
||
"USER hoge" 331 - | ||
"PASS (hidden)" 230 - | ||
"PASV" 227 - | ||
"TYPE I" 200 - | ||
"STOR 0001.txt" 226 1760 | ||
"PASV" 227 - | ||
"PORT 192,168,1,101,136,135" 200 - | ||
"PORT 192,168,1,101,199,141" 200 - | ||
"STOR 0004.txt" 425 0 | ||
"QUIT" 221 - | ||
|
||
ftp拡張のソースを見た感じ、(2)の ftp_put の中で発行されている PASV コマンドの応答が DROP されると、 | ||
その次の(3)からはアクティブモードになるようでした。 | ||
|
||
|
||
さらに、PHP Warning の内容が次のように1個ずれたような感じになっています。 | ||
|
||
- (2) のエラーメッセージは (1) の転送完了のメッセージ | ||
- (3) のエラーメッセージは (2) の PASV の応答メッセージ | ||
- (4) のエラーメッセージは (3) の PORT の応答メッセージ | ||
|
||
この原因を調べたところ、単純に次のようなフローでFTPコマンドのリクエストとレスポンスの対応がずれてしまうことが判りました。 | ||
|
||
- クライアントからサーバへコマンド A をリクエスト | ||
- サーバからクライアントへの A のレスポンスが何らかの原因で遅延 | ||
- クライアントは A をタイムアウトと判断して次のコマンド B をリクエスト(FTP関数では単に`false`が返るだけ) | ||
- A のレスポンスがクライアントに到達する | ||
- クライアントは A のレスポンスを B のレスポンスだと解釈する | ||
|
||
単にアクティブ/パッシブだけの問題であれば`ftp_put`の前に必ず`ftp_pasv`を呼べば解決できそうですが、 | ||
エラーメッセージがずれる問題は、コマンドのリクエストとレスポンスがずれてしまっているので、 | ||
FTPサーバとの接続を一旦切って再接続しなければ復帰することが出来ません。 | ||
|
||
が、FTP関数はリターンコードによるコマンドの失敗も、タイムアウトなどの問題と同じように`false`を返すだけになっているため、区別することが出来ません。 | ||
そのため、PHPのエラーメッセージから失敗の原因を判断するか、ftp関数で`false`が返された場合はもれなく再接続を行うようにする必要があります。 | ||
前者はちょっとどうかと思うので後者でどうにかしようと考えましたが、`ftp_cwd`や`ftp_mkdir`の失敗まで再接続しなければならなくなり、ちょっと使い勝手が良くありません。 | ||
|
||
そこで、FTP層のエラーとそれ以下のエラーを区別できるようにこれを作成しました。 | ||
## Example | ||
|
||
```php | ||
<?php | ||
require __DIR__ . '/../vendor/autoload.php'; | ||
|
||
use ngyuki\FtpClient\FtpClient; | ||
use ngyuki\FtpClient\FtpException; | ||
use ngyuki\FtpClient\TransportException; | ||
|
||
$ftp = new FtpClient(); | ||
|
||
try | ||
{ | ||
$ftp->connect("skr", 21, 10); | ||
$ftp->login("hoge", "piyo"); | ||
|
||
echo "nlist...\n"; | ||
echo implode("\n", $ftp->nlist(".")); | ||
echo "\n\n"; | ||
|
||
echo "put...\n"; | ||
$ftp->put("test.txt", "testing"); | ||
|
||
$ftp->quit(); | ||
|
||
echo "done.\n"; | ||
} | ||
catch (FtpException $ex) | ||
{ | ||
echo "FtpException: {$ex->getResponse()->getResponseLine()}\n"; | ||
} | ||
catch (TransportException $ex) | ||
{ | ||
echo "TransportException: {$ex->getMessage()}\n"; | ||
} | ||
``` |