[php-src] master: ext/curl: add more tests (#23333)

Sjoerd Langkemper via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Sjoerd Langkemper (Sjord)
Committer: GitHub (web-flow)
Pusher: devnexen
Date: 2026-08-29T15:34:51+01:00

Commit: https://github.com/php/php-src/commit/c282f04efd90d7bc42d1951dc84188be3039bf41
Raw diff: https://github.com/php/php-src/commit/c282f04efd90d7bc42d1951dc84188be3039bf41.diff

ext/curl: add more tests (#23333)

- CURLOPT_WRITEHEADER

- CURLOPT_MAXFILESIZE_LARGE

- CURLOPT_POSTREDIR

- CURLOPT_SSH_HOSTKEYFUNCTION

- CURLOPT_FNMATCH_FUNCTION

- CURLINFO_HEADER_OUT

- read_cb by sending a CURLFile twice

- CURLINFO_CERTINFO

- CURLE_ error constants

Changed paths:
  A  ext/curl/tests/curl_curlfile_seek.phpt
  A  ext/curl/tests/curl_errors.phpt
  A  ext/curl/tests/curl_getinfo_CURLINFO_CERTINFO.phpt
  A  ext/curl/tests/curl_getinfo_CURLINFO_HEADER_OUT.phpt
  A  ext/curl/tests/curl_setopt_CURLOPT_FNMATCH_FUNCTION.phpt
  A  ext/curl/tests/curl_setopt_CURLOPT_MAXFILESIZE_LARGE.phpt
  A  ext/curl/tests/curl_setopt_CURLOPT_POSTREDIR.phpt
  A  ext/curl/tests/curl_setopt_CURLOPT_SSH_HOSTKEYFUNCTION.phpt
  A  ext/curl/tests/curl_setopt_CURLOPT_WRITEHEADER.phpt
  M  ext/curl/tests/responder/get.inc
  M  ext/ftp/tests/server.inc


Diff:

diff --git a/ext/curl/tests/curl_curlfile_seek.phpt b/ext/curl/tests/curl_curlfile_seek.phpt
new file mode 100644
index 000000000000..8ebaab92bc43
--- /dev/null
+++ b/ext/curl/tests/curl_curlfile_seek.phpt
@@ -0,0 +1,22 @@
+--TEST--
+curl seek within uploaded file
+--EXTENSIONS--
+curl
+--FILE--
+<?php
+include 'server.inc';
+$host = curl_cli_server_start();
+
+$url = "{$host}/get.inc?test=redirect&target=file";
+
+$ch = curl_init();
+curl_setopt($ch, CURLOPT_URL, $url);
+curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+curl_setopt($ch, CURLOPT_POSTFIELDS, [
+    'file' => new CURLFile(__DIR__ . '/curl_testdata1.txt')
+]);
+curl_exec($ch);
+
+?>
+--EXPECT--
+curl_testdata1.txt|application/octet-stream|6
diff --git a/ext/curl/tests/curl_errors.phpt b/ext/curl/tests/curl_errors.phpt
new file mode 100644
index 000000000000..a3f1bce60768
--- /dev/null
+++ b/ext/curl/tests/curl_errors.phpt
@@ -0,0 +1,116 @@
+--TEST--
+curl error constants
+--EXTENSIONS--
+curl
+--FILE--
+<?php
+
+$expected = [
+    'CURLE_OK' => 0,
+    'CURLE_UNSUPPORTED_PROTOCOL' => 1,
+    'CURLE_FAILED_INIT' => 2,
+    'CURLE_URL_MALFORMAT' => 3,
+    'CURLE_URL_MALFORMAT_USER' => 4,
+    'CURLE_COULDNT_RESOLVE_PROXY' => 5,
+    'CURLE_COULDNT_RESOLVE_HOST' => 6,
+    'CURLE_COULDNT_CONNECT' => 7,
+    'CURLE_WEIRD_SERVER_REPLY' => 8,
+    'CURLE_FTP_WEIRD_SERVER_REPLY' => 8,
+    'CURLE_FTP_ACCESS_DENIED' => 9,
+    'CURLE_FTP_USER_PASSWORD_INCORRECT' => 10,
+    'CURLE_FTP_WEIRD_PASS_REPLY' => 11,
+    'CURLE_FTP_WEIRD_USER_REPLY' => 12,
+    'CURLE_FTP_WEIRD_PASV_REPLY' => 13,
+    'CURLE_FTP_WEIRD_227_FORMAT' => 14,
+    'CURLE_FTP_CANT_GET_HOST' => 15,
+    'CURLE_FTP_CANT_RECONNECT' => 16,
+    'CURLE_FTP_COULDNT_SET_BINARY' => 17,
+    'CURLE_PARTIAL_FILE' => 18,
+    'CURLE_FTP_PARTIAL_FILE' => 18,
+    'CURLE_FTP_COULDNT_RETR_FILE' => 19,
+    'CURLE_FTP_WRITE_ERROR' => 20,
+    'CURLE_FTP_QUOTE_ERROR' => 21,
+    'CURLE_HTTP_RETURNED_ERROR' => 22,
+    'CURLE_HTTP_NOT_FOUND' => 22,
+    'CURLE_WRITE_ERROR' => 23,
+    'CURLE_MALFORMAT_USER' => 24,
+    'CURLE_FTP_COULDNT_STOR_FILE' => 25,
+    'CURLE_READ_ERROR' => 26,
+    'CURLE_OUT_OF_MEMORY' => 27,
+    'CURLE_OPERATION_TIMEDOUT' => 28,
+    'CURLE_OPERATION_TIMEOUTED' => 28,
+    'CURLE_FTP_COULDNT_SET_ASCII' => 29,
+    'CURLE_FTP_PORT_FAILED' => 30,
+    'CURLE_FTP_COULDNT_USE_REST' => 31,
+    'CURLE_FTP_COULDNT_GET_SIZE' => 32,
+    'CURLE_HTTP_RANGE_ERROR' => 33,
+    'CURLE_HTTP_POST_ERROR' => 34,
+    'CURLE_SSL_CONNECT_ERROR' => 35,
+    'CURLE_BAD_DOWNLOAD_RESUME' => 36,
+    'CURLE_FTP_BAD_DOWNLOAD_RESUME' => 36,
+    'CURLE_FILE_COULDNT_READ_FILE' => 37,
+    'CURLE_LDAP_CANNOT_BIND' => 38,
+    'CURLE_LDAP_SEARCH_FAILED' => 39,
+    'CURLE_LIBRARY_NOT_FOUND' => 40,
+    'CURLE_FUNCTION_NOT_FOUND' => 41,
+    'CURLE_ABORTED_BY_CALLBACK' => 42,
+    'CURLE_BAD_FUNCTION_ARGUMENT' => 43,
+    'CURLE_BAD_CALLING_ORDER' => 44,
+    'CURLE_HTTP_PORT_FAILED' => 45,
+    'CURLE_BAD_PASSWORD_ENTERED' => 46,
+    'CURLE_TOO_MANY_REDIRECTS' => 47,
+    'CURLE_UNKNOWN_TELNET_OPTION' => 48,
+    'CURLE_TELNET_OPTION_SYNTAX' => 49,
+    'CURLE_OBSOLETE' => 50,
+    'CURLE_GOT_NOTHING' => 52,
+    'CURLE_SSL_ENGINE_NOTFOUND' => 53,
+    'CURLE_SSL_ENGINE_SETFAILED' => 54,
+    'CURLE_SEND_ERROR' => 55,
+    'CURLE_RECV_ERROR' => 56,
+    'CURLE_SHARE_IN_USE' => 57,
+    'CURLE_SSL_CERTPROBLEM' => 58,
+    'CURLE_SSL_CIPHER' => 59,
+    'CURLE_SSL_CACERT' => 60,
+    'CURLE_SSL_PEER_CERTIFICATE' => 60,
+    'CURLE_BAD_CONTENT_ENCODING' => 61,
+    'CURLE_LDAP_INVALID_URL' => 62,
+    'CURLE_FILESIZE_EXCEEDED' => 63,
+    'CURLE_FTP_SSL_FAILED' => 64,
+    'CURLE_SSL_CACERT_BADFILE' => 77,
+    'CURLE_SSH' => 79,
+    'CURLE_SSL_PINNEDPUBKEYNOTMATCH' => 90,
+];
+
+$optional = [
+    'CURLE_PROXY' => 97,
+];
+
+$defined = array_filter(
+    get_defined_constants(),
+    static fn (string $name): bool => str_starts_with($name, 'CURLE_'),
+    ARRAY_FILTER_USE_KEY,
+);
+
+foreach ($expected as $name => $value) {
+    if (!array_key_exists($name, $defined)) {
+        echo "missing: $name\n";
+    } elseif ($defined[$name] !== $value) {
+        echo "wrong value: $name is {$defined[$name]}, expected $value\n";
+    }
+}
+
+foreach ($optional as $name => $value) {
+    if (array_key_exists($name, $defined) && $defined[$name] !== $value) {
+        echo "wrong value: $name is {$defined[$name]}, expected $value\n";
+    }
+}
+
+foreach (array_diff_key($defined, $expected, $optional) as $name => $value) {
+    echo "not covered by this test: $name = $value\n";
+}
+
+echo "done\n";
+
+?>
+--EXPECT--
+done
diff --git a/ext/curl/tests/curl_getinfo_CURLINFO_CERTINFO.phpt b/ext/curl/tests/curl_getinfo_CURLINFO_CERTINFO.phpt
new file mode 100644
index 000000000000..7ce814717753
--- /dev/null
+++ b/ext/curl/tests/curl_getinfo_CURLINFO_CERTINFO.phpt
@@ -0,0 +1,26 @@
+--TEST--
+curl_getinfo - CURLINFO_CERTINFO
+--EXTENSIONS--
+curl
+--SKIPIF--
+<?php
+include 'skipif-nocaddy.inc';
+?>
+--FILE--
+<?php
+
+$ch = curl_init("https://localhost/ping");
+curl_setopt($ch, CURLOPT_CERTINFO, 1);
+curl_exec($ch);
+echo "\n";
+
+$certinfo = curl_getinfo($ch, CURLINFO_CERTINFO);
+
+var_dump(empty($certinfo));
+var_dump(str_starts_with($certinfo[0]['Cert'], '-----BEGIN CERTIFICATE-----'));
+
+?>
+--EXPECT--
+pong
+bool(false)
+bool(true)
diff --git a/ext/curl/tests/curl_getinfo_CURLINFO_HEADER_OUT.phpt b/ext/curl/tests/curl_getinfo_CURLINFO_HEADER_OUT.phpt
new file mode 100644
index 000000000000..1627c03f2ba2
--- /dev/null
+++ b/ext/curl/tests/curl_getinfo_CURLINFO_HEADER_OUT.phpt
@@ -0,0 +1,39 @@
+--TEST--
+curl_getinfo CURLINFO_HEADER_OUT
+--EXTENSIONS--
+curl
+--FILE--
+<?php
+include 'server.inc';
+$host = curl_cli_server_start();
+
+$ch = curl_init("{$host}/get.inc?test=method");
+curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+curl_setopt($ch, CURLOPT_HTTPHEADER, ['Request-num: 1']);
+curl_exec($ch);
+
+// The header string contains the full request header line(s).
+$headers = rtrim(curl_getinfo($ch, CURLINFO_HEADER_OUT));
+echo "With CURLINFO_HEADER_OUT=1:\n";
+echo $headers, "\n---\n";
+
+// Toggling back to 0 should clear the buffer; next getinfo returns false.
+curl_setopt($ch, CURLOPT_HTTPHEADER, ['Request-num: 2']);
+curl_setopt($ch, CURLINFO_HEADER_OUT, 0);
+curl_exec($ch);
+
+$headers = curl_getinfo($ch, CURLINFO_HEADER_OUT);
+echo "With CURLINFO_HEADER_OUT=0:\n";
+var_dump($headers);
+
+?>
+--EXPECTF--
+With CURLINFO_HEADER_OUT=1:
+GET /get.inc?test=method HTTP/1.1
+Host: localhost:%d
+%s
+Request-num: 1
+---
+With CURLINFO_HEADER_OUT=0:
+bool(false)
diff --git a/ext/curl/tests/curl_setopt_CURLOPT_FNMATCH_FUNCTION.phpt b/ext/curl/tests/curl_setopt_CURLOPT_FNMATCH_FUNCTION.phpt
new file mode 100644
index 000000000000..c3136f958835
--- /dev/null
+++ b/ext/curl/tests/curl_setopt_CURLOPT_FNMATCH_FUNCTION.phpt
@@ -0,0 +1,43 @@
+--TEST--
+Curl option CURLOPT_FNMATCH_FUNCTION
+--EXTENSIONS--
+curl
+--SKIPIF--
+<?php
+if (!function_exists('pcntl_fork')) die("skip pcntl_fork() not available.");
+?>
+--FILE--
+<?php
+
+require(__DIR__ . '/../../ftp/tests/server.inc');
+
+$seen_patterns = [];
+$seen_fnames = [];
+
+function fnmatch_function($ch, string $pattern, string $fname) {
+    global $seen_patterns, $seen_fnames;
+
+    $seen_patterns[] = $pattern;
+    $seen_fnames[] = $fname;
+
+    if ($fname === 'a story') {
+        return CURL_FNMATCHFUNC_MATCH;
+    } else {
+        return CURL_FNMATCHFUNC_NOMATCH;
+    }
+}
+
+$ch = curl_init("ftp://$socket_name/f*");
+curl_setopt($ch, CURLOPT_FNMATCH_FUNCTION, 'fnmatch_function');
+curl_setopt($ch, CURLOPT_WILDCARDMATCH, 1);
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+echo curl_exec($ch);
+
+var_dump(in_array('f*', $seen_patterns));
+var_dump(in_array('mediumfile', $seen_fnames));
+
+?>
+--EXPECT--
+For sale: baby shoes, never worn.
+bool(true)
+bool(true)
diff --git a/ext/curl/tests/curl_setopt_CURLOPT_MAXFILESIZE_LARGE.phpt b/ext/curl/tests/curl_setopt_CURLOPT_MAXFILESIZE_LARGE.phpt
new file mode 100644
index 000000000000..3b11c0da266f
--- /dev/null
+++ b/ext/curl/tests/curl_setopt_CURLOPT_MAXFILESIZE_LARGE.phpt
@@ -0,0 +1,60 @@
+--TEST--
+Curl option CURLOPT_MAXFILESIZE_LARGE
+--EXTENSIONS--
+curl
+--SKIPIF--
+<?php
+$curl_version = curl_version();
+if ($curl_version['version_number'] < 0x080400) {
+    // Earlier curl has this option, but it only works
+    // when the response has a Content-Length header.
+    exit("skip: test works only with curl >= 8.4.0");
+}
+?>
+--FILE--
+<?php
+include 'server.inc';
+$host = curl_cli_server_start();
+
+echo "Body larger than CURLOPT_MAXFILESIZE_LARGE\n";
+$ch = curl_init();
+curl_setopt($ch, CURLOPT_URL, "{$host}/get.inc");
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+curl_setopt($ch, CURLOPT_MAXFILESIZE_LARGE, 10);
+$r = curl_exec($ch);
+var_dump($r);
+var_dump(curl_errno($ch) === CURLE_FILESIZE_EXCEEDED);
+
+echo "Body smaller than CURLOPT_MAXFILESIZE_LARGE\n";
+curl_setopt($ch, CURLOPT_MAXFILESIZE_LARGE, 30);
+$r = curl_exec($ch);
+var_dump(strlen($r));
+
+echo "Limit disabled by setting CURLOPT_MAXFILESIZE_LARGE to 0\n";
+curl_setopt($ch, CURLOPT_MAXFILESIZE_LARGE, 0);
+$r = curl_exec($ch);
+var_dump(strlen($r));
+
+echo "Negative value for CURLOPT_MAXFILESIZE_LARGE\n";
+$ok = curl_setopt($ch, CURLOPT_MAXFILESIZE_LARGE, -1);
+var_dump($ok);
+var_dump(curl_errno($ch) === CURLE_BAD_FUNCTION_ARGUMENT);
+
+echo "Negative value not set, CURLOPT_MAXFILESIZE_LARGE is still 0\n";
+$r = curl_exec($ch);
+var_dump(strlen($r));
+
+?>
+--EXPECTF--
+Body larger than CURLOPT_MAXFILESIZE_LARGE
+bool(false)
+bool(true)
+Body smaller than CURLOPT_MAXFILESIZE_LARGE
+int(25)
+Limit disabled by setting CURLOPT_MAXFILESIZE_LARGE to 0
+int(25)
+Negative value for CURLOPT_MAXFILESIZE_LARGE
+bool(false)
+bool(true)
+Negative value not set, CURLOPT_MAXFILESIZE_LARGE is still 0
+int(25)
diff --git a/ext/curl/tests/curl_setopt_CURLOPT_POSTREDIR.phpt b/ext/curl/tests/curl_setopt_CURLOPT_POSTREDIR.phpt
new file mode 100644
index 000000000000..71142d807bc5
--- /dev/null
+++ b/ext/curl/tests/curl_setopt_CURLOPT_POSTREDIR.phpt
@@ -0,0 +1,54 @@
+--TEST--
+Curl option CURLOPT_POSTREDIR
+--EXTENSIONS--
+curl
+--FILE--
+<?php
+include 'server.inc';
+$host = curl_cli_server_start();
+
+function do_redirect($code, $postredir_value = null) {
+    global $host;
+
+    $ch = curl_init();
+    curl_setopt($ch, CURLOPT_URL, "{$host}/get.inc?test=redirect&code={$code}");
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, 'postdata was kept in redirect');
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+    if ($postredir_value !== null) {
+        curl_setopt($ch, CURLOPT_POSTREDIR, $postredir_value);
+    }
+    return trim(curl_exec($ch));
+}
+
+$options = [null, CURL_REDIR_POST_301, CURL_REDIR_POST_302, CURL_REDIR_POST_303, CURL_REDIR_POST_ALL, 0];
+$codes = [301, 302, 303];
+
+foreach ($options as $option) {
+    foreach ($codes as $code) {
+        echo "code: $code; option ", var_export($option, true), ': ';
+        echo do_redirect($code, $option), "\n";
+    }
+}
+
+?>
+--EXPECT--
+code: 301; option NULL: string(0) ""
+code: 302; option NULL: string(0) ""
+code: 303; option NULL: string(0) ""
+code: 301; option 1: string(29) "postdata was kept in redirect"
+code: 302; option 1: string(0) ""
+code: 303; option 1: string(0) ""
+code: 301; option 2: string(0) ""
+code: 302; option 2: string(29) "postdata was kept in redirect"
+code: 303; option 2: string(0) ""
+code: 301; option 4: string(0) ""
+code: 302; option 4: string(0) ""
+code: 303; option 4: string(29) "postdata was kept in redirect"
+code: 301; option 7: string(29) "postdata was kept in redirect"
+code: 302; option 7: string(29) "postdata was kept in redirect"
+code: 303; option 7: string(29) "postdata was kept in redirect"
+code: 301; option 0: string(0) ""
+code: 302; option 0: string(0) ""
+code: 303; option 0: string(0) ""
diff --git a/ext/curl/tests/curl_setopt_CURLOPT_SSH_HOSTKEYFUNCTION.phpt b/ext/curl/tests/curl_setopt_CURLOPT_SSH_HOSTKEYFUNCTION.phpt
new file mode 100644
index 000000000000..819d7a0c98de
--- /dev/null
+++ b/ext/curl/tests/curl_setopt_CURLOPT_SSH_HOSTKEYFUNCTION.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Curl option CURLOPT_SSH_HOSTKEYFUNCTION
+--EXTENSIONS--
+curl
+--SKIPIF--
+<?php
+if (getenv('SKIP_ONLINE_TESTS')) die('skip Online test');
+$curl_version = curl_version();
+if ($curl_version['version_number'] < 0x075400) {
+    exit("skip: test works only with curl >= 7.84.0");
+}
+?>
+--FILE--
+<?php
+
+function hostkeyfunction($ch, int $keyType, string $key, int $keyLength) {
+    return CURLKHMATCH_MISMATCH;
+}
+
+// GitHub doesn't actually support SFTP, it does get far enough that the host key callback is called.
+$ch = curl_init('sftp://[email protected]/file.txt');
+curl_setopt($ch, CURLOPT_SSH_HOSTKEYFUNCTION, 'hostkeyfunction');
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+var_dump(curl_exec($ch));
+var_dump(curl_errno($ch) == CURLE_SSL_CACERT);
+
+?>
+--EXPECT--
+bool(false)
+bool(true)
diff --git a/ext/curl/tests/curl_setopt_CURLOPT_WRITEHEADER.phpt b/ext/curl/tests/curl_setopt_CURLOPT_WRITEHEADER.phpt
new file mode 100644
index 000000000000..8dbc80b4178c
--- /dev/null
+++ b/ext/curl/tests/curl_setopt_CURLOPT_WRITEHEADER.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Curl option CURLOPT_WRITEHEADER
+--EXTENSIONS--
+curl
+--FILE--
+<?php
+include 'server.inc';
+$host = curl_cli_server_start();
+
+$header_file = sys_get_temp_dir() . '/curl_setopt_CURLOPT_WRITEHEADER.tmp';
+$fp = fopen($header_file, 'w') or die('failed to open header output file');
+
+$ch = curl_init();
+curl_setopt($ch, CURLOPT_URL, "{$host}/get.inc?test=contenttype");
+curl_setopt($ch, CURLOPT_WRITEHEADER, $fp);
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+curl_exec($ch);
+
+fclose($fp);
+
+// Verify headers were written to the file
+$header_contents = file_get_contents($header_file);
+echo $header_contents, "\n";
+?>
+--CLEAN--
+<?php
+@unlink(sys_get_temp_dir() . '/curl_setopt_CURLOPT_WRITEHEADER.tmp');
+?>
+--EXPECTF--
+HTTP/1.1 200 OK
+Host: localhost:%d
+Date: %s
+Connection: close
+X-Powered-By: PHP/%s
+Content-Type: text/plain;charset=utf-8
diff --git a/ext/curl/tests/responder/get.inc b/ext/curl/tests/responder/get.inc
index 2ef1e4a89dd3..f84d4b62858f 100644
--- a/ext/curl/tests/responder/get.inc
+++ b/ext/curl/tests/responder/get.inc
@@ -49,7 +49,9 @@
     case 'redirect':
       // A 307 preserves the method and body, so libcurl must rewind the upload
       // (via CURLOPT_SEEKFUNCTION) before resending it to the new location.
-      header('Location: /get.inc?test=input', true, 307);
+      $target = $_GET['target'] ?? 'input';
+      $code = $_GET['code'] ?? 307;
+      header('Location: /get.inc?test=' . $target, true, $code);
       break;
     default:
       echo "Hello World!\n";
diff --git a/ext/ftp/tests/server.inc b/ext/ftp/tests/server.inc
index 2c7a3a8a5643..b8ac8893ea0d 100644
--- a/ext/ftp/tests/server.inc
+++ b/ext/ftp/tests/server.inc
@@ -329,7 +329,7 @@ if ($pid) {
             }
         }elseif (preg_match('/^RETR ([\/]*[\w\h]+)/', $buf, $matches)) {
             if(!empty($pasv)){
-                ;
+                $fs = $pasvs;
             }
             else if (!$fs = stream_socket_client("tcp://$host:$port")) {
                 fputs($s, "425 Can't open data connection\r\n");
@@ -449,6 +449,8 @@ if ($pid) {
 
             if (empty($bug73457)) {
                 $pasvs = stream_socket_accept($soc,10);
+            } else {
+                $pasvs = null;
             }
 
         } elseif (preg_match('/^EPSV/', $buf, $matches)) {
@@ -495,7 +497,40 @@ if ($pid) {
             fputs($s, "226 Transfer complete\r\n");
         }elseif (preg_match('/^LIST no_exists\//', $buf, $matches)) {
             fputs($s, "425 Error establishing connection\r\n");
-
+        }elseif (preg_match('/^LIST\s*$/', $buf, $matches)) {
+            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;
+            }
+            fputs($fs,
+                "drwxr-xr-x 1 owner group        0 Jan 01 00:00 .\r\n" .
+                "drwxr-xr-x 1 owner group        0 Jan 01 00:00 ..\r\n" .
+                "drwxr-xr-x 1 owner group        0 Jan 01 00:00 www\r\n" .
+                "drwxr-xr-x 1 owner group        0 Jan 01 00:00 emptydir\r\n" .
+                "-rw-r--r-- 1 owner group       33 Jan 01 00:00 file1\r\n" .
+                "-rw-r--r-- 1 owner group        8 Jan 01 00:00 file\r\n" .
+                "-rw-r--r-- 1 owner group       35 Jan 01 00:00 a story\r\n" .
+                "-rw-r--r-- 1 owner group       14 Jan 01 00:00 binary data\r\n" .
+                "-rw-r--r-- 1 owner group       13 Jan 01 00:00 fget\r\n" .
+                "-rw-r--r-- 1 owner group        5 Jan 01 00:00 fgetresume\r\n" .
+                "-rw-r--r-- 1 owner group 5368709120 Jan 01 00:00 largefile\r\n" .
+                "-rw-r--r-- 1 owner group     5100 Jan 01 00:00 mediumfile\r\n" .
+                "-rw-r--r-- 1 owner group        1 Jan 01 00:00 fget_large\r\n" .
+                "-rw-r--r-- 1 owner group     4107 Jan 01 00:00 crlf_boundary\r\n" .
+                "-rw-r--r-- 1 owner group     4096 Jan 01 00:00 bare_cr\r\n" .
+                "-rw-r--r-- 1 owner group        6 Jan 01 00:00 trailing_cr\r\n" .
+                "-rw-r--r-- 1 owner group       10 Jan 01 00:00 gh10521\r\n" .
+                "-rw-r--r-- 1 owner group       10 Jan 01 00:00 pasv\r\n" .
+                "-rw-r--r-- 1 owner group       10 Jan 01 00:00 bug73457\r\n"
+            );
+            fclose($fs);
+            fputs($s, "226 Closing data Connection.\r\n");
         }elseif (preg_match('/^REST (\d+)/', $buf, $matches)) {
             $GLOBALS['rest_pos'] = $matches[1];
             fputs($s, "350 OK\r\n");
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.