Skip to content

Commit 0103d1e

Browse files
blarkrakjoe
authored andcommitted
FTP: implement MLSD for structured listing of directories, decribed at https://tools.ietf.org/html/rfc3659
1 parent 3de7b2a commit 0103d1e

File tree

8 files changed

+150
-0
lines changed

8 files changed

+150
-0
lines changed

ext/ftp/ftp.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,16 @@ ftp_list(ftpbuf_t *ftp, const char *path, const size_t path_len, int recursive)
698698
}
699699
/* }}} */
700700

701+
/* {{{ ftp_mlsd
702+
*/
703+
char**
704+
ftp_mlsd(ftpbuf_t *ftp, const char *path, const size_t path_len)
705+
{
706+
return ftp_genlist(ftp, "MLSD", sizeof("MLSD")-1, path, path_len);
707+
}
708+
/* }}} */
709+
710+
701711
/* {{{ ftp_type
702712
*/
703713
int

ext/ftp/ftp.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ char** ftp_nlist(ftpbuf_t *ftp, const char *path, const size_t path_len);
163163
*/
164164
char** ftp_list(ftpbuf_t *ftp, const char *path, const size_t path_len, int recursive);
165165

166+
/* returns a NULL-terminated array of lines returned by the ftp
167+
* MLSD command for the given path or NULL on error. the return
168+
* array must be freed (but don't
169+
* free the array elements)
170+
*/
171+
char** ftp_mlsd(ftpbuf_t *ftp, const char *path, const size_t path_len);
172+
166173
/* switches passive mode on or off
167174
* returns true on success, false on error
168175
*/

ext/ftp/php_ftp.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_ftp_rawlist, 0, 0, 2)
117117
ZEND_ARG_INFO(0, recursive)
118118
ZEND_END_ARG_INFO()
119119

120+
ZEND_BEGIN_ARG_INFO(arginfo_ftp_mlsd, 0)
121+
ZEND_ARG_INFO(0, ftp)
122+
ZEND_ARG_INFO(0, directory)
123+
ZEND_END_ARG_INFO()
124+
120125
ZEND_BEGIN_ARG_INFO(arginfo_ftp_systype, 0)
121126
ZEND_ARG_INFO(0, ftp)
122127
ZEND_END_ARG_INFO()
@@ -254,6 +259,7 @@ const zend_function_entry php_ftp_functions[] = {
254259
PHP_FE(ftp_alloc, arginfo_ftp_alloc)
255260
PHP_FE(ftp_nlist, arginfo_ftp_nlist)
256261
PHP_FE(ftp_rawlist, arginfo_ftp_rawlist)
262+
PHP_FE(ftp_mlsd, arginfo_ftp_mlsd)
257263
PHP_FE(ftp_systype, arginfo_ftp_systype)
258264
PHP_FE(ftp_pasv, arginfo_ftp_pasv)
259265
PHP_FE(ftp_get, arginfo_ftp_get)
@@ -748,6 +754,36 @@ PHP_FUNCTION(ftp_rawlist)
748754
}
749755
/* }}} */
750756

757+
/* {{{ proto array ftp_mlsd(resource stream, string directory)
758+
Returns a detailed listing of a directory as an array of output lines */
759+
PHP_FUNCTION(ftp_mlsd)
760+
{
761+
zval *z_ftp;
762+
ftpbuf_t *ftp;
763+
char **llist, **ptr, *dir;
764+
size_t dir_len;
765+
766+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &z_ftp, &dir, &dir_len) == FAILURE) {
767+
return;
768+
}
769+
770+
if ((ftp = (ftpbuf_t *)zend_fetch_resource(Z_RES_P(z_ftp), le_ftpbuf_name, le_ftpbuf)) == NULL) {
771+
RETURN_FALSE;
772+
}
773+
774+
/* get raw directory listing */
775+
if (NULL == (llist = ftp_mlsd(ftp, dir, dir_len))) {
776+
RETURN_FALSE;
777+
}
778+
779+
array_init(return_value);
780+
for (ptr = llist; *ptr; ptr++) {
781+
add_next_index_string(return_value, *ptr);
782+
}
783+
efree(llist);
784+
}
785+
/* }}} */
786+
751787
/* {{{ proto string ftp_systype(resource stream)
752788
Returns the system type identifier */
753789
PHP_FUNCTION(ftp_systype)

ext/ftp/php_ftp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ PHP_FUNCTION(ftp_chmod);
5454
PHP_FUNCTION(ftp_alloc);
5555
PHP_FUNCTION(ftp_nlist);
5656
PHP_FUNCTION(ftp_rawlist);
57+
PHP_FUNCTION(ftp_mlsd);
5758
PHP_FUNCTION(ftp_systype);
5859
PHP_FUNCTION(ftp_pasv);
5960
PHP_FUNCTION(ftp_get);

ext/ftp/tests/006.phpt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var_dump(ftp_rename($ftp));
3030
var_dump(ftp_site($ftp));
3131
var_dump(ftp_set_option($ftp));
3232
var_dump(ftp_get_option($ftp));
33+
var_dump(ftp_mlsd($ftp));
3334

3435
?>
3536
--EXPECTF--
@@ -98,3 +99,6 @@ NULL
9899

99100
Warning: ftp_get_option() expects exactly 2 parameters, 1 given in %s006.php on line 25
100101
NULL
102+
103+
Warning: ftp_mlsd() expects exactly 2 parameters, 1 given in %s006.php on line 26
104+
NULL
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
ftp_mlsd() must not return false on empty directories
3+
--SKIPIF--
4+
<?php
5+
require 'skipif.inc';
6+
?>
7+
--FILE--
8+
<?php
9+
require 'server.inc';
10+
11+
$ftp = ftp_connect('127.0.0.1', $port);
12+
if (!$ftp) die("Couldn't connect to the server");
13+
14+
var_dump(ftp_login($ftp, 'user', 'pass'));
15+
16+
var_dump(ftp_mlsd($ftp, ''));
17+
var_dump(ftp_mlsd($ftp, 'emptydir'));
18+
var_dump(ftp_mlsd($ftp, 'bogusdir'));
19+
20+
ftp_close($ftp);
21+
?>
22+
--EXPECT--
23+
bool(true)
24+
array(3) {
25+
[0]=>
26+
string(109) "modify=20170127230002;perm=flcdmpe;type=cdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; ."
27+
[1]=>
28+
string(110) "modify=20170127230002;perm=flcdmpe;type=pdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; .."
29+
[2]=>
30+
string(122) "modify=20170126121225;perm=adfrw;size=4729;type=file;unique=811U4340CB9;UNIX.group=33;UNIX.mode=0644;UNIX.owner=33; foobar"
31+
}
32+
array(0) {
33+
}
34+
bool(false)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Testing ftp_mlsd returns false on server error
3+
--CREDITS--
4+
Andreas Treichel <gmblar+github [at] gmail [dot] com>
5+
--SKIPIF--
6+
<?php
7+
require 'skipif.inc';
8+
?>
9+
--FILE--
10+
<?php
11+
require 'server.inc';
12+
13+
$ftp = ftp_connect('127.0.0.1', $port);
14+
ftp_login($ftp, 'user', 'pass');
15+
if (!$ftp) die("Couldn't connect to the server");
16+
17+
var_dump(ftp_mlsd($ftp, 'no_exists/'));
18+
?>
19+
--EXPECT--
20+
bool(false)

ext/ftp/tests/server.inc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,44 @@ if ($pid) {
441441
fputs($s, "350 OK\r\n");
442442
}elseif (preg_match('/^SIZE largefile/', $buf)) {
443443
fputs($s, "213 5368709120\r\n");
444+
}elseif (preg_match('/^MLSD no_exists\//', $buf, $matches)) {
445+
fputs($s, "425 Error establishing connection\r\n");
446+
}elseif (preg_match("~^MLSD(?: ([A-Za-z./]+))?\r\n$~", $buf, $m)) {
447+
448+
if(isset($m[1]) && (($m[1] === 'bogusdir') || ($m[1] === '/bogusdir'))) {
449+
fputs($s, "250 $m[1]: No such file or directory\r\n");
450+
continue;
451+
}
452+
453+
// there are some servers that don't open the ftp-data socket if there's nothing to send
454+
if(isset($bug39458) && isset($m[1]) && $m[1] === 'emptydir') {
455+
fputs($s, "226 Transfer complete.\r\n");
456+
continue;
457+
}
458+
459+
if(empty($pasv)) {
460+
fputs($s, "150 File status okay; about to open data connection\r\n");
461+
if(!$fs = stream_socket_client("tcp://$host:$port")) {
462+
fputs($s, "425 Can't open data connection\r\n");
463+
continue;
464+
}
465+
} else {
466+
fputs($s, "125 Data connection already open; transfer starting.\r\n");
467+
$fs = $pasvs;
468+
}
469+
470+
if((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, TRUE, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) {
471+
die("SSLv23 handshake failed.\n");
472+
}
473+
474+
if(empty($m[1]) || $m[1] !== 'emptydir') {
475+
fputs($fs, "modify=20170127230002;perm=flcdmpe;type=cdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; .\r\n");
476+
fputs($fs, "modify=20170127230002;perm=flcdmpe;type=pdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; ..\r\n");
477+
fputs($fs, "modify=20170126121225;perm=adfrw;size=4729;type=file;unique=811U4340CB9;UNIX.group=33;UNIX.mode=0644;UNIX.owner=33; foobar\r\n");
478+
}
479+
480+
fputs($s, "226 Closing data Connection.\r\n");
481+
fclose($fs);
444482
}else {
445483
fputs($s, "500 Syntax error, command unrecognized.\r\n");
446484
dump_and_exit($buf);

0 commit comments

Comments
 (0)