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
|
<!-- YAML
|
||||||
added: v22.5.0
|
added: v22.5.0
|
||||||
changes:
|
changes:
|
||||||
|
- version:
|
||||||
|
- REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/60217
|
||||||
|
description: Add `defensive` option.
|
||||||
- version:
|
- version:
|
||||||
- v24.4.0
|
- v24.4.0
|
||||||
- v22.18.0
|
- v22.18.0
|
||||||
|
|
@ -140,6 +144,10 @@ changes:
|
||||||
character (e.g., `foo` instead of `:foo`). **Default:** `true`.
|
character (e.g., `foo` instead of `:foo`). **Default:** `true`.
|
||||||
* `allowUnknownNamedParameters` {boolean} If `true`, unknown named parameters are ignored when binding.
|
* `allowUnknownNamedParameters` {boolean} If `true`, unknown named parameters are ignored when binding.
|
||||||
If `false`, an exception is thrown for unknown named parameters. **Default:** `false`.
|
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.
|
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
|
method. When `allowExtension` is `false` when constructing, you cannot enable
|
||||||
loading extensions for security reasons.
|
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])`
|
### `database.location([dbName])`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- 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
|
[Type conversion between JavaScript and SQLite]: #type-conversion-between-javascript-and-sqlite
|
||||||
[`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html
|
[`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html
|
||||||
[`PRAGMA foreign_keys`]: https://www.sqlite.org/pragma.html#pragma_foreign_keys
|
[`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_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html
|
||||||
[`SQLITE_DIRECTONLY`]: 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
|
[`SQLITE_MAX_FUNCTION_ARG`]: https://www.sqlite.org/limits.html#max_function_arg
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@
|
||||||
V(cwd_string, "cwd") \
|
V(cwd_string, "cwd") \
|
||||||
V(data_string, "data") \
|
V(data_string, "data") \
|
||||||
V(default_is_true_string, "defaultIsTrue") \
|
V(default_is_true_string, "defaultIsTrue") \
|
||||||
|
V(defensive_string, "defensive") \
|
||||||
V(deserialize_info_string, "deserializeInfo") \
|
V(deserialize_info_string, "deserializeInfo") \
|
||||||
V(dest_string, "dest") \
|
V(dest_string, "dest") \
|
||||||
V(destroyed_string, "destroyed") \
|
V(destroyed_string, "destroyed") \
|
||||||
|
|
|
||||||
|
|
@ -753,6 +753,14 @@ bool DatabaseSync::Open() {
|
||||||
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
|
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
|
||||||
CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys());
|
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());
|
sqlite3_busy_timeout(connection_, open_config_.get_timeout());
|
||||||
|
|
||||||
if (allow_load_extension_) {
|
if (allow_load_extension_) {
|
||||||
|
|
@ -1065,6 +1073,21 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
|
||||||
allow_unknown_named_params_v.As<Boolean>()->Value());
|
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(
|
new DatabaseSync(
|
||||||
|
|
@ -1835,6 +1858,26 @@ void DatabaseSync::EnableLoadExtension(
|
||||||
CHECK_ERROR_OR_THROW(isolate, db, load_extension_ret, SQLITE_OK, void());
|
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) {
|
void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
|
||||||
DatabaseSync* db;
|
DatabaseSync* db;
|
||||||
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
|
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
|
||||||
|
|
@ -3316,6 +3359,8 @@ static void Initialize(Local<Object> target,
|
||||||
db_tmpl,
|
db_tmpl,
|
||||||
"enableLoadExtension",
|
"enableLoadExtension",
|
||||||
DatabaseSync::EnableLoadExtension);
|
DatabaseSync::EnableLoadExtension);
|
||||||
|
SetProtoMethod(
|
||||||
|
isolate, db_tmpl, "enableDefensive", DatabaseSync::EnableDefensive);
|
||||||
SetProtoMethod(
|
SetProtoMethod(
|
||||||
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
|
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
|
||||||
SetProtoMethod(
|
SetProtoMethod(
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ class DatabaseOpenConfiguration {
|
||||||
return allow_unknown_named_params_;
|
return allow_unknown_named_params_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void set_enable_defensive(bool flag) { defensive_ = flag; }
|
||||||
|
|
||||||
|
inline bool get_enable_defensive() const { return defensive_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string location_;
|
std::string location_;
|
||||||
bool read_only_ = false;
|
bool read_only_ = false;
|
||||||
|
|
@ -75,6 +79,7 @@ class DatabaseOpenConfiguration {
|
||||||
bool return_arrays_ = false;
|
bool return_arrays_ = false;
|
||||||
bool allow_bare_named_params_ = true;
|
bool allow_bare_named_params_ = true;
|
||||||
bool allow_unknown_named_params_ = false;
|
bool allow_unknown_named_params_ = false;
|
||||||
|
bool defensive_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DatabaseSync;
|
class DatabaseSync;
|
||||||
|
|
@ -140,6 +145,7 @@ class DatabaseSync : public BaseObject {
|
||||||
static void ApplyChangeset(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void ApplyChangeset(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void EnableLoadExtension(
|
static void EnableLoadExtension(
|
||||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
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 LoadExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void SetAuthorizer(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void SetAuthorizer(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static int AuthorizerCallback(void* user_data,
|
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