Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,10 @@
],
'node_sqlite_sources': [
'src/node_sqlite.cc',
'src/node_sqlite_vfs.cc',
'src/node_webstorage.cc',
'src/node_sqlite.h',
'src/node_sqlite_vfs.h',
'src/node_webstorage.h',
],
'node_ffi_sources': [
Expand Down
118 changes: 101 additions & 17 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ class BackupJob : public ThreadPoolWork {
std::string source_db,
std::string destination_name,
std::string dest_db,
std::shared_ptr<SQLitePermissionVFS> permission_vfs,
int pages,
Local<Function> progressFunc)
: ThreadPoolWork(env, "node_sqlite3.BackupJob"),
Expand All @@ -496,7 +497,8 @@ class BackupJob : public ThreadPoolWork {
pages_(pages),
source_db_(std::move(source_db)),
destination_name_(std::move(destination_name)),
dest_db_(std::move(dest_db)) {
dest_db_(std::move(dest_db)),
permission_vfs_(std::move(permission_vfs)) {
resolver_.Reset(env->isolate(), resolver);
progressFunc_.Reset(env->isolate(), progressFunc);
}
Expand All @@ -508,7 +510,8 @@ class BackupJob : public ThreadPoolWork {
destination_name_.c_str(),
&dest_,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
nullptr);
permission_vfs_ == nullptr ? nullptr
: permission_vfs_->name().c_str());
Local<Promise::Resolver> resolver =
Local<Promise::Resolver>::New(env()->isolate(), resolver_);
if (backup_status_ != SQLITE_OK) {
Expand Down Expand Up @@ -609,6 +612,8 @@ class BackupJob : public ThreadPoolWork {
sqlite3_close_v2(dest_);
dest_ = nullptr;
}

permission_vfs_.reset();
}

private:
Expand Down Expand Up @@ -651,6 +656,7 @@ class BackupJob : public ThreadPoolWork {
std::string source_db_;
std::string destination_name_;
std::string dest_db_;
std::shared_ptr<SQLitePermissionVFS> permission_vfs_;
};

UserDefinedFunction::UserDefinedFunction(Environment* env,
Expand Down Expand Up @@ -935,12 +941,62 @@ void DatabaseSync::MemoryInfo(MemoryTracker* tracker) const {
"open_config", sizeof(open_config_), "DatabaseOpenConfiguration");
}

namespace {

bool IsSQLiteMemoryLocation(std::string_view location) {
return location == ":memory:" ||
(location.starts_with("file:") &&
(SQLitePathForPermission(location) == ":memory:" ||
SQLiteUriParameterEquals(location, "mode", "memory")));
}

bool IsSQLiteReadOnlyLocation(std::string_view location, bool read_only) {
return read_only ||
(location.starts_with("file:") &&
(SQLiteUriParameterEquals(location, "mode", "ro") ||
SQLiteUriBooleanParameter(location, "immutable")));
}

} // namespace

bool DatabaseSync::Open() {
if (IsOpen()) {
THROW_ERR_INVALID_STATE(env(), "database is already open");
return false;
}

// Permission checks: in-memory databases do not access the filesystem.
// SQLite URI modes are handled before the VFS is created so that the
// user-facing error remains Node's permission error.
std::string_view db_location = open_config_.location();
if (!IsSQLiteMemoryLocation(db_location)) {
const std::string db_path = SQLitePathForPermission(db_location);
const bool read_only =
IsSQLiteReadOnlyLocation(db_location, open_config_.get_read_only());
THROW_IF_INSUFFICIENT_PERMISSIONS(
env(),
permission::PermissionScope::kFileSystemRead,
db_path,
false);
if (!read_only) {
THROW_IF_INSUFFICIENT_PERMISSIONS(
env(),
permission::PermissionScope::kFileSystemWrite,
db_path,
false);
}
}

if (env()->permission()->enabled()) {
permission_vfs_ = std::make_shared<SQLitePermissionVFS>(env());
if (!permission_vfs_->Register()) {
THROW_ERR_SQLITE_ERROR(env()->isolate(),
"Unable to register the SQLite permission VFS");
permission_vfs_.reset();
return false;
}
}

// sqlite3_open_v2() assigns a database handle even when it fails. Such a
// handle is in a "sick" state and may only be used to retrieve the error
// and must then be released with sqlite3_close(). Close and reset the
Expand All @@ -959,12 +1015,19 @@ bool DatabaseSync::Open() {
int flags = open_config_.get_read_only()
? SQLITE_OPEN_READONLY
: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
int r = sqlite3_open_v2(open_config_.location().c_str(),
&connection_,
flags | default_flags,
nullptr);
int r = sqlite3_open_v2(
open_config_.location().c_str(),
&connection_,
flags | default_flags,
permission_vfs_ == nullptr ? nullptr : permission_vfs_->name().c_str());
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);

if (permission_vfs_ != nullptr) {
r = sqlite3_set_authorizer(
connection_, DatabaseSync::AuthorizerCallback, this);
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
}

r = sqlite3_db_config(connection_,
SQLITE_DBCONFIG_DQS_DML,
static_cast<int>(open_config_.get_enable_dqs()),
Expand Down Expand Up @@ -1088,10 +1151,21 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
constexpr auto has_null_bytes = [](std::string_view str) {
return str.find('\0') != std::string_view::npos;
};
const auto validate_uri_vfs = [&](std::string value)
-> std::optional<std::string> {
if (env->permission()->enabled() && HasSQLiteVfsUriParameter(value)) {
THROW_ERR_INVALID_ARG_VALUE(
env->isolate(),
"The \"%s\" argument must not specify a SQLite VFS.",
field_name);
return std::nullopt;
}
return value;
};
if (path->IsString()) {
Utf8Value location(env->isolate(), path.As<String>());
if (!has_null_bytes(location.ToStringView())) {
return location.ToString();
return validate_uri_vfs(location.ToString());
}
} else if (path->IsUint8Array()) {
Local<Uint8Array> buffer = path.As<Uint8Array>();
Expand All @@ -1100,7 +1174,8 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
auto data =
static_cast<const uint8_t*>(buffer->Buffer()->Data()) + byteOffset;
if (std::find(data, data + byteLength, 0) == data + byteLength) {
return std::string(reinterpret_cast<const char*>(data), byteLength);
return validate_uri_vfs(
std::string(reinterpret_cast<const char*>(data), byteLength));
}
} else if (path->IsObject()) { // When is URL
auto url = path.As<Object>();
Expand All @@ -1116,7 +1191,7 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
return std::nullopt;
}

return location_value.ToString();
return validate_uri_vfs(location_value.ToString());
}
}
}
Expand Down Expand Up @@ -1439,6 +1514,7 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
int r = sqlite3_close_v2(db->connection_);
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
db->connection_ = nullptr;
db->permission_vfs_.reset();
}

void DatabaseSync::Dispose(const v8::FunctionCallbackInfo<v8::Value>& args) {
Expand Down Expand Up @@ -2229,6 +2305,7 @@ void Backup(const FunctionCallbackInfo<Value>& args) {
std::move(source_db),
dest_path.value(),
std::move(dest_db),
db->PermissionVFS(),
rate,
progressFunc);
db->AddBackup(job);
Expand Down Expand Up @@ -2464,9 +2541,13 @@ void DatabaseSync::SetAuthorizer(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = env->isolate();

if (args[0]->IsNull()) {
// Clear the authorizer
sqlite3_set_authorizer(db->connection_, nullptr, nullptr);
db->object()->SetInternalField(kAuthorizerCallback, Null(isolate));
if (db->permission_vfs_ == nullptr) {
sqlite3_set_authorizer(db->connection_, nullptr, nullptr);
} else {
sqlite3_set_authorizer(
db->connection_, DatabaseSync::AuthorizerCallback, db);
}
return;
}

Expand Down Expand Up @@ -2495,16 +2576,19 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
const char* param3,
const char* param4) {
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
Environment* env = db->env();
Isolate* isolate = env->isolate();
HandleScope handle_scope(isolate);
Local<Context> context = env->context();
if (db->permission_vfs_ != nullptr && action_code == SQLITE_ATTACH &&
param1 != nullptr && HasSQLiteVfsUriParameter(param1)) {
return SQLITE_DENY;
}

Local<Value> cb =
db->object()->GetInternalField(kAuthorizerCallback).template As<Value>();
if (!cb->IsFunction()) return SQLITE_OK;

CHECK(cb->IsFunction());

Environment* env = db->env();
Isolate* isolate = env->isolate();
HandleScope handle_scope(isolate);
Local<Context> context = env->context();
Local<Function> callback = cb.As<Function>();

LocalVector<Value> js_argv(
Expand Down
6 changes: 6 additions & 0 deletions src/node_sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
#include "base_object.h"
#include "lru_cache-inl.h"
#include "node_mem.h"
#include "node_sqlite_vfs.h"
#include "sqlite3.h"
#include "util.h"

#include <array>
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <string_view>
#include <unordered_set>
Expand Down Expand Up @@ -220,6 +222,9 @@ class DatabaseSync : public BaseObject {
return open_config_.get_allow_unknown_named_params();
}
sqlite3* Connection();
std::shared_ptr<SQLitePermissionVFS> PermissionVFS() const {
return permission_vfs_;
}

// In some situations, such as when using custom functions, it is possible
// that SQLite reports an error while JavaScript already has a pending
Expand All @@ -241,6 +246,7 @@ class DatabaseSync : public BaseObject {
bool enable_load_extension_;
sqlite3* connection_;
bool ignore_next_sqlite_error_;
std::shared_ptr<SQLitePermissionVFS> permission_vfs_;

std::set<BackupJob*> backups_;
std::set<sqlite3_session*> sessions_;
Expand Down
Loading
Loading