diff --git a/NEWS b/NEWS index 86cb13963862..4c9c5645a00f 100644 --- a/NEWS +++ b/NEWS @@ -102,6 +102,8 @@ PHP NEWS - PDO_SQLite: . Fixed bug GH-24990 (Statement unusable after a failed execute()). (lazerg) + . Added Pdo\Sqlite::ATTR_DQS to disable double-quoted string literals + (GH-13597). (Juan Versolato Lopes) - PGSQL: . Fixed pg_lo_write() rejecting data containing null bytes. (Ilia Alshanetsky) diff --git a/UPGRADING b/UPGRADING index 9804b8706b81..dc41efa430c4 100644 --- a/UPGRADING +++ b/UPGRADING @@ -84,6 +84,12 @@ PHP 8.7 UPGRADE NOTES A ValueError is thrown if the array is empty or contains an invalid locale, and a TypeError if it contains a non-string value. +- PDO_SQLite: + . Added the Pdo\Sqlite::ATTR_DQS connection attribute. Setting it to false + makes SQLite reject double-quoted string literals, so "abc" is only read + as an identifier, as in the sqlite3 shell since SQLite 3.41.0. It is + available when PHP is built against SQLite 3.29.0 or later. + - Streams: . The so_linger, so_rcvbuf and so_sndbuf stream socket context options are now also applied to unix and unix datagram sockets. diff --git a/ext/pdo_sqlite/pdo_sqlite.stub.php b/ext/pdo_sqlite/pdo_sqlite.stub.php index 53f1ceba427b..5a7336639195 100644 --- a/ext/pdo_sqlite/pdo_sqlite.stub.php +++ b/ext/pdo_sqlite/pdo_sqlite.stub.php @@ -42,6 +42,11 @@ class Sqlite extends \PDO /** @cvalue PDO_SQLITE_ATTR_TRANSACTION_MODE */ public const int ATTR_TRANSACTION_MODE = UNKNOWN; +#ifdef SQLITE_DBCONFIG_DQS_DML + /** @cvalue PDO_SQLITE_ATTR_DQS */ + public const int ATTR_DQS = UNKNOWN; +#endif + public const int TRANSACTION_MODE_DEFERRED = 0; public const int TRANSACTION_MODE_IMMEDIATE = 1; public const int TRANSACTION_MODE_EXCLUSIVE = 2; diff --git a/ext/pdo_sqlite/pdo_sqlite_arginfo.h b/ext/pdo_sqlite/pdo_sqlite_arginfo.h index 73a9158301c0..303b4b49fcc0 100644 --- a/ext/pdo_sqlite/pdo_sqlite_arginfo.h +++ b/ext/pdo_sqlite/pdo_sqlite_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit pdo_sqlite.stub.php instead. - * Stub hash: 721c46905fa8fb1e18d7196ed85c37f56049ea33 */ + * Stub hash: ffc048d27553b31bbbd6cf33751c983339058285 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Sqlite_createAggregate, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) @@ -127,6 +127,14 @@ static zend_class_entry *register_class_Pdo_Sqlite(zend_class_entry *class_entry zend_string *const_ATTR_TRANSACTION_MODE_name = zend_string_init_interned("ATTR_TRANSACTION_MODE", sizeof("ATTR_TRANSACTION_MODE") - 1, true); zend_declare_typed_class_constant(class_entry, const_ATTR_TRANSACTION_MODE_name, &const_ATTR_TRANSACTION_MODE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release_ex(const_ATTR_TRANSACTION_MODE_name, true); +#if defined(SQLITE_DBCONFIG_DQS_DML) + + zval const_ATTR_DQS_value; + ZVAL_LONG(&const_ATTR_DQS_value, PDO_SQLITE_ATTR_DQS); + zend_string *const_ATTR_DQS_name = zend_string_init_interned("ATTR_DQS", sizeof("ATTR_DQS") - 1, true); + zend_declare_typed_class_constant(class_entry, const_ATTR_DQS_name, &const_ATTR_DQS_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_ATTR_DQS_name, true); +#endif zval const_TRANSACTION_MODE_DEFERRED_value; ZVAL_LONG(&const_TRANSACTION_MODE_DEFERRED_value, 0); diff --git a/ext/pdo_sqlite/php_pdo_sqlite_int.h b/ext/pdo_sqlite/php_pdo_sqlite_int.h index 122f714040de..5535f86f01f8 100644 --- a/ext/pdo_sqlite/php_pdo_sqlite_int.h +++ b/ext/pdo_sqlite/php_pdo_sqlite_int.h @@ -75,7 +75,8 @@ enum { PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES, PDO_SQLITE_ATTR_BUSY_STATEMENT, PDO_SQLITE_ATTR_EXPLAIN_STATEMENT, - PDO_SQLITE_ATTR_TRANSACTION_MODE + PDO_SQLITE_ATTR_TRANSACTION_MODE, + PDO_SQLITE_ATTR_DQS }; typedef int pdo_sqlite_create_collation_callback(void*, int, const void*, int, const void*); diff --git a/ext/pdo_sqlite/sqlite_driver.c b/ext/pdo_sqlite/sqlite_driver.c index 692bb8fd6c86..6de50ea76442 100644 --- a/ext/pdo_sqlite/sqlite_driver.c +++ b/ext/pdo_sqlite/sqlite_driver.c @@ -303,6 +303,15 @@ static int pdo_sqlite_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return case PDO_SQLITE_ATTR_TRANSACTION_MODE: ZVAL_LONG(return_value, H->transaction_mode); break; +#ifdef SQLITE_DBCONFIG_DQS_DML + case PDO_SQLITE_ATTR_DQS: { + int dml = 0, ddl = 0; + sqlite3_db_config(H->db, SQLITE_DBCONFIG_DQS_DML, -1, &dml); + sqlite3_db_config(H->db, SQLITE_DBCONFIG_DQS_DDL, -1, &ddl); + ZVAL_BOOL(return_value, dml || ddl); + break; + } +#endif default: return 0; @@ -351,6 +360,22 @@ static bool pdo_sqlite_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) default: return false; } +#ifdef SQLITE_DBCONFIG_DQS_DML + case PDO_SQLITE_ATTR_DQS: { + bool bval; + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + /* Double-quoted string literals are controlled separately for DML + * and DDL statements; toggle both so the attribute has a single + * meaning. */ + if (sqlite3_db_config(H->db, SQLITE_DBCONFIG_DQS_DML, (int) bval, NULL) != SQLITE_OK + || sqlite3_db_config(H->db, SQLITE_DBCONFIG_DQS_DDL, (int) bval, NULL) != SQLITE_OK) { + return false; + } + return true; + } +#endif } return false; } diff --git a/ext/pdo_sqlite/tests/gh13597.phpt b/ext/pdo_sqlite/tests/gh13597.phpt new file mode 100644 index 000000000000..163bf5a922a8 --- /dev/null +++ b/ext/pdo_sqlite/tests/gh13597.phpt @@ -0,0 +1,86 @@ +--TEST-- +GH-13597 (Allow disabling double-quoted string literals in SQLite) +--EXTENSIONS-- +pdo_sqlite +--SKIPIF-- + +--FILE-- +query($sql)->fetchColumn()); + } catch (PDOException $e) { + echo $e->getMessage(), "\n"; + } +} + +$pdo = new Pdo\Sqlite('sqlite::memory:'); +$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +echo "Enabled:\n"; +var_dump($pdo->setAttribute(Pdo\Sqlite::ATTR_DQS, true)); +var_dump($pdo->getAttribute(Pdo\Sqlite::ATTR_DQS)); +check($pdo, 'SELECT "abc"'); +$pdo->exec('CREATE TABLE t1 (a TEXT)'); +$pdo->exec('CREATE INDEX t1_idx ON t1("missing")'); +echo "index created\n"; + +echo "Disabled:\n"; +var_dump($pdo->setAttribute(Pdo\Sqlite::ATTR_DQS, false)); +var_dump($pdo->getAttribute(Pdo\Sqlite::ATTR_DQS)); +check($pdo, 'SELECT "abc"'); +check($pdo, "SELECT 'abc'"); +check($pdo, 'SELECT "a" FROM t1 UNION ALL SELECT 1'); +try { + $pdo->exec('CREATE INDEX t1_idx2 ON t1("missing")'); +} catch (PDOException $e) { + echo $e->getMessage(), "\n"; +} + +echo "Prepared statement:\n"; +$pdo->setAttribute(Pdo\Sqlite::ATTR_DQS, true); +$stmt = $pdo->prepare('SELECT "abc"'); +$stmt->execute(); +var_dump($stmt->fetchColumn()); +$pdo->setAttribute(Pdo\Sqlite::ATTR_DQS, false); +try { + $stmt->execute(); +} catch (PDOException $e) { + echo $e->getMessage(), "\n"; +} +$pdo->setAttribute(Pdo\Sqlite::ATTR_DQS, true); +$stmt->execute(); +var_dump($stmt->fetchColumn()); + +echo "Constructor option:\n"; +$pdo = new Pdo\Sqlite('sqlite::memory:', options: [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + Pdo\Sqlite::ATTR_DQS => false, +]); +var_dump($pdo->getAttribute(Pdo\Sqlite::ATTR_DQS)); +check($pdo, 'SELECT "abc"'); + +?> +--EXPECTF-- +Enabled: +bool(true) +bool(true) +string(3) "abc" +index created +Disabled: +bool(true) +bool(false) +SQLSTATE[HY000]: General error: 1 no such column: %Sabc%S +string(3) "abc" +int(1) +SQLSTATE[HY000]: General error: 1 no such column: %Smissing%S +Prepared statement: +string(3) "abc" +SQLSTATE[HY000]: General error: 1 no such column: %Sabc%S +string(3) "abc" +Constructor option: +bool(false) +SQLSTATE[HY000]: General error: 1 no such column: %Sabc%S