@@ -3557,4 +3557,102 @@ public function testCreateTableWithDefaultNowFunction() {
35573557 $ result = $ this ->assertQuery ( 'SELECT * FROM test_now_default WHERE id = 2 ' );
35583558 $ this ->assertRegExp ( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/ ' , $ result [0 ]->updated );
35593559 }
3560+
3561+ public function testDoubleQuotedStringsAreParameterized () {
3562+ $ this ->assertQuery ( 'INSERT INTO _options (option_name, option_value) VALUES ("dq_name", "dq_value") ' );
3563+
3564+ // The double-quoted strings should be bound as parameters, not inlined.
3565+ $ insert_query = null ;
3566+ foreach ( $ this ->engine ->executed_sqlite_queries as $ q ) {
3567+ if ( stripos ( $ q ['sql ' ], 'INSERT ' ) !== false && stripos ( $ q ['sql ' ], '_options ' ) !== false ) {
3568+ $ insert_query = $ q ;
3569+ }
3570+ }
3571+ $ this ->assertNotNull ( $ insert_query );
3572+ $ this ->assertNotEmpty ( $ insert_query ['params ' ], 'Double-quoted strings should be bound as parameters ' );
3573+ $ this ->assertStringNotContainsString ( 'dq_name ' , $ insert_query ['sql ' ], 'Value should not appear in SQL ' );
3574+ $ this ->assertStringNotContainsString ( 'dq_value ' , $ insert_query ['sql ' ], 'Value should not appear in SQL ' );
3575+ $ this ->assertContains ( 'dq_name ' , $ insert_query ['params ' ] );
3576+ $ this ->assertContains ( 'dq_value ' , $ insert_query ['params ' ] );
3577+
3578+ // Verify the data was inserted correctly.
3579+ $ result = $ this ->assertQuery ( 'SELECT * FROM _options WHERE option_name = "dq_name" ' );
3580+ $ this ->assertCount ( 1 , $ result );
3581+ $ this ->assertEquals ( 'dq_value ' , $ result [0 ]->option_value );
3582+ }
3583+
3584+ public function testDoubleQuotedStringWithBackslashEscapeDoesNotCauseInjection () {
3585+ // In MySQL, \" inside double-quoted strings is an escaped double quote.
3586+ // The MySQL lexer produces a single token: "admin\" OR 1=1--"
3587+ // with value: admin" OR 1=1--
3588+ //
3589+ // Without parameterization, passing the raw token to SQLite would be:
3590+ // "admin\" OR 1=1--" (SQLite sees "admin\" as identifier + SQL)
3591+ //
3592+ // With parameterization, the value is safely bound as a parameter.
3593+ $ this ->assertQuery (
3594+ 'INSERT INTO _options (option_name, option_value) VALUES ("safe_key", "admin\" OR 1=1--") '
3595+ );
3596+
3597+ $ result = $ this ->assertQuery ( 'SELECT * FROM _options WHERE option_name = "safe_key" ' );
3598+ $ this ->assertCount ( 1 , $ result );
3599+ $ this ->assertEquals ( 'admin" OR 1=1-- ' , $ result [0 ]->option_value );
3600+ }
3601+
3602+ public function testDateFormatWithSingleQuotesInFormat () {
3603+ $ this ->assertQuery (
3604+ 'CREATE TABLE _tmp_dates (
3605+ ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
3606+ created_at DATETIME NOT NULL
3607+ ); '
3608+ );
3609+ $ this ->assertQuery ( "INSERT INTO _tmp_dates (created_at) VALUES ('2024-01-15 10:30:00') " );
3610+
3611+ // DATE_FORMAT with a format that produces a value — verify it works.
3612+ $ result = $ this ->assertQuery (
3613+ "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') as formatted FROM _tmp_dates "
3614+ );
3615+ $ this ->assertCount ( 1 , $ result );
3616+ $ this ->assertEquals ( '2024-01-15 ' , $ result [0 ]->formatted );
3617+ }
3618+
3619+ public function testIntervalExpression () {
3620+ $ this ->assertQuery (
3621+ 'CREATE TABLE _tmp_dates (
3622+ ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
3623+ created_at DATETIME NOT NULL
3624+ ); '
3625+ );
3626+ $ this ->assertQuery ( 'INSERT INTO _tmp_dates (created_at) VALUES ( \'2024-01-15 10:30:00 \') ' );
3627+
3628+ $ result = $ this ->assertQuery (
3629+ 'SELECT DATE_ADD(created_at, INTERVAL 1 DAY) as future_date FROM _tmp_dates '
3630+ );
3631+ $ this ->assertCount ( 1 , $ result );
3632+ $ this ->assertEquals ( '2024-01-16 10:30:00 ' , $ result [0 ]->future_date );
3633+
3634+ $ result = $ this ->assertQuery (
3635+ 'SELECT DATE_SUB(created_at, INTERVAL 1 DAY) as past_date FROM _tmp_dates '
3636+ );
3637+ $ this ->assertCount ( 1 , $ result );
3638+ $ this ->assertEquals ( '2024-01-14 10:30:00 ' , $ result [0 ]->past_date );
3639+ }
3640+
3641+ public function testLikeBinaryWithSingleQuoteInPattern () {
3642+ $ this ->assertQuery (
3643+ "CREATE TABLE _tmp_table (
3644+ ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
3645+ name varchar(50) NOT NULL default ''
3646+ ); "
3647+ );
3648+
3649+ $ this ->assertQuery ( "INSERT INTO _tmp_table (name) VALUES ('it''s a test') " );
3650+ $ this ->assertQuery ( "INSERT INTO _tmp_table (name) VALUES ('no quote here') " );
3651+
3652+ $ result = $ this ->assertQuery (
3653+ "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'it''s%' "
3654+ );
3655+ $ this ->assertCount ( 1 , $ result );
3656+ $ this ->assertEquals ( "it's a test " , $ result [0 ]->name );
3657+ }
35603658}
0 commit comments