Skip to content
31 changes: 26 additions & 5 deletions ext/ftp/ftp.c
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,7 @@ bool ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_
goto bail;
}

bool pending_cr = false;
while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
if (rcvd == (size_t)-1) {
goto bail;
Expand All @@ -869,13 +870,30 @@ bool ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_
php_stream_write(outstream, ptr, (e - ptr));
ptr = e;
#else
while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) {
php_stream_write(outstream, ptr, (s - ptr));
if (s + 1 < e && *(s + 1) == '\n') {
s++;
if (pending_cr) {
pending_cr = false;
if (*ptr == '\n') {
php_stream_putc(outstream, '\n');
ptr++;
} else {
php_stream_putc(outstream, '\r');
}
}
while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) {
if (s + 1 < e) {
if (*(s + 1) == '\n') {
php_stream_write(outstream, ptr, (s - ptr));
php_stream_putc(outstream, '\n');
ptr = s + 2;
} else {
php_stream_write(outstream, ptr, (s - ptr) + 1);
ptr = s + 1;
}
} else {
php_stream_write(outstream, ptr, (s - ptr));
pending_cr = true;
ptr = s + 1;
}
ptr = s + 1;
}
#endif
if (ptr < e) {
Expand All @@ -885,6 +903,9 @@ bool ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_
goto bail;
}
}
if (pending_cr) {
php_stream_putc(outstream, '\r');
}

data_close(ftp);

Expand Down
32 changes: 32 additions & 0 deletions ext/ftp/tests/ftp_get_ascii_bare_cr.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
ftp_get() ASCII mode: bare CR is preserved, CRLF folds to LF
--EXTENSIONS--
ftp
pcntl
--FILE--
<?php
require 'server.inc';

$ftp = ftp_connect('127.0.0.1', $port);
ftp_login($ftp, 'user', 'pass');
$ftp or die("Couldn't connect to the server");

$local = __DIR__ . "/bare_cr_out.txt";

$expected = "line1\nba\rre\nend" . str_repeat("X", 4078) . "\r" . str_repeat("Y", 10);

var_dump(ftp_get($ftp, $local, 'bare_cr', FTP_ASCII));
var_dump(file_get_contents($local) === $expected);

var_dump(ftp_get($ftp, $local, 'trailing_cr', FTP_ASCII));
var_dump(file_get_contents($local) === "trail\r");
?>
--CLEAN--
<?php
@unlink(__DIR__ . "/bare_cr_out.txt");
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
14 changes: 14 additions & 0 deletions ext/ftp/tests/server.inc
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,20 @@ if ($pid) {
fputs($fs, str_repeat("A", 4095) . "\r\n" . str_repeat("B", 10));
fputs($s, "226 Closing data Connection.\r\n");
break;
case "bare_cr":
// A bare CR (not part of CRLF) mid-stream, plus a bare CR on
// the final byte of the first FTP_BUFSIZE (4096) read followed
// by a non-LF byte in the next read.
fputs($s, "150 File status okay; about to open data connection.\r\n");
fputs($fs, "line1\r\nba\rre\r\nend" . str_repeat("X", 4078) . "\r" . str_repeat("Y", 10));
fputs($s, "226 Closing data Connection.\r\n");
break;
case "trailing_cr":
// The whole transfer ends on a bare CR.
fputs($s, "150 File status okay; about to open data connection.\r\n");
fputs($fs, "trail\r");
fputs($s, "226 Closing data Connection.\r\n");
break;

default:
fputs($s, "550 {$matches[1]}: No such file or directory \r\n");
Expand Down
1 change: 1 addition & 0 deletions ext/snmp/snmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,7 @@ static bool snmp_session_init(php_snmp_session **session_p, int version, zend_st

if (strlen(session->peername) == 0) {
php_error_docref(NULL, E_WARNING, "Unknown failure while resolving '%s'", ZSTR_VAL(hostname));
php_network_freeaddresses(psal);
return false;
}
/* XXX FIXME
Expand Down
16 changes: 16 additions & 0 deletions ext/snmp/tests/snmp_resolve_fail_leak.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
snmp: resolved address list is freed when peername resolution drains empty
--EXTENSIONS--
snmp
--FILE--
<?php
// Bracketed (IPv6) syntax over an IPv4-only literal forces IPv6 resolution,
// which drains to no usable address, hitting the "Unknown failure while
// resolving" path. That path must free the address list from
// php_network_getaddresses() (it previously leaked it). No SNMP agent needed:
// the function fails at peername resolution before any query is sent.
var_dump(snmpget("[127.0.0.1]", "public", ".1.3.6.1.2.1.1.1.0"));
?>
--EXPECTF--
Warning: snmpget(): Unknown failure while resolving '[127.0.0.1]' in %s on line %d
bool(false)
2 changes: 1 addition & 1 deletion ext/sockets/conversions.c
Original file line number Diff line number Diff line change
Expand Up @@ -1398,7 +1398,7 @@ static void from_zval_write_fd_array_aux(zval *elem, unsigned i, void **args, se
return;
}

iarr[i] = sock->bsd_socket;
iarr[i - 1] = sock->bsd_socket;
return;
}

Expand Down
2 changes: 1 addition & 1 deletion ext/sockets/tests/socket_cmsg_rights.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ if ($data["control"]) {
if ($control["level"] == SOL_SOCKET &&
$control["type"] == SCM_RIGHTS) {
foreach ($control["data"] as $resource) {
if (!is_resource($resource)) {
if (!is_resource($resource) && !($resource instanceof Socket)) {
echo "FAIL RES\n";
var_dump($data);
exit;
Expand Down
77 changes: 77 additions & 0 deletions ext/sockets/tests/socket_sendmsg_scm_rights_object.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
--TEST--
socket_sendmsg(): SCM_RIGHTS transfers Socket objects with the correct fds
--EXTENSIONS--
sockets
--SKIPIF--
<?php
if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
die('skip not for Microsoft Windows');
}
if (strtolower(substr(PHP_OS, 0, 3)) == 'aix') {
die('skip not for AIX');
}
if (!defined('SCM_RIGHTS')) {
die('skip SCM_RIGHTS not available');
}
?>
--FILE--
<?php
$dir = sys_get_temp_dir();
$rpath = $dir . "/socket_sendmsg_scm_rights_object_r.sock";
$ppath1 = $dir . "/socket_sendmsg_scm_rights_object_p1.sock";
$ppath2 = $dir . "/socket_sendmsg_scm_rights_object_p2.sock";
@unlink($rpath);
@unlink($ppath1);
@unlink($ppath2);

$recv = socket_create(AF_UNIX, SOCK_DGRAM, 0);
socket_bind($recv, $rpath);

$send = socket_create(AF_UNIX, SOCK_DGRAM, 0);
socket_connect($send, $rpath);

// Two Socket objects in a single SCM_RIGHTS message, each bound to a known
// path so the received fds can be identified and ordered via getsockname().
$payload1 = socket_create(AF_UNIX, SOCK_DGRAM, 0);
socket_bind($payload1, $ppath1);
$payload2 = socket_create(AF_UNIX, SOCK_DGRAM, 0);
socket_bind($payload2, $ppath2);

socket_sendmsg($send, [
'iov' => ['x'],
'control' => [[
'level' => SOL_SOCKET,
'type' => SCM_RIGHTS,
'data' => [$payload1, $payload2],
]],
], 0);

$data = [
'name' => [],
'buffer_size' => 64,
'controllen' => socket_cmsg_space(SOL_SOCKET, SCM_RIGHTS, 2),
];
socket_recvmsg($recv, $data, 0);

$got = $data['control'][0]['data'];
var_dump(count($got));
var_dump($got[0] instanceof Socket);
var_dump($got[1] instanceof Socket);
socket_getsockname($got[0], $addr0);
socket_getsockname($got[1], $addr1);
var_dump($addr0 === $ppath1);
var_dump($addr1 === $ppath2);
?>
--CLEAN--
<?php
$dir = sys_get_temp_dir();
@unlink($dir . "/socket_sendmsg_scm_rights_object_r.sock");
@unlink($dir . "/socket_sendmsg_scm_rights_object_p1.sock");
@unlink($dir . "/socket_sendmsg_scm_rights_object_p2.sock");
?>
--EXPECT--
int(2)
bool(true)
bool(true)
bool(true)
bool(true)
49 changes: 5 additions & 44 deletions ext/standard/io_poll.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,32 +85,12 @@ static inline void php_io_poll_throw_failed_operation(
/* Event enum to bit mask mapping */
static uint32_t php_io_poll_event_enum_to_bit(zend_object *event_enum)
{
zval *case_name = zend_enum_fetch_case_name(event_enum);
const char *name = Z_STRVAL_P(case_name);

if (strcmp(name, "Read") == 0) {
return PHP_POLL_READ;
} else if (strcmp(name, "Write") == 0) {
return PHP_POLL_WRITE;
} else if (strcmp(name, "Error") == 0) {
return PHP_POLL_ERROR;
} else if (strcmp(name, "HangUp") == 0) {
return PHP_POLL_HUP;
} else if (strcmp(name, "ReadHangUp") == 0) {
return PHP_POLL_RDHUP;
} else if (strcmp(name, "OneShot") == 0) {
return PHP_POLL_ONESHOT;
} else if (strcmp(name, "EdgeTriggered") == 0) {
return PHP_POLL_ET;
}

return 0;
return 1 << (zend_enum_fetch_case_id(event_enum) - 1);
}

static uint32_t php_io_poll_event_enums_to_events(zval *event_enums)
{
HashTable *ht;
zval *entry;
uint32_t events = 0;

if (Z_TYPE_P(event_enums) != IS_ARRAY) {
Expand All @@ -119,7 +99,7 @@ static uint32_t php_io_poll_event_enums_to_events(zval *event_enums)

ht = Z_ARRVAL_P(event_enums);

ZEND_HASH_FOREACH_VAL(ht, entry) {
ZEND_HASH_FOREACH_VAL(ht, zval *entry) {
if (Z_TYPE_P(entry) != IS_OBJECT
|| !instanceof_function(Z_OBJCE_P(entry), php_io_poll_event_class_entry)) {
return 0;
Expand Down Expand Up @@ -179,24 +159,7 @@ static zend_result php_io_poll_events_to_event_enums(uint32_t events, zval *even
/* Backend enum name to backend type mapping */
static php_poll_backend_type php_io_poll_backend_enum_to_type(zend_object *backend_enum)
{
zval *case_name = zend_enum_fetch_case_name(backend_enum);
const char *name = Z_STRVAL_P(case_name);

if (strcmp(name, "Auto") == 0) {
return PHP_POLL_BACKEND_AUTO;
} else if (strcmp(name, "Poll") == 0) {
return PHP_POLL_BACKEND_POLL;
} else if (strcmp(name, "Epoll") == 0) {
return PHP_POLL_BACKEND_EPOLL;
} else if (strcmp(name, "Kqueue") == 0) {
return PHP_POLL_BACKEND_KQUEUE;
} else if (strcmp(name, "EventPorts") == 0) {
return PHP_POLL_BACKEND_EVENTPORT;
} else if (strcmp(name, "WSAPoll") == 0) {
return PHP_POLL_BACKEND_WSAPOLL;
}

return PHP_POLL_BACKEND_AUTO;
return zend_enum_fetch_case_id(backend_enum) - 2;
}

static const char *php_io_poll_backend_type_to_name(php_poll_backend_type type)
Expand Down Expand Up @@ -333,8 +296,7 @@ static void php_io_poll_context_free_object(zend_object *obj)
php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(obj);

if (intern->watchers) {
zval *zv;
ZEND_HASH_FOREACH_VAL(intern->watchers, zv) {
ZEND_HASH_FOREACH_VAL(intern->watchers, zval *zv) {
php_io_poll_watcher_object *watcher = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(Z_OBJ_P(zv));
watcher->active = false;
watcher->poll_ctx = NULL;
Expand Down Expand Up @@ -373,8 +335,7 @@ static HashTable *php_io_poll_context_get_gc(zend_object *obj, zval **table, int
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();

if (intern->watchers) {
zval *zv;
ZEND_HASH_FOREACH_VAL(intern->watchers, zv) {
ZEND_HASH_FOREACH_VAL(intern->watchers, zval *zv) {
zend_get_gc_buffer_add_zval(gc_buffer, zv);
} ZEND_HASH_FOREACH_END();
}
Expand Down
7 changes: 6 additions & 1 deletion ext/standard/io_poll.stub.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
<?php

/** @generate-class-entries */
/**
* @generate-class-entries
* @generate-c-enums
*/

namespace Io {
class IoException extends \Exception {}
}

namespace Io\Poll {

// Keep in sync with main/php_poll.h!
enum Backend
{
case Auto;
Expand All @@ -25,6 +29,7 @@ public function isAvailable(): bool {}
public function supportsEdgeTriggering(): bool {}
}

// Keep in sync with main/php_poll.h!
enum Event {
case Read;
case Write;
Expand Down
3 changes: 2 additions & 1 deletion ext/standard/io_poll_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions ext/standard/io_poll_decl.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading