Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions ext/openssl/tests/io_hooks_handshake_cancelled_accept.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
IO hooks: a TLS server handshake whose wait throws ends at once and reports only the exception
--EXTENSIONS--
openssl
--FILE--
<?php
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'io_hooks_handshake_cancelled_accept.pem.tmp';
include 'CertificateGenerator.inc';
(new CertificateGenerator())->saveNewCertAsFileWithKey('io_hooks_handshake_cancelled_accept', $certFile);

$context = stream_context_create(['ssl' => ['local_cert' => $certFile]]);
$server = stream_socket_server('ssl://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);

/* The client connects and never sends a ClientHello */
$client = stream_socket_client('tcp://' . stream_socket_get_name($server, false));

Io\Hooks\set_hooks(new class implements Io\Hooks\Hooks {
public function getCapabilities(): array { return []; }
public function add(Io\Registration $registration): void {}
public function remove(Io\Registration $registration): void {}
public function run(Io\Operation $op): Io\Completion {
return match (true) {
$op instanceof Io\Operation\Accept, $op instanceof Io\Operation\Send
=> $op->complete(Io\CompletionStatus::Unsupported),
default => throw new RuntimeException("cancelled " . $op::class),
};
}
});

try {
var_dump(stream_socket_accept($server, 2));
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}
?>
--CLEAN--
<?php
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'io_hooks_handshake_cancelled_accept.pem.tmp');
?>
--EXPECT--
cancelled Io\Operation\Recv
31 changes: 31 additions & 0 deletions ext/openssl/tests/io_hooks_handshake_cancelled_client.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
IO hooks: a TLS client handshake whose wait throws ends at once and reports only the exception
--EXTENSIONS--
openssl
--FILE--
<?php
/* The server accepts nothing and never answers the ClientHello */
$server = stream_socket_server('tcp://127.0.0.1:0');
$address = stream_socket_get_name($server, false);

Io\Hooks\set_hooks(new class implements Io\Hooks\Hooks {
public function getCapabilities(): array { return []; }
public function add(Io\Registration $registration): void {}
public function remove(Io\Registration $registration): void {}
public function run(Io\Operation $op): Io\Completion {
return match (true) {
$op instanceof Io\Operation\Connect, $op instanceof Io\Operation\Poll => $op->completeReady($op->getEvents()),
$op instanceof Io\Operation\Send => $op->complete(Io\CompletionStatus::Unsupported),
default => throw new RuntimeException("cancelled " . $op::class),
};
}
});

try {
var_dump(stream_socket_client("tls://$address", $errno, $errstr, 2));
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
cancelled Io\Operation\Recv
4 changes: 0 additions & 4 deletions ext/openssl/tests/session_resumption_invalid_data.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,4 @@ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'session_invalid.pem.tmp');
?>
--EXPECTF--

Warning: stream_socket_client(): Failed to enable crypto in %s on line %d

Warning: stream_socket_client(): Unable to connect to %s in %s on line %d
Type error thrown: session_data must be an Openssl\Session instance
14 changes: 12 additions & 2 deletions ext/openssl/xp_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -3188,6 +3188,7 @@ static int php_openssl_enable_crypto(php_stream *stream,
sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout));
}
sslsock->deadline = blocked ? &deadline : NULL;
sslsock->io_cancelled = 0;

do {
ERR_clear_error();
Expand All @@ -3196,6 +3197,11 @@ static int php_openssl_enable_crypto(php_stream *stream,
break;
}
retry = php_openssl_handle_ssl_error(stream, n, blocked);
if (UNEXPECTED(sslsock->io_cancelled)) {
/* A cancelled wait comes back as a retry; retrying would spin until the deadline */
sslsock->deadline = NULL;
return -1;
}
if (retry && php_deadline_to_timeout_ms(&deadline) == 0) {
sslsock->deadline = NULL;
php_stream_warn(stream, TimeOut, "SSL: Handshake timed out");
Expand Down Expand Up @@ -3551,7 +3557,9 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_
if (php_stream_xport_crypto_setup(xparam->outputs.client, clisockdata->method,
sock->ctx ? stream : NULL) < 0 || php_stream_xport_crypto_enable(
xparam->outputs.client, 1) < 0) {
php_stream_warn(stream, ProtocolError, "Failed to enable crypto");
if (EXPECTED(!EG(exception))) {
php_stream_warn(stream, ProtocolError, "Failed to enable crypto");
}

php_stream_close(xparam->outputs.client);
xparam->outputs.client = NULL;
Expand Down Expand Up @@ -3765,7 +3773,9 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val

if (php_stream_xport_crypto_setup(stream, sslsock->method, session_stream) < 0 ||
php_stream_xport_crypto_enable(stream, 1) < 0) {
php_stream_warn(stream, ProtocolError, "Failed to enable crypto");
if (EXPECTED(!EG(exception))) {
php_stream_warn(stream, ProtocolError, "Failed to enable crypto");
}
xparam->outputs.returncode = -1;
}
}
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/fsock.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
if (port > 0) {
efree(hostname);
}
if (stream == NULL) {
if (stream == NULL && EXPECTED(!EG(exception))) {
php_error_docref(NULL, E_WARNING, "Unable to connect to %s:" ZEND_LONG_FMT " (%s)", host, port, errstr == NULL ? "Unknown error" : ZSTR_VAL(errstr));
}

Expand Down
12 changes: 8 additions & 4 deletions ext/standard/streamsfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ PHP_FUNCTION(stream_socket_client)

php_stream_error_operation_end(context);

if (stream == NULL) {
if (stream == NULL && EXPECTED(!EG(exception))) {
/* host might contain binary characters */
zend_string *quoted_host = php_addslashes(host);

Expand Down Expand Up @@ -248,7 +248,7 @@ PHP_FUNCTION(stream_socket_server)

php_stream_error_operation_end(context);

if (stream == NULL) {
if (stream == NULL && EXPECTED(!EG(exception))) {
php_error_docref(NULL, E_WARNING, "Unable to connect to %s (%s)", host, errstr == NULL ? "Unknown error" : ZSTR_VAL(errstr));
}

Expand Down Expand Up @@ -329,7 +329,9 @@ PHP_FUNCTION(stream_socket_accept)
if (peername) {
zend_string_release(peername);
}
php_stream_warn(stream, AcceptFailed, "Accept failed: %s", errstr ? ZSTR_VAL(errstr) : "Unknown error");
if (EXPECTED(!EG(exception))) {
php_stream_warn(stream, AcceptFailed, "Accept failed: %s", errstr ? ZSTR_VAL(errstr) : "Unknown error");
}
RETVAL_FALSE;
}

Expand Down Expand Up @@ -393,7 +395,9 @@ PHP_FUNCTION(stream_socket_sendto)
/* parse the address */
if (FAILURE == php_network_parse_network_address_with_port(target_addr, target_addr_len, (struct sockaddr*)&sa, &sl)) {
php_stream_error_operation_end_for_stream(stream);
php_error_docref(NULL, E_WARNING, "Failed to parse `%s' into a valid network address", target_addr);
if (EXPECTED(!EG(exception))) {
php_error_docref(NULL, E_WARNING, "Failed to parse `%s' into a valid network address", target_addr);
}
RETURN_FALSE;
}
}
Expand Down
19 changes: 19 additions & 0 deletions ext/standard/tests/streams/hooks/accept-cancelled-warns.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
IO hooks: stream_socket_accept() whose operation completes Cancelled with no exception still warns
--FILE--
<?php
$server = stream_socket_server('tcp://127.0.0.1:0');
Io\Hooks\set_hooks(new class implements Io\Hooks\Hooks {
public function getCapabilities(): array { return []; }
public function add(Io\Registration $registration): void {}
public function remove(Io\Registration $registration): void {}
public function run(Io\Operation $op): Io\Completion {
return $op->complete(Io\CompletionStatus::Cancelled);
}
});

var_dump(stream_socket_accept($server, 5));
?>
--EXPECTF--
Warning: stream_socket_accept(): Accept failed: %s in %s on line %d
bool(false)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
IO hooks: stream_socket_accept() whose operation throws, on a listener in the exception mode, throws that exception
--FILE--
<?php
$context = stream_context_create(['stream' => ['error_mode' => StreamErrorMode::Exception]]);
$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);
require __DIR__ . '/provider-throws.inc';

try {
var_dump(stream_socket_accept($server, 5));
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
?>
--EXPECT--
RuntimeException: cancelled Io\Operation\Accept
15 changes: 15 additions & 0 deletions ext/standard/tests/streams/hooks/accept-throws.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
IO hooks: stream_socket_accept() whose operation throws reports only the exception
--FILE--
<?php
$server = stream_socket_server('tcp://127.0.0.1:0');
require __DIR__ . '/provider-throws.inc';

try {
var_dump(stream_socket_accept($server, 5));
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
cancelled Io\Operation\Accept
20 changes: 20 additions & 0 deletions ext/standard/tests/streams/hooks/client-lookup-fails-warns.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
IO hooks: stream_socket_client() whose name lookup finds no address with no exception still warns
--FILE--
<?php
Io\Hooks\set_hooks(new class implements Io\Hooks\Hooks {
public function getCapabilities(): array { return []; }
public function add(Io\Registration $registration): void {}
public function remove(Io\Registration $registration): void {}
public function run(Io\Operation $op): Io\Completion {
return $op->completeWithAddresses([]);
}
});

var_dump(stream_socket_client('tcp://nowhere.provider:80', $errno, $errstr, 5));
?>
--EXPECTF--
Warning: stream_socket_client(): php_network_getaddresses: getaddrinfo for nowhere.provider failed: %s in %s on line %d

Warning: stream_socket_client(): Unable to connect to tcp://nowhere.provider:80 (php_network_getaddresses: getaddrinfo for nowhere.provider failed: %s) in %s on line %d
bool(false)
14 changes: 14 additions & 0 deletions ext/standard/tests/streams/hooks/client-lookup-throws.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
IO hooks: stream_socket_client() whose name lookup throws reports only the exception
--FILE--
<?php
require __DIR__ . '/provider-throws.inc';

try {
var_dump(stream_socket_client('tcp://localhost:80', $errno, $errstr, 5));
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
cancelled Io\Operation\GetAddrInfo
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
IO hooks: a connect whose operation throws tries no further address
--FILE--
<?php
$server = stream_socket_server('tcp://127.0.0.1:0');
$port = (int) substr(strrchr(stream_socket_get_name($server, false), ':'), 1);

Io\Hooks\set_hooks(new class implements Io\Hooks\Hooks {
public function getCapabilities(): array { return []; }
public function add(Io\Registration $registration): void {}
public function remove(Io\Registration $registration): void {}
public function run(Io\Operation $op): Io\Completion {
if ($op instanceof Io\Operation\GetAddrInfo) {
return $op->completeWithAddresses(['127.0.0.1', '127.0.0.1']);
}
throw new RuntimeException("cancelled " . $op::class);
}
});

try {
var_dump(stream_socket_client("tcp://two.addresses:$port", $errno, $errstr, 5));
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}
Io\Hooks\set_hooks(null);
/* Each attempt's connect() reaches the listener even when the provider throws, so a second attempt
* would show as a second connection; the first may close before it is accepted */
$accepted = 0;
while (@stream_socket_accept($server, 0.2)) {
$accepted++;
}
var_dump($accepted <= 1);
?>
--EXPECTREGEX--
cancelled Io\\Operation\\(Connect|Poll)
bool\(true\)
16 changes: 16 additions & 0 deletions ext/standard/tests/streams/hooks/connect-throws.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
IO hooks: stream_socket_client() whose connect throws reports only the exception
--FILE--
<?php
$server = stream_socket_server('tcp://127.0.0.1:0');
$address = stream_socket_get_name($server, false);
require __DIR__ . '/provider-throws.inc';

try {
var_dump(stream_socket_client("tcp://$address", $errno, $errstr, 5));
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTREGEX--
cancelled Io\\Operation\\(Connect|Poll)
16 changes: 16 additions & 0 deletions ext/standard/tests/streams/hooks/fsockopen-throws.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
IO hooks: fsockopen() whose connect throws reports only the exception
--FILE--
<?php
$server = stream_socket_server('tcp://127.0.0.1:0');
[$host, $port] = explode(':', stream_socket_get_name($server, false));
require __DIR__ . '/provider-throws.inc';

try {
var_dump(fsockopen($host, (int) $port, $errno, $errstr, 5));
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTREGEX--
cancelled Io\\Operation\\(Connect|Poll)
11 changes: 11 additions & 0 deletions ext/standard/tests/streams/hooks/provider-throws.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

/* A provider whose every operation throws, as one would for a cancelled coroutine */
Io\Hooks\set_hooks(new class implements Io\Hooks\Hooks {
public function getCapabilities(): array { return []; }
public function add(Io\Registration $registration): void {}
public function remove(Io\Registration $registration): void {}
public function run(Io\Operation $op): Io\Completion {
throw new RuntimeException("cancelled " . $op::class);
}
});
15 changes: 15 additions & 0 deletions ext/standard/tests/streams/hooks/sendto-lookup-throws.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
IO hooks: stream_socket_sendto() whose name lookup throws reports only the exception
--FILE--
<?php
$socket = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
require __DIR__ . '/provider-throws.inc';

try {
var_dump(stream_socket_sendto($socket, 'x', 0, 'localhost:9'));
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
cancelled Io\Operation\GetAddrInfo
14 changes: 14 additions & 0 deletions ext/standard/tests/streams/hooks/server-lookup-throws.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
IO hooks: stream_socket_server() whose name lookup throws reports only the exception
--FILE--
<?php
require __DIR__ . '/provider-throws.inc';

try {
var_dump(stream_socket_server('tcp://localhost:0'));
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
cancelled Io\Operation\GetAddrInfo
Loading
Loading