04ba8ad135c71a6e3005af42744dc4cf4d68dc6a
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / Database / QueryTest.php
1 <?php
2
3 namespace Drupal\KernelTests\Core\Database;
4
5 /**
6  * Tests Drupal's extended prepared statement syntax..
7  *
8  * @group Database
9  */
10 class QueryTest extends DatabaseTestBase {
11
12   /**
13    * Tests that we can pass an array of values directly in the query.
14    */
15   public function testArraySubstitution() {
16     $names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', [':ages[]' => [25, 26, 27]])->fetchAll();
17     $this->assertEqual(count($names), 3, 'Correct number of names returned');
18
19     $names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', [':ages[]' => [25]])->fetchAll();
20     $this->assertEqual(count($names), 1, 'Correct number of names returned');
21   }
22
23   /**
24    * Tests that we can not pass a scalar value when an array is expected.
25    */
26   public function testScalarSubstitution() {
27     try {
28       $names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', [':ages[]' => 25])->fetchAll();
29       $this->fail('Array placeholder with scalar argument should result in an exception.');
30     }
31     catch (\InvalidArgumentException $e) {
32       $this->pass('Array placeholder with scalar argument should result in an exception.');
33     }
34
35   }
36
37   /**
38    * Tests SQL injection via database query array arguments.
39    */
40   public function testArrayArgumentsSQLInjection() {
41     // Attempt SQL injection and verify that it does not work.
42     $condition = [
43       "1 ;INSERT INTO {test} (name) VALUES ('test12345678'); -- " => '',
44       '1' => '',
45     ];
46     try {
47       db_query("SELECT * FROM {test} WHERE name = :name", [':name' => $condition])->fetchObject();
48       $this->fail('SQL injection attempt via array arguments should result in a database exception.');
49     }
50     catch (\InvalidArgumentException $e) {
51       $this->pass('SQL injection attempt via array arguments should result in a database exception.');
52     }
53
54     // Test that the insert query that was used in the SQL injection attempt did
55     // not result in a row being inserted in the database.
56     $result = db_select('test')
57       ->condition('name', 'test12345678')
58       ->countQuery()
59       ->execute()
60       ->fetchField();
61     $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
62   }
63
64   /**
65    * Tests SQL injection via condition operator.
66    */
67   public function testConditionOperatorArgumentsSQLInjection() {
68     $injection = "IS NOT NULL) ;INSERT INTO {test} (name) VALUES ('test12345678'); -- ";
69
70     // Convert errors to exceptions for testing purposes below.
71     set_error_handler(function ($severity, $message, $filename, $lineno) {
72       throw new \ErrorException($message, 0, $severity, $filename, $lineno);
73     });
74     try {
75       $result = db_select('test', 't')
76         ->fields('t')
77         ->condition('name', 1, $injection)
78         ->execute();
79       $this->fail('Should not be able to attempt SQL injection via condition operator.');
80     }
81     catch (\ErrorException $e) {
82       $this->pass('SQL injection attempt via condition arguments should result in a database exception.');
83     }
84
85     // Test that the insert query that was used in the SQL injection attempt did
86     // not result in a row being inserted in the database.
87     $result = db_select('test')
88       ->condition('name', 'test12345678')
89       ->countQuery()
90       ->execute()
91       ->fetchField();
92     $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
93
94     // Attempt SQLi via union query with no unsafe characters.
95     $this->enableModules(['user']);
96     $this->installEntitySchema('user');
97     db_insert('test')
98       ->fields(['name' => '123456'])
99       ->execute();
100     $injection = "= 1 UNION ALL SELECT password FROM user WHERE uid =";
101
102     try {
103       $result = db_select('test', 't')
104         ->fields('t', ['name', 'name'])
105         ->condition('name', 1, $injection)
106         ->execute();
107       $this->fail('Should not be able to attempt SQL injection via operator.');
108     }
109     catch (\ErrorException $e) {
110       $this->pass('SQL injection attempt via condition arguments should result in a database exception.');
111     }
112
113     // Attempt SQLi via union query - uppercase tablename.
114     db_insert('TEST_UPPERCASE')
115       ->fields(['name' => 'secrets'])
116       ->execute();
117     $injection = "IS NOT NULL) UNION ALL SELECT name FROM {TEST_UPPERCASE} -- ";
118
119     try {
120       $result = db_select('test', 't')
121         ->fields('t', ['name'])
122         ->condition('name', 1, $injection)
123         ->execute();
124       $this->fail('Should not be able to attempt SQL injection via operator.');
125     }
126     catch (\ErrorException $e) {
127       $this->pass('SQL injection attempt via condition arguments should result in a database exception.');
128     }
129     restore_error_handler();
130   }
131
132   /**
133    * Tests numeric query parameter expansion in expressions.
134    *
135    * @see \Drupal\Core\Database\Driver\sqlite\Statement::getStatement()
136    * @see http://bugs.php.net/bug.php?id=45259
137    */
138   public function testNumericExpressionSubstitution() {
139     $count = db_query('SELECT COUNT(*) >= 3 FROM {test}')->fetchField();
140     $this->assertEqual((bool) $count, TRUE);
141
142     $count = db_query('SELECT COUNT(*) >= :count FROM {test}', [
143       ':count' => 3,
144     ])->fetchField();
145     $this->assertEqual((bool) $count, TRUE);
146   }
147
148 }