When the server never opens the active-mode data connection, my_accept()
fails and data_accept() frees the databuf_t. ftp_getdata() has already
stored it in ftp->data, so the caller's bail path then runs data_close(),
which frees it a second time. This affects ftp_get(), ftp_put(),
ftp_append(), ftp_nb_get(), ftp_nb_put() and the listing functions.
data_accept() does not own the buffer: leave it in ftp->data and let
data_close() release it, as the SSL failure paths in data_accept()
already do.
Closes GH-24339.
Correct typographical and grammatical mistakes across docs, comments, and build tools. Refactor local identifiers childrens to children in RecursiveFilterIterator and sav to saved_char in sapi_header_add_op.
* ftp: use SSL_write_ex() in single_send()
Replace SSL_write() with SSL_write_ex() and pass its return value
to SSL_get_error(). This preserves the original API contract and
avoids signed/unsigned conversion issues when handling errors.
Signed-off-by: Denis Sergeev <zeff@altlinux.org>
* ftp: widen single_send() return type to ssize_t
* ftp: initialize sent before SSL_write_ex()
---------
Signed-off-by: Denis Sergeev <zeff@altlinux.org>
Co-authored-by: Denis Sergeev <zeff@altlinux.org>
* ext/ftp: throw an Error when the connection is already transferring
ftp_nb_fget() and ftp_nb_fput() answered an already busy connection with a
warning and false, against a declared int return type. Reaching that guard
means a transfer was started from inside another transfer, which is a
programming mistake, so throw an Error instead, as ftp_close() already does
on the same in_use flag. The declarations stay int.
The check stays ahead of the direction and closestream writes, so the
running transfer is left untouched. The test reaches the guard through a
stream wrapper that calls back into the extension mid transfer.
* [skip ci] Note the ftp_nb_fget()/ftp_nb_fput() Error in UPGRADING
* Address review: extend the Error to ftp_nb_get() and ftp_nb_put()
The in_use guard is a programming error in all four non-blocking transfer
functions, so all four now throw the same Error instead of two of them
emitting a warning and returning false. ftp_nb_get() and ftp_nb_put() keep
their int|false declaration, which is still returned when the local file
cannot be opened.
ftp_nb_put() closes the local stream before throwing, and the guards stay
ahead of the direction/closestream writes, so a rejected re-entrant call
leaves the running transfer untouched.
ftp_nb_get_during_transfer.phpt and ftp_nb_get_during_nb_transfer.phpt
asserted the old warning; they now record the Error and still assert that
the outer transfer completes.
* Catch Throwable in the re-entrant transfer test
The test asserts the class it prints, so it must not presume Error in the
catch: a change of thrown class has to fail the test rather than escape it.
Headers required by the generated code (zend_attributes.h, zend_constants.h, zend_enum.h) are collected during generation and emitted as includes at the top of the arginfo file, with the same preprocessor conditions and PHP version guards as the code that needs them.
Removes the manual zend_attributes.h and zend_enum.h includes from sources that only needed them for the arginfo file they include.
* ext/bz2: applied fixers to improve test robustness
* ext/calendar: applied fixers to improve test robustness
* ext/exif: applied fixers to improve test robustness
* ext/fileinfo: applied fixers to improve test robustness
* ext/filter: applied fixers to improve test robustness
* ext/ftp: applied fixers to improve test robustness
* ext/hash: applied fixers to improve test robustness
* ext/iconv: applied fixers to improve test robustness
* ext/ldap: applied fixers to improve test robustness
* ext/pcre: applied fixers to improve test robustness
* ext/posix: applied fixers to improve test robustness
* ext/readline: applied fixers to improve test robustness
* ext/shmop: applied fixers to improve test robustness
* ext/snmp: applied fixers to improve test robustness
* ext/sodium: applied fixers to improve test robustness
* ext/sqlite3: applied fixers to improve test robustness
* ext/sysvmsg: applied fixers to improve test robustness
* ext/sysvsem: applied fixers to improve test robustness
* ext/sysvshm: applied fixers to improve test robustness
* ext/tidy: applied fixers to improve test robustness
* ext/tokenizer: applied fixers to improve test robustness
* ext/zend_test: applied fixers to improve test robustness
* ext/zlib: applied fixers to improve test robustness
* ftp: For single fd poll calls, use php_pollfd_for_ms
* pgsql: Use php_pollfd_for_ms in compat shim
This is only used on pre-libpq 17.
* fastcgi: Use php_pollfd_for_ms for single fd
This also handles the case where poll isn't available (does that even
matter nowadays?)
* io: Use php_pollfd_for_ms for single fd poll
* cli: Convert to php_pollfd_for from select
This is not a poll call, but it is functionally the same as one; convert
it to the single fd polling function which is clearer to read.
The self-signed certificate the FTP test server presents
(ext/ftp/tests/server.inc) was generated in 2016 with a ten-year
validity and expired on 2026-07-23, so ext/ftp/tests and the ftps://
stream tests (ext/standard/tests/streams/opendir-003 and -004) fail the
TLS handshake with "certificate expired" on every branch. Regenerate it
as a self-signed CN=127.0.0.1 certificate that expires 2099-12-31, so
the suite does not silently break again on a ten-year cadence.
GH-20601 capped the timeout in ftp_connect() so a value that overflows the
timeval conversion cannot reach php_network_set_limit_time(). ftp_ssl_connect()
and ftp_set_option(FTP_TIMEOUT_SEC) kept rejecting only non-positive values, so
PHP_INT_MAX still reached ftp_open() and my_poll() through them.
Apply the same bound to both, factored into a shared PHP_FTP_TIMEOUT_SEC_MAX
macro. ftp_ssl_connect() with PHP_INT_MAX hung before this rather than
reporting the bad argument.
Closes GH-22767
php_ftp_dirstream_read() sized the basename copy with
MIN(sizeof(ent->d_name), ZSTR_LEN(basename) - 1) and wrote the terminator at
[tmp_len - 1]. An empty listing line leaves a basename of length 1, so
tmp_len is 0 and the NUL lands one byte before d_name; a slash-only line
leaves length 0, so the subtraction underflows to SIZE_MAX and memcpy() reads
4096 bytes past the interned empty string. The same off-by-one truncated
entry names that do not end in CRLF.
Closes GH-22768
A user stream wrapper passed to a blocking or non-blocking FTP transfer can
re-enter the engine from its stream_read/stream_write handler while the
engine still holds the freed ftpbuf_t/databuf_t on the C stack, so the
transfer resumes on freed memory. Guard every operation that opens a data
connection with an in_use flag: ftp_close() throws while it is set, and the
transfer and listing functions refuse to run. The non-blocking checks sit
at the userland entry, before the wrapper mutates ftp->direction/stream, so
a rejected re-entrant call cannot corrupt the outer transfer; the nb cleanup
clears ftp->stream before closing the stream so a stream_close() that calls
ftp_close() cannot double-close it.
Closes GH-22400
The bug80901 fix (09696eee9d) terminates an over-long response with
*data = 0, but when the line fills the whole FTP_BUFSIZE inbuf without a
CR/LF, data points at inbuf[FTP_BUFSIZE] and the terminator is written one
byte past the buffer, into the adjacent ftpbuf_t::extra field. Reserve the
final byte for the terminator so it always lands inside inbuf. A
buffer-filling response loses its last character (bug80901's SYST reply is
now 4095 visible chars, with the terminator taking the 4096th slot).
Closes GH-22377
In ASCII mode ftp_get() stripped every '\r' and emitted only a following
'\n', dropping bare CR bytes not part of a CRLF sequence. Fold CRLF to
LF but write a lone '\r' through unchanged, carrying a '\r' on the final
byte of a read into the next read and flushing it at EOF, so the buffer
boundary behaves the same as the in-buffer case. ftp_nb_get() already
does this via the lastch carry in ftp_nb_continue_read().
In ASCII mode ftp_get() peeks at the byte after each '\r' to fold CRLF
into LF. When that '\r' is the last byte of a full FTP_BUFSIZE read, the
lookahead reads one byte past the buffer; a server placing '\r' at
offset 4095 of a 4096-byte read hits it (ASAN: heap-buffer-overflow
read of size 1). Bound the lookahead to the received data, as
ftp_readline() does. ftp_nb_continue_read() carries the trailing '\r'
across reads and isn't affected.
Closes GH-22328