Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.43% covered (success)
91.43%
32 / 35
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
CwdCommand
91.43% covered (success)
91.43%
32 / 35
0.00% covered (danger)
0.00%
0 / 2
12.09
0.00% covered (danger)
0.00%
0 / 1
 run
96.15% covered (success)
96.15%
25 / 26
0.00% covered (danger)
0.00%
0 / 1
7
 normalizePath
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
5.27
1<?php
2namespace Apie\FtpServer\Commands;
3
4use Apie\ApieFileSystem\ApieFilesystem;
5use Apie\ApieFileSystem\Virtual\VirtualFolderInterface;
6use Apie\Core\Context\ApieContext;
7use Apie\FtpServer\FtpConstants;
8use React\Socket\ConnectionInterface;
9
10class CwdCommand implements CommandInterface
11{
12    public function run(ApieContext $apieContext, string $arg = ''): ApieContext
13    {
14        $conn = $apieContext->getContext(ConnectionInterface::class);
15        if (!$arg) {
16            $conn->write("550 Name invalid\r\n");
17            return $apieContext;
18        }
19
20        $filesystem = $apieContext->getContext(ApieFilesystem::class);
21        assert($filesystem instanceof ApieFilesystem);
22        $pwd = trim($apieContext->getContext(FtpConstants::CURRENT_PWD), '/');
23        if (str_starts_with($arg, '/')) {
24            $pwd = '';
25        }
26        if ($arg === '.') {
27            $conn->write("250 Directory successfully changed.\r\n");
28            return $apieContext;
29        }
30        if ($arg === '..') {
31            return (new CdupCommand())->run($apieContext, $arg);
32        }
33        $path = $this->normalizePath($pwd . '/' . $arg);
34    
35
36        $child = $filesystem->visit($path);
37        if (!$child) {
38            $conn->write("550 Folder $path not found\r\n");
39            return $apieContext;
40        }
41        if ($child instanceof VirtualFolderInterface) {
42            $conn->write("250 Directory successfully changed.\r\n");
43            return $apieContext
44                ->withContext(FtpConstants::CURRENT_FOLDER, $child)
45                ->withContext(FtpConstants::CURRENT_PWD, $path);
46        }
47
48        $conn->write("550 Failed to change directory: $path is a file.\r\n");
49        return $apieContext;
50    }
51
52    private function normalizePath($path)
53    {
54        $parts = explode('/', $path);
55        $stack = [];
56
57        foreach ($parts as $part) {
58            if ($part === '' || $part === '.') {
59                continue;
60            }
61            if ($part === '..') {
62                array_pop($stack);
63            } else {
64                $stack[] = $part;
65            }
66        }
67
68        return implode('/', $stack);
69    }
70}