Commit c282f04efd9 for php.net

commit c282f04efd90d7bc42d1951dc84188be3039bf41
Author: Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
Date:   Sat Aug 29 16:34:51 2026 +0200

    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

diff --git a/ext/curl/tests/curl_curlfile_seek.phpt b/ext/curl/tests/curl_curlfile_seek.phpt
new file mode 100644
index 00000000000..8ebaab92bc4
--- /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 00000000000..a3f1bce6076
--- /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 00000000000..7ce81471775
--- /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 00000000000..1627c03f2ba
--- /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 00000000000..c3136f95883
--- /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 00000000000..3b11c0da266
--- /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 00000000000..71142d807bc
--- /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 00000000000..819d7a0c98d
--- /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://php@github.com/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 00000000000..8dbc80b4178
--- /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 2ef1e4a89dd..f84d4b62858 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 2c7a3a8a564..b8ac8893ea0 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");