mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
sqlite: allow setting defensive flag
PR-URL: https://github.com/nodejs/node/pull/60217 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
This commit is contained in:
parent
1f2c9f82b7
commit
fd7b33e763
|
|
@ -98,6 +98,10 @@ exposed by this class execute synchronously.
|
|||
<!-- YAML
|
||||
added: v22.5.0
|
||||
changes:
|
||||
- version:
|
||||
- REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/60217
|
||||
description: Add `defensive` option.
|
||||
- version:
|
||||
- v24.4.0
|
||||
- v22.18.0
|
||||
|
|
@ -140,6 +144,10 @@ changes:
|
|||
character (e.g., `foo` instead of `:foo`). **Default:** `true`.
|
||||
* `allowUnknownNamedParameters` {boolean} If `true`, unknown named parameters are ignored when binding.
|
||||
If `false`, an exception is thrown for unknown named parameters. **Default:** `false`.
|
||||
* `defensive` {boolean} If `true`, enables the defensive flag. When the defensive flag is enabled,
|
||||
language features that allow ordinary SQL to deliberately corrupt the database file are disabled.
|
||||
The defensive flag can also be set using `enableDefensive()`.
|
||||
**Default:** `false`.
|
||||
|
||||
Constructs a new `DatabaseSync` instance.
|
||||
|
||||
|
|
@ -261,6 +269,19 @@ Enables or disables the `loadExtension` SQL function, and the `loadExtension()`
|
|||
method. When `allowExtension` is `false` when constructing, you cannot enable
|
||||
loading extensions for security reasons.
|
||||
|
||||
### `database.enableDefensive(active)`
|
||||
|
||||
<!-- YAML
|
||||
added:
|
||||
- REPLACEME
|
||||
-->
|
||||
|
||||
* `active` {boolean} Whether to set the defensive flag.
|
||||
|
||||
Enables or disables the defensive flag. When the defensive flag is active,
|
||||
language features that allow ordinary SQL to deliberately corrupt the database file are disabled.
|
||||
See [`SQLITE_DBCONFIG_DEFENSIVE`][] in the SQLite documentation for details.
|
||||
|
||||
### `database.location([dbName])`
|
||||
|
||||
<!-- YAML
|
||||
|
|
@ -1306,6 +1327,7 @@ callback function to indicate what type of operation is being authorized.
|
|||
[Type conversion between JavaScript and SQLite]: #type-conversion-between-javascript-and-sqlite
|
||||
[`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html
|
||||
[`PRAGMA foreign_keys`]: https://www.sqlite.org/pragma.html#pragma_foreign_keys
|
||||
[`SQLITE_DBCONFIG_DEFENSIVE`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigdefensive
|
||||
[`SQLITE_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html
|
||||
[`SQLITE_DIRECTONLY`]: https://www.sqlite.org/c3ref/c_deterministic.html
|
||||
[`SQLITE_MAX_FUNCTION_ARG`]: https://www.sqlite.org/limits.html#max_function_arg
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@
|
|||
V(cwd_string, "cwd") \
|
||||
V(data_string, "data") \
|
||||
V(default_is_true_string, "defaultIsTrue") \
|
||||
V(defensive_string, "defensive") \
|
||||
V(deserialize_info_string, "deserializeInfo") \
|
||||
V(dest_string, "dest") \
|
||||
V(destroyed_string, "destroyed") \
|
||||
|
|
|
|||
|
|
@ -753,6 +753,14 @@ bool DatabaseSync::Open() {
|
|||
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
|
||||
CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys());
|
||||
|
||||
int defensive_enabled;
|
||||
r = sqlite3_db_config(connection_,
|
||||
SQLITE_DBCONFIG_DEFENSIVE,
|
||||
static_cast<int>(open_config_.get_enable_defensive()),
|
||||
&defensive_enabled);
|
||||
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
|
||||
CHECK_EQ(defensive_enabled, open_config_.get_enable_defensive());
|
||||
|
||||
sqlite3_busy_timeout(connection_, open_config_.get_timeout());
|
||||
|
||||
if (allow_load_extension_) {
|
||||
|
|
@ -1065,6 +1073,21 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
|
|||
allow_unknown_named_params_v.As<Boolean>()->Value());
|
||||
}
|
||||
}
|
||||
|
||||
Local<Value> defensive_v;
|
||||
if (!options->Get(env->context(), env->defensive_string())
|
||||
.ToLocal(&defensive_v)) {
|
||||
return;
|
||||
}
|
||||
if (!defensive_v->IsUndefined()) {
|
||||
if (!defensive_v->IsBoolean()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(
|
||||
env->isolate(),
|
||||
"The \"options.defensive\" argument must be a boolean.");
|
||||
return;
|
||||
}
|
||||
open_config.set_enable_defensive(defensive_v.As<Boolean>()->Value());
|
||||
}
|
||||
}
|
||||
|
||||
new DatabaseSync(
|
||||
|
|
@ -1835,6 +1858,26 @@ void DatabaseSync::EnableLoadExtension(
|
|||
CHECK_ERROR_OR_THROW(isolate, db, load_extension_ret, SQLITE_OK, void());
|
||||
}
|
||||
|
||||
void DatabaseSync::EnableDefensive(const FunctionCallbackInfo<Value>& args) {
|
||||
DatabaseSync* db;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
|
||||
|
||||
auto isolate = args.GetIsolate();
|
||||
if (!args[0]->IsBoolean()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(isolate,
|
||||
"The \"active\" argument must be a boolean.");
|
||||
return;
|
||||
}
|
||||
|
||||
const int enable = args[0].As<Boolean>()->Value();
|
||||
int defensive_enabled;
|
||||
const int defensive_ret = sqlite3_db_config(
|
||||
db->connection_, SQLITE_DBCONFIG_DEFENSIVE, enable, &defensive_enabled);
|
||||
CHECK_ERROR_OR_THROW(isolate, db, defensive_ret, SQLITE_OK, void());
|
||||
}
|
||||
|
||||
void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
|
||||
DatabaseSync* db;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
|
||||
|
|
@ -3316,6 +3359,8 @@ static void Initialize(Local<Object> target,
|
|||
db_tmpl,
|
||||
"enableLoadExtension",
|
||||
DatabaseSync::EnableLoadExtension);
|
||||
SetProtoMethod(
|
||||
isolate, db_tmpl, "enableDefensive", DatabaseSync::EnableDefensive);
|
||||
SetProtoMethod(
|
||||
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
|
||||
SetProtoMethod(
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ class DatabaseOpenConfiguration {
|
|||
return allow_unknown_named_params_;
|
||||
}
|
||||
|
||||
inline void set_enable_defensive(bool flag) { defensive_ = flag; }
|
||||
|
||||
inline bool get_enable_defensive() const { return defensive_; }
|
||||
|
||||
private:
|
||||
std::string location_;
|
||||
bool read_only_ = false;
|
||||
|
|
@ -75,6 +79,7 @@ class DatabaseOpenConfiguration {
|
|||
bool return_arrays_ = false;
|
||||
bool allow_bare_named_params_ = true;
|
||||
bool allow_unknown_named_params_ = false;
|
||||
bool defensive_ = false;
|
||||
};
|
||||
|
||||
class DatabaseSync;
|
||||
|
|
@ -140,6 +145,7 @@ class DatabaseSync : public BaseObject {
|
|||
static void ApplyChangeset(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void EnableLoadExtension(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void EnableDefensive(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void LoadExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void SetAuthorizer(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static int AuthorizerCallback(void* user_data,
|
||||
|
|
|
|||
56
test/parallel/test-sqlite-config.js
Normal file
56
test/parallel/test-sqlite-config.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
'use strict';
|
||||
const { skipIfSQLiteMissing } = require('../common/index.mjs');
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
skipIfSQLiteMissing();
|
||||
|
||||
function checkDefensiveMode(db) {
|
||||
function journalMode() {
|
||||
return db.prepare('PRAGMA journal_mode').get().journal_mode;
|
||||
}
|
||||
|
||||
assert.strictEqual(journalMode(), 'memory');
|
||||
db.exec('PRAGMA journal_mode=OFF');
|
||||
|
||||
switch (journalMode()) {
|
||||
case 'memory': return true; // journal_mode unchanged, defensive mode must be active
|
||||
case 'off': return false; // journal_mode now 'off', so defensive mode not active
|
||||
default: throw new Error('unexpected journal_mode');
|
||||
}
|
||||
}
|
||||
|
||||
test('by default, defensive mode is off', (t) => {
|
||||
const db = new DatabaseSync(':memory:');
|
||||
t.assert.strictEqual(checkDefensiveMode(db), false);
|
||||
});
|
||||
|
||||
test('when passing { defensive: true } as config, defensive mode is on', (t) => {
|
||||
const db = new DatabaseSync(':memory:', {
|
||||
defensive: true
|
||||
});
|
||||
t.assert.strictEqual(checkDefensiveMode(db), true);
|
||||
});
|
||||
|
||||
test('defensive mode on after calling db.enableDefensive(true)', (t) => {
|
||||
const db = new DatabaseSync(':memory:');
|
||||
db.enableDefensive(true);
|
||||
t.assert.strictEqual(checkDefensiveMode(db), true);
|
||||
});
|
||||
|
||||
test('defensive mode should be off after calling db.enableDefensive(false)', (t) => {
|
||||
const db = new DatabaseSync(':memory:', {
|
||||
defensive: true
|
||||
});
|
||||
db.enableDefensive(false);
|
||||
t.assert.strictEqual(checkDefensiveMode(db), false);
|
||||
});
|
||||
|
||||
test('throws if options.defensive is provided but is not a boolean', (t) => {
|
||||
t.assert.throws(() => {
|
||||
new DatabaseSync(':memory:', { defensive: 42 });
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "options.defensive" argument must be a boolean.',
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user