]> granicus.if.org Git - php/commitdiff
FTP: implement MLSD for structured listing of directories, decribed at https://tools...
authorAndreas Treichel <gmblar+github@gmail.com>
Sat, 28 Jan 2017 03:20:40 +0000 (04:20 +0100)
committerJoe Watkins <krakjoe@php.net>
Sun, 29 Jan 2017 07:28:22 +0000 (07:28 +0000)
ext/ftp/ftp.c
ext/ftp/ftp.h
ext/ftp/php_ftp.c
ext/ftp/php_ftp.h
ext/ftp/tests/006.phpt
ext/ftp/tests/ftp_mlsd_empty_directory.phpt [new file with mode: 0644]
ext/ftp/tests/ftp_mlsd_missing_directory.phpt [new file with mode: 0644]
ext/ftp/tests/server.inc

index a7793f527963bd9392fd6a73de4b617449e9edd3..4f682e709d9962e51080717963ef3d8d5900079b 100644 (file)
@@ -698,6 +698,16 @@ ftp_list(ftpbuf_t *ftp, const char *path, const size_t path_len, int recursive)
 }
 /* }}} */
 
+/* {{{ ftp_mlsd
+ */
+char**
+ftp_mlsd(ftpbuf_t *ftp, const char *path, const size_t path_len)
+{
+       return ftp_genlist(ftp, "MLSD", sizeof("MLSD")-1, path, path_len);
+}
+/* }}} */
+
+
 /* {{{ ftp_type
  */
 int
index 077dc78943aa893aa76f5047b1d564aa3e71b0a0..494c527ca1b405cf296a549b780b9a4f97bc136a 100644 (file)
@@ -163,6 +163,13 @@ char**             ftp_nlist(ftpbuf_t *ftp, const char *path, const size_t path_len);
  */
 char**         ftp_list(ftpbuf_t *ftp, const char *path, const size_t path_len, int recursive);
 
+/* returns a NULL-terminated array of lines returned by the ftp
+ * MLSD command for the given path or NULL on error.  the return
+ * array must be freed (but don't
+ * free the array elements)
+ */
+char**         ftp_mlsd(ftpbuf_t *ftp, const char *path, const size_t path_len);
+
 /* switches passive mode on or off
  * returns true on success, false on error
  */
index 631eb69744ceef3a9109ecc97285965eb3e9dced..dc7351ff8298c26bdadc9bf23f32e8bc6f0a297c 100644 (file)
@@ -117,6 +117,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_ftp_rawlist, 0, 0, 2)
        ZEND_ARG_INFO(0, recursive)
 ZEND_END_ARG_INFO()
 
+ZEND_BEGIN_ARG_INFO(arginfo_ftp_mlsd, 0)
+       ZEND_ARG_INFO(0, ftp)
+       ZEND_ARG_INFO(0, directory)
+ZEND_END_ARG_INFO()
+
 ZEND_BEGIN_ARG_INFO(arginfo_ftp_systype, 0)
        ZEND_ARG_INFO(0, ftp)
 ZEND_END_ARG_INFO()
@@ -254,6 +259,7 @@ const zend_function_entry php_ftp_functions[] = {
        PHP_FE(ftp_alloc,                       arginfo_ftp_alloc)
        PHP_FE(ftp_nlist,                       arginfo_ftp_nlist)
        PHP_FE(ftp_rawlist,                     arginfo_ftp_rawlist)
+       PHP_FE(ftp_mlsd,                        arginfo_ftp_mlsd)
        PHP_FE(ftp_systype,                     arginfo_ftp_systype)
        PHP_FE(ftp_pasv,                        arginfo_ftp_pasv)
        PHP_FE(ftp_get,                         arginfo_ftp_get)
@@ -748,6 +754,36 @@ PHP_FUNCTION(ftp_rawlist)
 }
 /* }}} */
 
+/* {{{ proto array ftp_mlsd(resource stream, string directory)
+   Returns a detailed listing of a directory as an array of output lines */
+PHP_FUNCTION(ftp_mlsd)
+{
+       zval            *z_ftp;
+       ftpbuf_t        *ftp;
+       char            **llist, **ptr, *dir;
+       size_t          dir_len;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &z_ftp, &dir, &dir_len) == FAILURE) {
+               return;
+       }
+
+       if ((ftp = (ftpbuf_t *)zend_fetch_resource(Z_RES_P(z_ftp), le_ftpbuf_name, le_ftpbuf)) == NULL) {
+               RETURN_FALSE;
+       }
+
+       /* get raw directory listing */
+       if (NULL == (llist = ftp_mlsd(ftp, dir, dir_len))) {
+               RETURN_FALSE;
+       }
+
+       array_init(return_value);
+       for (ptr = llist; *ptr; ptr++) {
+               add_next_index_string(return_value, *ptr);
+       }
+       efree(llist);
+}
+/* }}} */
+
 /* {{{ proto string ftp_systype(resource stream)
    Returns the system type identifier */
 PHP_FUNCTION(ftp_systype)
index d8d987bfd1f23b22a633a9ef937ecc2357c86a93..37b75f6b9b2a3028ed90c93a4417d2fff3654b50 100644 (file)
@@ -54,6 +54,7 @@ PHP_FUNCTION(ftp_chmod);
 PHP_FUNCTION(ftp_alloc);
 PHP_FUNCTION(ftp_nlist);
 PHP_FUNCTION(ftp_rawlist);
+PHP_FUNCTION(ftp_mlsd);
 PHP_FUNCTION(ftp_systype);
 PHP_FUNCTION(ftp_pasv);
 PHP_FUNCTION(ftp_get);
index 4d31be78c0a5bb7836ee9d868989180c015213e8..899ecbbec3b7e03286e330434140193405e69ec3 100644 (file)
@@ -30,6 +30,7 @@ var_dump(ftp_rename($ftp));
 var_dump(ftp_site($ftp));
 var_dump(ftp_set_option($ftp));
 var_dump(ftp_get_option($ftp));
+var_dump(ftp_mlsd($ftp));
 
 ?>
 --EXPECTF--
@@ -98,3 +99,6 @@ NULL
 
 Warning: ftp_get_option() expects exactly 2 parameters, 1 given in %s006.php on line 25
 NULL
+
+Warning: ftp_mlsd() expects exactly 2 parameters, 1 given in %s006.php on line 26
+NULL
diff --git a/ext/ftp/tests/ftp_mlsd_empty_directory.phpt b/ext/ftp/tests/ftp_mlsd_empty_directory.phpt
new file mode 100644 (file)
index 0000000..c9c278a
--- /dev/null
@@ -0,0 +1,34 @@
+--TEST--
+ftp_mlsd() must not return false on empty directories
+--SKIPIF--
+<?php
+require 'skipif.inc';
+?>
+--FILE--
+<?php
+require 'server.inc';
+
+$ftp = ftp_connect('127.0.0.1', $port);
+if (!$ftp) die("Couldn't connect to the server");
+
+var_dump(ftp_login($ftp, 'user', 'pass'));
+
+var_dump(ftp_mlsd($ftp, ''));
+var_dump(ftp_mlsd($ftp, 'emptydir'));
+var_dump(ftp_mlsd($ftp, 'bogusdir'));
+
+ftp_close($ftp);
+?>
+--EXPECT--
+bool(true)
+array(3) {
+  [0]=>
+  string(109) "modify=20170127230002;perm=flcdmpe;type=cdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; ."
+  [1]=>
+  string(110) "modify=20170127230002;perm=flcdmpe;type=pdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; .."
+  [2]=>
+  string(122) "modify=20170126121225;perm=adfrw;size=4729;type=file;unique=811U4340CB9;UNIX.group=33;UNIX.mode=0644;UNIX.owner=33; foobar"
+}
+array(0) {
+}
+bool(false)
diff --git a/ext/ftp/tests/ftp_mlsd_missing_directory.phpt b/ext/ftp/tests/ftp_mlsd_missing_directory.phpt
new file mode 100644 (file)
index 0000000..3656ef9
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+Testing ftp_mlsd returns false on server error
+--CREDITS--
+Andreas Treichel <gmblar+github [at] gmail [dot] com>
+--SKIPIF--
+<?php
+require 'skipif.inc';
+?>
+--FILE--
+<?php
+require 'server.inc';
+
+$ftp = ftp_connect('127.0.0.1', $port);
+ftp_login($ftp, 'user', 'pass');
+if (!$ftp) die("Couldn't connect to the server");
+
+var_dump(ftp_mlsd($ftp, 'no_exists/'));
+?>
+--EXPECT--
+bool(false)
index ffef025074241e6db8a038872389359354a91b4d..1ae52f05b632cc46be1e4c393dd61bee5952bff0 100644 (file)
@@ -441,6 +441,44 @@ if ($pid) {
                        fputs($s, "350 OK\r\n");
                 }elseif (preg_match('/^SIZE largefile/', $buf)) {
                         fputs($s, "213 5368709120\r\n");
+               }elseif (preg_match('/^MLSD no_exists\//', $buf, $matches)) {
+                       fputs($s, "425 Error establishing connection\r\n");
+               }elseif (preg_match("~^MLSD(?: ([A-Za-z./]+))?\r\n$~", $buf, $m)) {
+
+                       if(isset($m[1]) && (($m[1] === 'bogusdir') || ($m[1] === '/bogusdir'))) {
+                               fputs($s, "250 $m[1]: No such file or directory\r\n");
+                               continue;
+                       }
+
+                       // there are some servers that don't open the ftp-data socket if there's nothing to send
+                       if(isset($bug39458) && isset($m[1]) && $m[1] === 'emptydir') {
+                               fputs($s, "226 Transfer complete.\r\n");
+                               continue;
+                       }
+
+                       if(empty($pasv)) {
+                               fputs($s, "150 File status okay; about to open data connection\r\n");
+                               if(!$fs = stream_socket_client("tcp://$host:$port")) {
+                                       fputs($s, "425 Can't open data connection\r\n");
+                                       continue;
+                               }
+                       } else {
+                               fputs($s, "125 Data connection already open; transfer starting.\r\n");
+                               $fs = $pasvs;
+                       }
+
+                       if((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, TRUE, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) {
+                               die("SSLv23 handshake failed.\n");
+                       }
+
+                       if(empty($m[1]) || $m[1] !== 'emptydir') {
+                               fputs($fs, "modify=20170127230002;perm=flcdmpe;type=cdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; .\r\n");
+                               fputs($fs, "modify=20170127230002;perm=flcdmpe;type=pdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; ..\r\n");
+                               fputs($fs, "modify=20170126121225;perm=adfrw;size=4729;type=file;unique=811U4340CB9;UNIX.group=33;UNIX.mode=0644;UNIX.owner=33; foobar\r\n");
+                       }
+
+                       fputs($s, "226 Closing data Connection.\r\n");
+                       fclose($fs);
                 }else {
                        fputs($s, "500 Syntax error, command unrecognized.\r\n");
                        dump_and_exit($buf);