Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.62% covered (success)
90.62%
29 / 32
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
PasvCommand
90.62% covered (success)
90.62%
29 / 32
0.00% covered (danger)
0.00%
0 / 1
3.01
0.00% covered (danger)
0.00%
0 / 1
 run
90.62% covered (success)
90.62%
29 / 32
0.00% covered (danger)
0.00%
0 / 1
3.01
1<?php
2namespace Apie\FtpServer\Commands;
3
4use Apie\Core\Context\ApieContext;
5use Apie\FtpServer\Factories\ServerFactoryInterface;
6use Apie\FtpServer\Factories\SimpleFtpServerFactory;
7use Apie\FtpServer\FtpConstants;
8use Apie\FtpServer\Transfers\PasvTransfer;
9use Apie\FtpServer\Transfers\TransferInterface;
10use React\Socket\ConnectionInterface;
11use RuntimeException;
12
13class PasvCommand implements CommandInterface
14{
15    public function run(ApieContext $apieContext, string $arg = ''): ApieContext
16    {
17        $conn = $apieContext->getContext(ConnectionInterface::class);
18        $transfer = $apieContext->getContext(TransferInterface::class, false);
19        if ($transfer instanceof PasvTransfer) {
20            $transfer->end();
21        }
22        try {
23            $transfer = new PasvTransfer(
24                $apieContext->getContext(ServerFactoryInterface::class, false) ?? new SimpleFtpServerFactory(),
25                $apieContext->getContext(FtpConstants::PASV_MIN_PORT, false) ?? '49152',
26                $apieContext->getContext(FtpConstants::PASV_MAX_PORT, false) ?? '65534',
27            );
28        } catch (RuntimeException $error) {
29            error_log($error->getMessage());
30            $conn->write("522 No port number available, use PORT instead.\r\n");
31            return $apieContext;
32        }
33        $address = $transfer->getAddress();
34        $port = parse_url($address, PHP_URL_PORT);
35        $ip = str_replace(
36            '.',
37            ',',
38            $apieContext->getContext(FtpConstants::PUBLIC_IP, false) ?? '127.0.0.1'
39        );
40        $p1 = intdiv($port, 256);
41        $p2 = $port % 256;
42        $transfer->connectOnly()->then(
43            function() use ($conn, $ip, $p1, $p2) {
44                $conn->write("227 Entering Passive Mode ($ip,$p1,$p2)\r\n");
45            },
46            function (\Throwable $error) use ($conn) {
47                error_log($error->getMessage());
48                $conn->write("425 Can't open data connection.\r\n");
49            }
50        );
51
52        return $apieContext->withContext(TransferInterface::class, $transfer);
53    }
54}