Add option to force use of Extended Queries (#3214)

This feature can be used as follows:

```
client.query({ text: 'SELECT 1', queryMode: 'extended' })
```

This will force the query to be sent with parse/bind/execute even when it has no parameters and disallows multiple statements being executed.  This can be useful in scenarios where you want to enforce more security & help prevent sql injection attacks...particularly by library authors.

---------

Co-authored-by: alxndrsn <alxndrsn>
Co-authored-by: Brian Carlson <brian.m.carlson@gmail.com>
This commit is contained in:
Alex Anderson 2024-06-04 17:14:04 +01:00 committed by GitHub
parent fe88e825e5
commit ff47a97f28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 37 additions and 1 deletions

View File

@ -75,6 +75,9 @@ type QueryConfig {
// custom type parsers just for this query result
types?: Types;
// TODO: document
queryMode?: string;
}
```

View File

@ -57,7 +57,7 @@ Client.prototype.query = function (text, values, cb) {
cb = values
}
if (Array.isArray(values) && values.length > 0) {
if (Array.isArray(values)) {
queryFn = function () {
return self.pq.sendQueryParams(text, values)
}

View File

@ -10,6 +10,7 @@ var NativeQuery = (module.exports = function (config, values, callback) {
this.text = config.text
this.values = config.values
this.name = config.name
this.queryMode = config.queryMode
this.callback = config.callback
this.state = 'new'
this._arrayMode = config.rowMode === 'array'
@ -159,6 +160,8 @@ NativeQuery.prototype.submit = function (client) {
}
var vals = this.values.map(utils.prepareValue)
client.native.query(this.text, vals, after)
} else if (this.queryMode === 'extended') {
client.native.query(this.text, [], after)
} else {
client.native.query(this.text, after)
}

View File

@ -16,6 +16,7 @@ class Query extends EventEmitter {
this.rows = config.rows
this.types = config.types
this.name = config.name
this.queryMode = config.queryMode
this.binary = config.binary
// use unique portal name each time
this.portal = config.portal || ''
@ -32,6 +33,10 @@ class Query extends EventEmitter {
}
requiresPreparation() {
if (this.queryMode === 'extended') {
return true
}
// named queries must always be prepared
if (this.name) {
return true

View File

@ -25,6 +25,31 @@ suite.test(
})
)
suite.test(
'throws if queryMode set to "extended"',
co.wrap(function* () {
const client = new helper.Client()
yield client.connect()
// TODO should be text or sql?
try {
const results = yield client.query({
text: `SELECT 'foo'::text as name; SELECT 'bar'::text as baz`,
queryMode: 'extended',
})
assert.fail('Should have thrown')
} catch (err) {
if (err instanceof assert.AssertionError) throw err
assert.equal(err.severity, 'ERROR')
assert.equal(err.code, '42601')
assert.equal(err.message, 'cannot insert multiple commands into a prepared statement')
}
return client.end()
})
)
suite.test(
'multiple selects work',
co.wrap(function* () {