diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 63eb38c7590f3..f69dcdf11cfc4 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -103,7 +103,23 @@ 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) /* {{{ */ { -/* pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; */ + int ret; + zval zarg; + zval retval; + pdo_pgsql_fci * fc; + if ((fc = ((pdo_pgsql_db_handle *)dbh->driver_data)->notice_callback)) { + ZVAL_STRINGL(&zarg, (char *) message, strlen(message)); + fc->fci.param_count = 1; + fc->fci.params = &zarg; + fc->fci.retval = &retval; + if ((ret = zend_call_function(&fc->fci, &fc->fcc)) != FAILURE) { + zval_ptr_dtor(&retval); + } + zval_ptr_dtor(&zarg); + if (ret == FAILURE) { + pdo_raise_impl_error(dbh, NULL, "HY000", "could not call user-supplied function"); + } + } } /* }}} */ @@ -121,6 +137,16 @@ static int pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *in } /* }}} */ +static void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H) /* {{{ */ +{ + if (H->notice_callback) { + zval_ptr_dtor(&H->notice_callback->fci.function_name); + 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) { @@ -203,6 +229,7 @@ static int pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ { pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; if (H) { + pdo_pgsql_cleanup_notice_callback(H); if (H->server) { PQfinish(H->server); H->server = NULL; @@ -1128,6 +1155,53 @@ static PHP_METHOD(PDO, pgsqlGetPid) } /* }}} */ +/* {{{ proto bool PDO::pgsqlSetNoticeCallback(mixed callback) + Sets a callback to receive DB notices (after client_min_messages has been set) */ +static PHP_METHOD(PDO, pgsqlSetNoticeCallback) +{ + zval *callback; + zend_string *cbname; + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + pdo_pgsql_fci *fc; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callback)) { + RETURN_FALSE; + } + + dbh = Z_PDO_DBH_P(getThis()); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (Z_TYPE_P(callback) == IS_NULL) { + pdo_pgsql_cleanup_notice_callback(H); + RETURN_TRUE; + } else { + if (!(fc = H->notice_callback)) { + fc = (pdo_pgsql_fci*)ecalloc(1, sizeof(pdo_pgsql_fci)); + } else { + zval_ptr_dtor(&fc->fci.function_name); + memcpy(&fc->fcc, &empty_fcall_info_cache, sizeof(fc->fcc)); + } + + if (FAILURE == zend_fcall_info_init(callback, 0, &fc->fci, &fc->fcc, &cbname, NULL)) { + php_error_docref(NULL, E_WARNING, "function '%s' is not callable", ZSTR_VAL(cbname)); + zend_string_release_ex(cbname, 0); + efree(fc); + H->notice_callback = NULL; + RETURN_FALSE; + } + Z_TRY_ADDREF_P(&fc->fci.function_name); + zend_string_release_ex(cbname, 0); + + H->notice_callback = fc; + + RETURN_TRUE; + } +} +/* }}} */ + static const zend_function_entry dbh_methods[] = { PHP_ME(PDO, pgsqlLOBCreate, NULL, ZEND_ACC_PUBLIC) @@ -1139,6 +1213,7 @@ static const zend_function_entry dbh_methods[] = { PHP_ME(PDO, pgsqlCopyToFile, NULL, ZEND_ACC_PUBLIC) PHP_ME(PDO, pgsqlGetNotify, NULL, ZEND_ACC_PUBLIC) PHP_ME(PDO, pgsqlGetPid, NULL, ZEND_ACC_PUBLIC) + PHP_ME(PDO, pgsqlSetNoticeCallback, NULL, ZEND_ACC_PUBLIC) PHP_FE_END }; @@ -1245,7 +1320,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, (void(*)(void*,const char*))_pdo_pgsql_notice, (void *)dbh); H->attached = 1; H->pgoid = -1; diff --git a/ext/pdo_pgsql/php_pdo_pgsql_int.h b/ext/pdo_pgsql/php_pdo_pgsql_int.h index 5d80e3220568e..ec4c012dbffc9 100644 --- a/ext/pdo_pgsql/php_pdo_pgsql_int.h +++ b/ext/pdo_pgsql/php_pdo_pgsql_int.h @@ -32,6 +32,11 @@ typedef struct { char *errmsg; } pdo_pgsql_error_info; +typedef struct { + zend_fcall_info fci; + zend_fcall_info_cache fcc; +} pdo_pgsql_fci; + /* stuff we use in a pgsql database handle */ typedef struct { PGconn *server; @@ -45,6 +50,7 @@ typedef struct { zend_bool emulate_prepares; zend_bool disable_native_prepares; /* deprecated since 5.6 */ zend_bool disable_prepares; + pdo_pgsql_fci * 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..76acd201c1794 --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621.inc @@ -0,0 +1,22 @@ +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"; +?> 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..f041f85f8e066 --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621_closure.phpt @@ -0,0 +1,31 @@ +--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]; +} +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_method.phpt b/ext/pdo_pgsql/tests/issue78621_method.phpt new file mode 100644 index 0000000000000..749788b667bfd --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621_method.phpt @@ -0,0 +1,35 @@ +--TEST-- +pgsqlSetNoticeCallback catches Postgres "raise notice". +--SKIPIF-- + +--FILE-- +pgsqlSetNoticeCallback(array($logger, '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