diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 525f9cd1c15f7..a540393c7365d 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -102,9 +102,16 @@ int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char * } /* }}} */ -static void _pdo_pgsql_notice(pdo_dbh_t *dbh, const char *message) /* {{{ */ +static void _pdo_pgsql_notice(void *context, const char *message) /* {{{ */ { -/* pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; */ + pdo_dbh_t * dbh = (pdo_dbh_t *)context; + zend_fcall_info_cache *fc = ((pdo_pgsql_db_handle *)dbh->driver_data)->notice_callback; + if (fc) { + zval zarg; + ZVAL_STRING(&zarg, message); + zend_call_known_fcc(fc, NULL, 1, &zarg, NULL); + zval_ptr_dtor_str(&zarg); + } } /* }}} */ @@ -125,6 +132,16 @@ static void pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *i } /* }}} */ +static void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H) /* {{{ */ +{ + if (H->notice_callback) { + zend_fcc_dtor(H->notice_callback); + efree(H->notice_callback); + H->notice_callback = NULL; + } +} +/* }}} */ + /* {{{ pdo_pgsql_create_lob_stream */ static ssize_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count) { @@ -229,6 +246,7 @@ static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ pefree(H->lob_streams, dbh->is_persistent); H->lob_streams = NULL; } + pdo_pgsql_cleanup_notice_callback(H); if (H->server) { PQfinish(H->server); H->server = NULL; @@ -1224,6 +1242,30 @@ PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) } /* }}} */ +/* {{{ proto void PDO::pgsqlSetNoticeCallback(mixed callback) + Sets a callback to receive DB notices (after client_min_messages has been set) */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback) +{ + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "F!", &fci, &fcc)) { + RETURN_THROWS(); + } + + pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + pdo_pgsql_cleanup_notice_callback(H); + + if (ZEND_FCC_INITIALIZED(fcc)) { + H->notice_callback = emalloc(sizeof(zend_fcall_info_cache)); + zend_fcc_dup(H->notice_callback, &fcc); + } +} +/* }}} */ + static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind) { switch (kind) { @@ -1341,7 +1383,7 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ goto cleanup; } - PQsetNoticeProcessor(H->server, (void(*)(void*,const char*))_pdo_pgsql_notice, (void *)&dbh); + PQsetNoticeProcessor(H->server, _pdo_pgsql_notice, (void *)dbh); H->attached = 1; H->pgoid = -1; diff --git a/ext/pdo_pgsql/pgsql_driver.stub.php b/ext/pdo_pgsql/pgsql_driver.stub.php index cf3504fbbfb83..5e09ec683b29a 100644 --- a/ext/pdo_pgsql/pgsql_driver.stub.php +++ b/ext/pdo_pgsql/pgsql_driver.stub.php @@ -33,4 +33,7 @@ public function pgsqlGetNotify(int $fetchMode = PDO::FETCH_DEFAULT, int $timeout /** @tentative-return-type */ public function pgsqlGetPid(): int {} + + /** @tentative-return-type */ + public function pgsqlSetNoticeCallback(?callable $callback): void {} } diff --git a/ext/pdo_pgsql/pgsql_driver_arginfo.h b/ext/pdo_pgsql/pgsql_driver_arginfo.h index 2b858e0a1ad1c..d38b0fb7577dc 100644 --- a/ext/pdo_pgsql/pgsql_driver_arginfo.h +++ b/ext/pdo_pgsql/pgsql_driver_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 9bb79af98dbb7c171fd9533aeabece4937a06cd2 */ + * Stub hash: 14174ab18f198b9916f83986d10c93b657d8ffb9 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) @@ -46,6 +46,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlSetNoticeCallback, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 1) +ZEND_END_ARG_INFO() + ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray); ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile); ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray); @@ -55,6 +59,7 @@ ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen); ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink); ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetNotify); ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetPid); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback); static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, ZEND_ACC_PUBLIC) @@ -66,5 +71,6 @@ static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { ZEND_ME(PDO_PGSql_Ext, pgsqlLOBUnlink, arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, ZEND_ACC_PUBLIC) ZEND_ME(PDO_PGSql_Ext, pgsqlGetNotify, arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, ZEND_ACC_PUBLIC) ZEND_ME(PDO_PGSql_Ext, pgsqlGetPid, arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlSetNoticeCallback, arginfo_class_PDO_PGSql_Ext_pgsqlSetNoticeCallback, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/pdo_pgsql/php_pdo_pgsql_int.h b/ext/pdo_pgsql/php_pdo_pgsql_int.h index ad48de384a3da..daa3357d79acb 100644 --- a/ext/pdo_pgsql/php_pdo_pgsql_int.h +++ b/ext/pdo_pgsql/php_pdo_pgsql_int.h @@ -46,6 +46,7 @@ typedef struct { bool disable_native_prepares; /* deprecated since 5.6 */ bool disable_prepares; HashTable *lob_streams; + zend_fcall_info_cache *notice_callback; } pdo_pgsql_db_handle; typedef struct { diff --git a/ext/pdo_pgsql/tests/issue78621.inc b/ext/pdo_pgsql/tests/issue78621.inc new file mode 100644 index 0000000000000..8a42a1b50cfd2 --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621.inc @@ -0,0 +1,23 @@ +beginTransaction(); +$db->exec("set client_min_messages to notice"); +$db->exec("create temporary table t (a varchar(3))"); +$db->exec("create function hey() returns trigger as \$\$ begin new.a := 'oh'; raise notice 'I tampered your data, did you know?'; return new; end; \$\$ language plpgsql"); +$db->exec("create trigger hop before insert on t for each row execute procedure hey()"); +$db->exec("insert into t values ('ah')"); +attach($db, 'Re'); +$db->exec("delete from t"); +$db->exec("insert into t values ('ah')"); +$db->pgsqlSetNoticeCallback(null); +$db->exec("delete from t"); +$db->exec("insert into t values ('ah')"); +var_dump($db->query("select * from t")->fetchAll(PDO::FETCH_ASSOC)); +echo "Done\n"; +$db->rollback(); +?> diff --git a/ext/pdo_pgsql/tests/issue78621.phpt b/ext/pdo_pgsql/tests/issue78621.phpt new file mode 100644 index 0000000000000..ee474a116cbdb --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621.phpt @@ -0,0 +1,30 @@ +--TEST-- +pgsqlSetNoticeCallback catches Postgres "raise notice". +--SKIPIF-- + +--FILE-- +pgsqlSetNoticeCallback('disp'.$prefix); +} +require dirname(__FILE__) . '/issue78621.inc'; +?> +--EXPECT-- +NOTICE: I tampered your data, did you know? +ReNOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done diff --git a/ext/pdo_pgsql/tests/issue78621_closure.phpt b/ext/pdo_pgsql/tests/issue78621_closure.phpt new file mode 100644 index 0000000000000..55165e3aebc9e --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621_closure.phpt @@ -0,0 +1,58 @@ +--TEST-- +pgsqlSetNoticeCallback catches Postgres "raise notice". +--SKIPIF-- + +--FILE-- +pgsqlSetNoticeCallback(function($message) use($prefix) { echo $prefix.trim($message)."\n"; }); + // https://github.com/php/php-src/pull/4823#pullrequestreview-335623806 + $eraseCallbackMemoryHere = (object)[1]; + break; + case 1: + $closure = function($message) use($prefix) { echo $prefix.'('.get_class($this).')'.trim($message)."\n"; }; + $db->pgsqlSetNoticeCallback($closure->bindTo(new \stdClass)); + break; + } +} +echo "Testing with a simple inline closure:\n"; +$flavor = 0; +require dirname(__FILE__) . '/issue78621.inc'; +echo "Testing with a postbound closure object:\n"; +++$flavor; +require dirname(__FILE__) . '/issue78621.inc'; +?> +--EXPECT-- +Testing with a simple inline closure: +NOTICE: I tampered your data, did you know? +ReNOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done +Testing with a postbound closure object: +(stdClass)NOTICE: I tampered your data, did you know? +Re(stdClass)NOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done diff --git a/ext/pdo_pgsql/tests/issue78621_method.phpt b/ext/pdo_pgsql/tests/issue78621_method.phpt new file mode 100644 index 0000000000000..2b3622ae4ab22 --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621_method.phpt @@ -0,0 +1,65 @@ +--TEST-- +pgsqlSetNoticeCallback catches Postgres "raise notice". +--SKIPIF-- + +--FILE-- + 'disp' ]); + echo "$method trampoline for $realMethod\n"; + return call_user_func_array([ $this, $realMethod ], $args); + } +} +$logger = new Logger(); +function attach($db, $prefix = '') +{ + global $logger; + global $flavor; + switch($flavor) + { + case 0: $db->pgsqlSetNoticeCallback([ $logger, 'disp'.$prefix ]); break; + case 1: $db->pgsqlSetNoticeCallback([ $logger, 'whatever'.$prefix ]); break; + } +} +echo "Testing with method explicitely plugged:\n"; +$flavor = 0; +require dirname(__FILE__) . '/issue78621.inc'; +echo "Testing with a bit of magic:\n"; +++$flavor; +require dirname(__FILE__) . '/issue78621.inc'; +?> +--EXPECT-- +Testing with method explicitely plugged: +NOTICE: I tampered your data, did you know? +ReNOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done +Testing with a bit of magic: +whatever trampoline for disp +NOTICE: I tampered your data, did you know? +whateverRe trampoline for dispRe +ReNOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done