diff --git a/docs/api.md b/docs/api.md index 0bb8997d2..d07d663e0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -11,6 +11,7 @@ - [Database#pragma()](#pragmastring-options---results) - [Database#backup()](#backupdestination-options---promise) - [Database#function()](#functionname-options-function---this) +- [Database#setAuthorizer()](#setauthorizerfn---this) - [Database#aggregate()](#aggregatename-options---this) - [Database#loadExtension()](#loadextensionpath-entrypoint---this) - [Database#exec()](#execstring---this) @@ -176,6 +177,27 @@ db.prepare("SELECT void()").pluck().get(); // => null db.prepare("SELECT void(?, ?)").pluck().get(55, 19); // => null ``` +### .setAuthorizer(fn) -> *this* + +Register a [compile-time authorization callback](https://sqlite.org/c3ref/set_authorizer.html) function. + +```js +db.setAuthorizer(function(op, a0, a1, database, trigger){ + // do something with the arg + // and return the decision as either SQLITE_OK, SQLITE_DENY or SQLITE_IGNORE + // not returning any value or throwing an error will cause the driver to return SQLITE_ERROR + return SQLITE_DENY; // for fun :) +}); + +db.prepare("SELECT * FROM users") // this will throw SqliteError: authorization error +``` + +The authorizer callback is invoked as SQL statements are being compiled by `sqlite3_prepare()` or its variants. The callback function receives as argument the [action code](https://sqlite.org/c3ref/c_alter_table.html)and the related parameters, along with the current _database_ and _trigger_ name. + +The callback function _must_ accept exactly 5 arguments. Any given database can only have a single authorizer function attached to it at any given point in time. + +To unset a previously registered authorizer invoke `setAuthorizer` with a `null` argument. + ### .aggregate(*name*, *options*) -> *this* Registers a user-defined [aggregate function](https://sqlite.org/lang_aggfunc.html). diff --git a/lib/authorizer.js b/lib/authorizer.js new file mode 100644 index 000000000..ae9419ff0 --- /dev/null +++ b/lib/authorizer.js @@ -0,0 +1,17 @@ +'use strict'; + +module.exports = (setAuthorizer) => { + return function defineAuthorizer(fn) { + if(fn !== null) { // must be a valid function if not null + if (typeof fn !== 'function') { + throw new TypeError('Expected argument to be a function'); + } + + if(fn.length != 5) { + throw new RangeError('Authorizer function must accept exactly 5 arguments') + } + } + + return setAuthorizer.call(this, fn) + } +} \ No newline at end of file diff --git a/lib/codes.js b/lib/codes.js new file mode 100644 index 000000000..577b27b01 --- /dev/null +++ b/lib/codes.js @@ -0,0 +1,2 @@ +// Codes.js - export sqlite codes / constants https://sqlite.org/c3ref/constlist.html +module.exports = require('bindings')('better_sqlite3.node').Codes \ No newline at end of file diff --git a/lib/database.js b/lib/database.js index 19851cfd8..8b41a8a9f 100644 --- a/lib/database.js +++ b/lib/database.js @@ -37,6 +37,7 @@ function Database(filenameGiven, options) { setErrorConstructor(require('./sqlite-error')); util.wrap(CPPDatabase, 'pragma', require('./pragma')); util.wrap(CPPDatabase, 'function', require('./function')); +util.wrap(CPPDatabase, 'setAuthorizer', require('./authorizer')); util.wrap(CPPDatabase, 'aggregate', require('./aggregate')); util.wrap(CPPDatabase, 'backup', require('./backup')); CPPDatabase.prototype.transaction = require('./transaction'); diff --git a/package.json b/package.json index cde0826f9..926bf6199 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "chai": "^4.2.0", + "chai-spies": "^1.0.0", "cli-color": "^2.0.0", "fs-extra": "^8.1.0", "mocha": "^7.0.1", diff --git a/src/better_sqlite3.cpp b/src/better_sqlite3.cpp index ab362d4f2..61bf408f7 100644 --- a/src/better_sqlite3.cpp +++ b/src/better_sqlite3.cpp @@ -2,7 +2,7 @@ // #include "better_sqlite3.hpp" -#line 67 "./src/better_sqlite3.lzz" +#line 70 "./src/better_sqlite3.lzz" NODE_MODULE_INIT(/* exports, context */) { v8::Isolate* isolate = context->GetIsolate(); v8::HandleScope scope(isolate); @@ -18,6 +18,7 @@ NODE_MODULE_INIT(/* exports, context */) { exports->Set(context, InternalizedFromLatin1(isolate, "StatementIterator"), StatementIterator::Init(isolate, data)).FromJust(); exports->Set(context, InternalizedFromLatin1(isolate, "Backup"), Backup::Init(isolate, data)).FromJust(); exports->Set(context, InternalizedFromLatin1(isolate, "setErrorConstructor"), v8::FunctionTemplate::New(isolate, Addon::JS_setErrorConstructor, data)->GetFunction(context).ToLocalChecked()).FromJust(); + exports->Set(context, InternalizedFromLatin1(isolate, "Codes"), Codes::New(isolate, context)).FromJust(); // Store addon instance data. addon->Statement.Reset(isolate, v8::Local::Cast(exports->Get(context, InternalizedFromLatin1(isolate, "Statement")).ToLocalChecked())); @@ -322,45 +323,46 @@ v8::Local Database::Init (v8::Isolate * isolate, v8::Local GetFunction( isolate -> GetCurrentContext ( ) ).ToLocalChecked(); } -#line 23 "./src/objects/database.lzz" +#line 24 "./src/objects/database.lzz" bool Database::CompareDatabase::operator () (Database const * const a, Database const * const b) const -#line 23 "./src/objects/database.lzz" +#line 24 "./src/objects/database.lzz" { return a < b; } -#line 28 "./src/objects/database.lzz" +#line 29 "./src/objects/database.lzz" bool Database::CompareStatement::operator () (Statement const * const a, Statement const * const b) const -#line 28 "./src/objects/database.lzz" +#line 29 "./src/objects/database.lzz" { return Statement::Compare(a, b); } -#line 33 "./src/objects/database.lzz" +#line 34 "./src/objects/database.lzz" bool Database::CompareBackup::operator () (Backup const * const a, Backup const * const b) const -#line 33 "./src/objects/database.lzz" +#line 34 "./src/objects/database.lzz" { return Backup::Compare(a, b); } -#line 39 "./src/objects/database.lzz" +#line 40 "./src/objects/database.lzz" void Database::ThrowDatabaseError () -#line 39 "./src/objects/database.lzz" +#line 40 "./src/objects/database.lzz" { if (was_js_error) was_js_error = false; else ThrowSqliteError(addon, db_handle); } -#line 43 "./src/objects/database.lzz" +#line 44 "./src/objects/database.lzz" void Database::ThrowSqliteError (Addon * addon, sqlite3 * db_handle) -#line 43 "./src/objects/database.lzz" +#line 44 "./src/objects/database.lzz" { assert(db_handle != NULL); ThrowSqliteError(addon, sqlite3_errmsg(db_handle), sqlite3_extended_errcode(db_handle)); } -#line 47 "./src/objects/database.lzz" +#line 48 "./src/objects/database.lzz" void Database::ThrowSqliteError (Addon * addon, char const * message, int code) -#line 47 "./src/objects/database.lzz" +#line 48 "./src/objects/database.lzz" { assert(message != NULL); assert((code & 0xff) != SQLITE_OK); @@ -375,9 +377,9 @@ void Database::ThrowSqliteError (Addon * addon, char const * message, int code) ->NewInstance( isolate -> GetCurrentContext ( ) , 2, args) .ToLocalChecked()); } -#line 63 "./src/objects/database.lzz" +#line 64 "./src/objects/database.lzz" bool Database::Log (v8::Isolate * isolate, sqlite3_stmt * handle) -#line 63 "./src/objects/database.lzz" +#line 64 "./src/objects/database.lzz" { assert(was_js_error == false); if (!has_logger) return false; @@ -389,9 +391,9 @@ bool Database::Log (v8::Isolate * isolate, sqlite3_stmt * handle) if (expanded) sqlite3_free(expanded); return was_js_error; } -#line 106 "./src/objects/database.lzz" +#line 107 "./src/objects/database.lzz" void Database::CloseHandles () -#line 106 "./src/objects/database.lzz" +#line 107 "./src/objects/database.lzz" { if (open) { open = false; @@ -403,25 +405,26 @@ void Database::CloseHandles () assert(status == SQLITE_OK); ((void)status); } } -#line 118 "./src/objects/database.lzz" +#line 119 "./src/objects/database.lzz" Database::~ Database () -#line 118 "./src/objects/database.lzz" +#line 119 "./src/objects/database.lzz" { if (open) addon->dbs.erase(this); + if (this->authorizerFunction) delete this->authorizerFunction; CloseHandles(); } -#line 125 "./src/objects/database.lzz" +#line 127 "./src/objects/database.lzz" Database::Database (sqlite3 * _db_handle, v8::Isolate * isolate, Addon * _addon, v8::Local _logger) -#line 125 "./src/objects/database.lzz" - : node::ObjectWrap (), db_handle (_db_handle), open (true), busy (false), safe_ints (false), unsafe_mode (false), was_js_error (false), has_logger (_logger->IsFunction()), iterators (0), addon (_addon), logger (isolate, _logger), stmts (), backups () -#line 137 "./src/objects/database.lzz" - { +#line 127 "./src/objects/database.lzz" + : node::ObjectWrap (), db_handle (_db_handle), open (true), busy (false), safe_ints (false), unsafe_mode (false), was_js_error (false), has_logger (_logger->IsFunction()), iterators (0), addon (_addon), logger (isolate, _logger), stmts (), backups (), authorizerFunction (NULL) +#line 140 "./src/objects/database.lzz" + { assert(_db_handle != NULL); addon->dbs.insert(this); } -#line 142 "./src/objects/database.lzz" +#line 145 "./src/objects/database.lzz" void Database::JS_new (v8::FunctionCallbackInfo const & info) -#line 142 "./src/objects/database.lzz" +#line 145 "./src/objects/database.lzz" { assert(info.IsConstructCall()); if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > filename = v8 :: Local < v8 :: String > :: Cast ( info [ 0 ] ) ; @@ -466,9 +469,9 @@ void Database::JS_new (v8::FunctionCallbackInfo const & info) info.GetReturnValue().Set(info.This()); } -#line 187 "./src/objects/database.lzz" +#line 190 "./src/objects/database.lzz" void Database::JS_prepare (v8::FunctionCallbackInfo const & info) -#line 187 "./src/objects/database.lzz" +#line 190 "./src/objects/database.lzz" { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > source = v8 :: Local < v8 :: String > :: Cast ( info [ 0 ] ) ; (void)source; @@ -480,9 +483,9 @@ void Database::JS_prepare (v8::FunctionCallbackInfo const & info) addon->privileged_info = NULL; if (!maybe_statement.IsEmpty()) info.GetReturnValue().Set(maybe_statement.ToLocalChecked()); } -#line 199 "./src/objects/database.lzz" +#line 202 "./src/objects/database.lzz" void Database::JS_exec (v8::FunctionCallbackInfo const & info) -#line 199 "./src/objects/database.lzz" +#line 202 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap (info.This()); if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > source = v8 :: Local < v8 :: String > :: Cast ( info [ 0 ] ) ; @@ -521,16 +524,16 @@ void Database::JS_exec (v8::FunctionCallbackInfo const & info) if (status == SQLITE_OK) info.GetReturnValue().Set(info.This()); else db->ThrowDatabaseError(); } -#line 238 "./src/objects/database.lzz" +#line 241 "./src/objects/database.lzz" void Database::JS_pragma (v8::FunctionCallbackInfo const & info) -#line 238 "./src/objects/database.lzz" +#line 241 "./src/objects/database.lzz" { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; bool active = v8 :: Local < v8 :: Boolean > :: Cast ( info [ 0 ] ) -> Value ( ) ; static_cast < Addon * > ( v8 :: Local < v8 :: External > :: Cast ( info . Data ( ) ) -> Value ( ) ) ->SetPragmaMode(active); } -#line 243 "./src/objects/database.lzz" +#line 246 "./src/objects/database.lzz" void Database::JS_backup (v8::FunctionCallbackInfo const & info) -#line 243 "./src/objects/database.lzz" +#line 246 "./src/objects/database.lzz" { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > attachedName = v8 :: Local < v8 :: String > :: Cast ( info [ 0 ] ) ; if ( info . Length ( ) <= ( 1 ) || ! info [ 1 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "second" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > destFile = v8 :: Local < v8 :: String > :: Cast ( info [ 1 ] ) ; @@ -546,9 +549,9 @@ void Database::JS_backup (v8::FunctionCallbackInfo const & info) addon->privileged_info = NULL; if (!maybe_backup.IsEmpty()) info.GetReturnValue().Set(maybe_backup.ToLocalChecked()); } -#line 259 "./src/objects/database.lzz" +#line 262 "./src/objects/database.lzz" void Database::JS_function (v8::FunctionCallbackInfo const & info) -#line 259 "./src/objects/database.lzz" +#line 262 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap (info.This()); if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsFunction ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a function" ) ; v8 :: Local < v8 :: Function > fn = v8 :: Local < v8 :: Function > :: Cast ( info [ 0 ] ) ; @@ -570,9 +573,38 @@ void Database::JS_function (v8::FunctionCallbackInfo const & info) } info.GetReturnValue().Set(info.This()); } -#line 281 "./src/objects/database.lzz" +#line 284 "./src/objects/database.lzz" +void Database::JS_setAuthorizer (v8::FunctionCallbackInfo const & info) +#line 284 "./src/objects/database.lzz" + { + Database* db = node :: ObjectWrap :: Unwrap (info.This()); + if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; + if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; + + if (db->authorizerFunction) { + Authorizerfunction* fn = db->authorizerFunction; + db->authorizerFunction = NULL; + delete fn; + } + + v8 :: Isolate * isolate = info . GetIsolate ( ) ; + if(info[ 0 ]->IsNull()) { + + if (sqlite3_set_authorizer(db->db_handle, NULL, NULL) != SQLITE_OK) { + return db->ThrowDatabaseError(); + } + } else { + if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsFunction ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a function" ) ; v8 :: Local < v8 :: Function > fn = v8 :: Local < v8 :: Function > :: Cast ( info [ 0 ] ) ; + db->authorizerFunction = new Authorizerfunction(isolate, fn); + if (sqlite3_set_authorizer(db->db_handle, Authorizerfunction::xAuth, db->authorizerFunction) != SQLITE_OK) { + return db->ThrowDatabaseError(); + } + } + info.GetReturnValue().Set(info.This()); +} +#line 311 "./src/objects/database.lzz" void Database::JS_aggregate (v8::FunctionCallbackInfo const & info) -#line 281 "./src/objects/database.lzz" +#line 311 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap (info.This()); if ( info . Length ( ) <= ( 0 ) ) return ThrowTypeError ( "Expected a " "first" " argument" ) ; v8 :: Local < v8 :: Value > start = info [ 0 ] ; @@ -599,9 +631,9 @@ void Database::JS_aggregate (v8::FunctionCallbackInfo const & info } info.GetReturnValue().Set(info.This()); } -#line 308 "./src/objects/database.lzz" +#line 338 "./src/objects/database.lzz" void Database::JS_loadExtension (v8::FunctionCallbackInfo const & info) -#line 308 "./src/objects/database.lzz" +#line 338 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap (info.This()); v8::Local entryPoint; @@ -622,9 +654,9 @@ void Database::JS_loadExtension (v8::FunctionCallbackInfo const & else ThrowSqliteError(db->addon, error, status); sqlite3_free(error); } -#line 329 "./src/objects/database.lzz" +#line 359 "./src/objects/database.lzz" void Database::JS_close (v8::FunctionCallbackInfo const & info) -#line 329 "./src/objects/database.lzz" +#line 359 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap (info.This()); if (db->open) { @@ -635,18 +667,18 @@ void Database::JS_close (v8::FunctionCallbackInfo const & info) } info.GetReturnValue().Set(info.This()); } -#line 340 "./src/objects/database.lzz" +#line 370 "./src/objects/database.lzz" void Database::JS_defaultSafeIntegers (v8::FunctionCallbackInfo const & info) -#line 340 "./src/objects/database.lzz" +#line 370 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap (info.This()); if (info.Length() == 0) db->safe_ints = true; else { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; db -> safe_ints = v8 :: Local < v8 :: Boolean > :: Cast ( info [ 0 ] ) -> Value ( ) ; } info.GetReturnValue().Set(info.This()); } -#line 347 "./src/objects/database.lzz" +#line 377 "./src/objects/database.lzz" void Database::JS_unsafeMode (v8::FunctionCallbackInfo const & info) -#line 347 "./src/objects/database.lzz" +#line 377 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap (info.This()); if (info.Length() == 0) db->unsafe_mode = true; @@ -654,22 +686,22 @@ void Database::JS_unsafeMode (v8::FunctionCallbackInfo const & inf sqlite3_db_config(db->db_handle, SQLITE_DBCONFIG_DEFENSIVE, static_cast(!db->unsafe_mode), NULL); info.GetReturnValue().Set(info.This()); } -#line 355 "./src/objects/database.lzz" +#line 385 "./src/objects/database.lzz" void Database::JS_open (v8::Local _, v8::PropertyCallbackInfo const & info) -#line 355 "./src/objects/database.lzz" +#line 385 "./src/objects/database.lzz" { info.GetReturnValue().Set( node :: ObjectWrap :: Unwrap (info.This())->open); } -#line 359 "./src/objects/database.lzz" +#line 389 "./src/objects/database.lzz" void Database::JS_inTransaction (v8::Local _, v8::PropertyCallbackInfo const & info) -#line 359 "./src/objects/database.lzz" +#line 389 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap (info.This()); info.GetReturnValue().Set(db->open && !static_cast(sqlite3_get_autocommit(db->db_handle))); } -#line 364 "./src/objects/database.lzz" +#line 394 "./src/objects/database.lzz" int const Database::MAX_BUFFER_SIZE; -#line 365 "./src/objects/database.lzz" +#line 395 "./src/objects/database.lzz" int const Database::MAX_STRING_SIZE; #line 5 "./src/objects/statement.lzz" v8::Local Statement::Init (v8::Isolate * isolate, v8::Local data) @@ -1658,22 +1690,106 @@ Binder::Result Binder::BindArgs (v8::FunctionCallbackInfo const & return { count, bound_object }; } -#line 32 "./src/better_sqlite3.lzz" +#line 3 "./src/util/authorizer-function.lzz" +Authorizerfunction::Authorizerfunction (v8::Isolate * _isolate, v8::Local _fn) +#line 4 "./src/util/authorizer-function.lzz" + : isolate (_isolate), fn (_isolate, _fn) +#line 4 "./src/util/authorizer-function.lzz" + {} +#line 6 "./src/util/authorizer-function.lzz" +int Authorizerfunction::xAuth (void * p, int op, char const * a0, char const * a1, char const * database, char const * trigger) +#line 6 "./src/util/authorizer-function.lzz" + { + Authorizerfunction* self = static_cast(p); + v8::Isolate* isolate = self->isolate; + v8::HandleScope scope(isolate); + + v8::Local args[5]; + args[0] = v8::Integer::New(isolate, op); + args[1] = a0? StringFromUtf8(isolate, a0, -1) : v8::String::Empty(isolate); + args[2] = a1? StringFromUtf8(isolate, a1, -1) : v8::String::Empty(isolate); + args[3] = database? StringFromUtf8(isolate, database, -1) : v8::String::Empty(isolate); + args[4] = trigger? StringFromUtf8(isolate, trigger, -1) : v8::String::Empty(isolate); + + v8::MaybeLocal maybe_return_value = v8::Local::New(isolate, self->fn)->Call( isolate -> GetCurrentContext ( ) , v8::Undefined(isolate), 5, args); + if(!maybe_return_value.IsEmpty()) { + v8::Local value = maybe_return_value.ToLocalChecked(); + if(value->IsNumber()) { + return v8::Local::Cast(value)->Value(); + } + } + + return SQLITE_ERROR; +} +#line 1 "./src/util/codes.lzz" +namespace Codes +{ +#line 5 "./src/util/codes.lzz" + v8::Local New (v8::Isolate * isolate, v8::Local context) +#line 5 "./src/util/codes.lzz" + { + v8::Local codes = v8::Object::New(isolate); + + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "OK" ) , v8 :: Integer :: New ( isolate , SQLITE_OK ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DENY" ) , v8 :: Integer :: New ( isolate , SQLITE_DENY ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "IGNORE" ) , v8 :: Integer :: New ( isolate , SQLITE_IGNORE ) ) . FromJust ( ) ; + + + + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "CREATE_INDEX" ) , v8 :: Integer :: New ( isolate , SQLITE_CREATE_INDEX ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "CREATE_TABLE" ) , v8 :: Integer :: New ( isolate , SQLITE_CREATE_TABLE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "CREATE_TEMP_INDEX" ) , v8 :: Integer :: New ( isolate , SQLITE_CREATE_TEMP_INDEX ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "CREATE_TEMP_TABLE" ) , v8 :: Integer :: New ( isolate , SQLITE_CREATE_TEMP_TABLE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "CREATE_TEMP_TRIGGER" ) , v8 :: Integer :: New ( isolate , SQLITE_CREATE_TEMP_TRIGGER ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "CREATE_TEMP_VIEW" ) , v8 :: Integer :: New ( isolate , SQLITE_CREATE_TEMP_VIEW ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "CREATE_TRIGGER" ) , v8 :: Integer :: New ( isolate , SQLITE_CREATE_TRIGGER ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "CREATE_VIEW" ) , v8 :: Integer :: New ( isolate , SQLITE_CREATE_VIEW ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DELETE" ) , v8 :: Integer :: New ( isolate , SQLITE_DELETE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DROP_INDEX" ) , v8 :: Integer :: New ( isolate , SQLITE_DROP_INDEX ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DROP_TABLE" ) , v8 :: Integer :: New ( isolate , SQLITE_DROP_TABLE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DROP_TEMP_INDEX" ) , v8 :: Integer :: New ( isolate , SQLITE_DROP_TEMP_INDEX ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DROP_TEMP_TABLE" ) , v8 :: Integer :: New ( isolate , SQLITE_DROP_TEMP_TABLE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DROP_TEMP_TRIGGER" ) , v8 :: Integer :: New ( isolate , SQLITE_DROP_TEMP_TRIGGER ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DROP_TEMP_VIEW" ) , v8 :: Integer :: New ( isolate , SQLITE_DROP_TEMP_VIEW ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DROP_TRIGGER" ) , v8 :: Integer :: New ( isolate , SQLITE_DROP_TRIGGER ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DROP_VIEW" ) , v8 :: Integer :: New ( isolate , SQLITE_DROP_VIEW ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "INSERT" ) , v8 :: Integer :: New ( isolate , SQLITE_INSERT ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "PRAGMA" ) , v8 :: Integer :: New ( isolate , SQLITE_PRAGMA ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "READ" ) , v8 :: Integer :: New ( isolate , SQLITE_READ ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "SELECT" ) , v8 :: Integer :: New ( isolate , SQLITE_SELECT ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "TRANSACTION" ) , v8 :: Integer :: New ( isolate , SQLITE_TRANSACTION ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "UPDATE" ) , v8 :: Integer :: New ( isolate , SQLITE_UPDATE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "ATTACH" ) , v8 :: Integer :: New ( isolate , SQLITE_ATTACH ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DETACH" ) , v8 :: Integer :: New ( isolate , SQLITE_DETACH ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "ALTER_TABLE" ) , v8 :: Integer :: New ( isolate , SQLITE_ALTER_TABLE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "REINDEX" ) , v8 :: Integer :: New ( isolate , SQLITE_REINDEX ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "ANALYZE" ) , v8 :: Integer :: New ( isolate , SQLITE_ANALYZE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "CREATE_VTABLE" ) , v8 :: Integer :: New ( isolate , SQLITE_CREATE_VTABLE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "DROP_VTABLE" ) , v8 :: Integer :: New ( isolate , SQLITE_DROP_VTABLE ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "FUNCTION" ) , v8 :: Integer :: New ( isolate , SQLITE_FUNCTION ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "SAVEPOINT" ) , v8 :: Integer :: New ( isolate , SQLITE_SAVEPOINT ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "COPY" ) , v8 :: Integer :: New ( isolate , SQLITE_COPY ) ) . FromJust ( ) ; + codes -> Set ( context , InternalizedFromLatin1 ( isolate , "SQLITE_" "RECURSIVE" ) , v8 :: Integer :: New ( isolate , SQLITE_RECURSIVE ) ) . FromJust ( ) ; + + return codes; + } +} +#line 35 "./src/better_sqlite3.lzz" Addon::Addon (v8::Isolate * isolate) -#line 32 "./src/better_sqlite3.lzz" +#line 35 "./src/better_sqlite3.lzz" : privileged_info (NULL), bit_field (0), cs (isolate) -#line 32 "./src/better_sqlite3.lzz" +#line 35 "./src/better_sqlite3.lzz" {} -#line 43 "./src/better_sqlite3.lzz" +#line 46 "./src/better_sqlite3.lzz" void Addon::JS_setErrorConstructor (v8::FunctionCallbackInfo const & info) -#line 43 "./src/better_sqlite3.lzz" +#line 46 "./src/better_sqlite3.lzz" { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsFunction ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a function" ) ; v8 :: Local < v8 :: Function > SqliteError = v8 :: Local < v8 :: Function > :: Cast ( info [ 0 ] ) ; static_cast < Addon * > ( v8 :: Local < v8 :: External > :: Cast ( info . Data ( ) ) -> Value ( ) ) ->SqliteError.Reset( info . GetIsolate ( ) , SqliteError); } -#line 48 "./src/better_sqlite3.lzz" +#line 51 "./src/better_sqlite3.lzz" void Addon::Cleanup (void * ptr) -#line 48 "./src/better_sqlite3.lzz" +#line 51 "./src/better_sqlite3.lzz" { Addon* addon = static_cast(ptr); for (Database* db : addon->dbs) db->CloseHandles(); diff --git a/src/better_sqlite3.hpp b/src/better_sqlite3.hpp index 00cb48954..98ec7c426 100644 --- a/src/better_sqlite3.hpp +++ b/src/better_sqlite3.hpp @@ -166,6 +166,8 @@ struct Addon; class Statement; #line 21 "./src/better_sqlite3.lzz" class Backup; +#line 22 "./src/better_sqlite3.lzz" +class Authorizerfunction; #line 1 "./src/objects/database.lzz" class Database : public node::ObjectWrap { @@ -173,134 +175,138 @@ class Database : public node::ObjectWrap public: #line 4 "./src/objects/database.lzz" static v8::Local Init (v8::Isolate * isolate, v8::Local data); -#line 22 "./src/objects/database.lzz" +#line 23 "./src/objects/database.lzz" class CompareDatabase { -#line 22 "./src/objects/database.lzz" - public: #line 23 "./src/objects/database.lzz" + public: +#line 24 "./src/objects/database.lzz" bool operator () (Database const * const a, Database const * const b) const; }; -#line 27 "./src/objects/database.lzz" +#line 28 "./src/objects/database.lzz" class CompareStatement { -#line 27 "./src/objects/database.lzz" - public: #line 28 "./src/objects/database.lzz" + public: +#line 29 "./src/objects/database.lzz" bool operator () (Statement const * const a, Statement const * const b) const; }; -#line 32 "./src/objects/database.lzz" +#line 33 "./src/objects/database.lzz" class CompareBackup { -#line 32 "./src/objects/database.lzz" - public: #line 33 "./src/objects/database.lzz" + public: +#line 34 "./src/objects/database.lzz" bool operator () (Backup const * const a, Backup const * const b) const; }; -#line 39 "./src/objects/database.lzz" +#line 40 "./src/objects/database.lzz" void ThrowDatabaseError (); -#line 43 "./src/objects/database.lzz" +#line 44 "./src/objects/database.lzz" static void ThrowSqliteError (Addon * addon, sqlite3 * db_handle); -#line 47 "./src/objects/database.lzz" +#line 48 "./src/objects/database.lzz" static void ThrowSqliteError (Addon * addon, char const * message, int code); -#line 63 "./src/objects/database.lzz" +#line 64 "./src/objects/database.lzz" bool Log (v8::Isolate * isolate, sqlite3_stmt * handle); -#line 76 "./src/objects/database.lzz" - void AddStatement (Statement * stmt); #line 77 "./src/objects/database.lzz" + void AddStatement (Statement * stmt); +#line 78 "./src/objects/database.lzz" void RemoveStatement (Statement * stmt); -#line 80 "./src/objects/database.lzz" - void AddBackup (Backup * backup); #line 81 "./src/objects/database.lzz" + void AddBackup (Backup * backup); +#line 82 "./src/objects/database.lzz" void RemoveBackup (Backup * backup); -#line 85 "./src/objects/database.lzz" +#line 86 "./src/objects/database.lzz" struct State { -#line 86 "./src/objects/database.lzz" - bool const open; #line 87 "./src/objects/database.lzz" - bool busy; + bool const open; #line 88 "./src/objects/database.lzz" - bool const safe_ints; + bool busy; #line 89 "./src/objects/database.lzz" - bool const unsafe_mode; + bool const safe_ints; #line 90 "./src/objects/database.lzz" - bool was_js_error; + bool const unsafe_mode; #line 91 "./src/objects/database.lzz" - bool const has_logger; + bool was_js_error; #line 92 "./src/objects/database.lzz" - unsigned short int iterators; + bool const has_logger; #line 93 "./src/objects/database.lzz" + unsigned short int iterators; +#line 94 "./src/objects/database.lzz" Addon * const addon; }; -#line 95 "./src/objects/database.lzz" +#line 96 "./src/objects/database.lzz" State * GetState (); -#line 98 "./src/objects/database.lzz" +#line 99 "./src/objects/database.lzz" sqlite3 * GetHandle (); -#line 101 "./src/objects/database.lzz" +#line 102 "./src/objects/database.lzz" Addon * GetAddon (); -#line 106 "./src/objects/database.lzz" +#line 107 "./src/objects/database.lzz" void CloseHandles (); -#line 118 "./src/objects/database.lzz" +#line 119 "./src/objects/database.lzz" ~ Database (); -#line 123 "./src/objects/database.lzz" -private: #line 125 "./src/objects/database.lzz" +private: +#line 127 "./src/objects/database.lzz" explicit Database (sqlite3 * _db_handle, v8::Isolate * isolate, Addon * _addon, v8::Local _logger); -#line 142 "./src/objects/database.lzz" +#line 145 "./src/objects/database.lzz" static void JS_new (v8::FunctionCallbackInfo const & info); -#line 187 "./src/objects/database.lzz" +#line 190 "./src/objects/database.lzz" static void JS_prepare (v8::FunctionCallbackInfo const & info); -#line 199 "./src/objects/database.lzz" +#line 202 "./src/objects/database.lzz" static void JS_exec (v8::FunctionCallbackInfo const & info); -#line 238 "./src/objects/database.lzz" +#line 241 "./src/objects/database.lzz" static void JS_pragma (v8::FunctionCallbackInfo const & info); -#line 243 "./src/objects/database.lzz" +#line 246 "./src/objects/database.lzz" static void JS_backup (v8::FunctionCallbackInfo const & info); -#line 259 "./src/objects/database.lzz" +#line 262 "./src/objects/database.lzz" static void JS_function (v8::FunctionCallbackInfo const & info); -#line 281 "./src/objects/database.lzz" +#line 284 "./src/objects/database.lzz" + static void JS_setAuthorizer (v8::FunctionCallbackInfo const & info); +#line 311 "./src/objects/database.lzz" static void JS_aggregate (v8::FunctionCallbackInfo const & info); -#line 308 "./src/objects/database.lzz" +#line 338 "./src/objects/database.lzz" static void JS_loadExtension (v8::FunctionCallbackInfo const & info); -#line 329 "./src/objects/database.lzz" +#line 359 "./src/objects/database.lzz" static void JS_close (v8::FunctionCallbackInfo const & info); -#line 340 "./src/objects/database.lzz" +#line 370 "./src/objects/database.lzz" static void JS_defaultSafeIntegers (v8::FunctionCallbackInfo const & info); -#line 347 "./src/objects/database.lzz" +#line 377 "./src/objects/database.lzz" static void JS_unsafeMode (v8::FunctionCallbackInfo const & info); -#line 355 "./src/objects/database.lzz" +#line 385 "./src/objects/database.lzz" static void JS_open (v8::Local _, v8::PropertyCallbackInfo const & info); -#line 359 "./src/objects/database.lzz" +#line 389 "./src/objects/database.lzz" static void JS_inTransaction (v8::Local _, v8::PropertyCallbackInfo const & info); -#line 364 "./src/objects/database.lzz" +#line 394 "./src/objects/database.lzz" static int const MAX_BUFFER_SIZE = node::Buffer::kMaxLength > INT_MAX ? INT_MAX : static_cast(node::Buffer::kMaxLength); -#line 365 "./src/objects/database.lzz" +#line 395 "./src/objects/database.lzz" static int const MAX_STRING_SIZE = v8::String::kMaxLength > INT_MAX ? INT_MAX : static_cast(v8::String::kMaxLength); -#line 367 "./src/objects/database.lzz" +#line 397 "./src/objects/database.lzz" sqlite3 * const db_handle; -#line 368 "./src/objects/database.lzz" +#line 398 "./src/objects/database.lzz" bool open; -#line 369 "./src/objects/database.lzz" +#line 399 "./src/objects/database.lzz" bool busy; -#line 370 "./src/objects/database.lzz" +#line 400 "./src/objects/database.lzz" bool safe_ints; -#line 371 "./src/objects/database.lzz" +#line 401 "./src/objects/database.lzz" bool unsafe_mode; -#line 372 "./src/objects/database.lzz" +#line 402 "./src/objects/database.lzz" bool was_js_error; -#line 373 "./src/objects/database.lzz" +#line 403 "./src/objects/database.lzz" bool const has_logger; -#line 374 "./src/objects/database.lzz" +#line 404 "./src/objects/database.lzz" unsigned short int iterators; -#line 375 "./src/objects/database.lzz" +#line 405 "./src/objects/database.lzz" Addon * const addon; -#line 376 "./src/objects/database.lzz" +#line 406 "./src/objects/database.lzz" CopyablePersistent const logger; -#line 377 "./src/objects/database.lzz" +#line 407 "./src/objects/database.lzz" std::set stmts; -#line 378 "./src/objects/database.lzz" +#line 408 "./src/objects/database.lzz" std::set backups; +#line 409 "./src/objects/database.lzz" + Authorizerfunction * authorizerFunction; }; #line 1 "./src/objects/statement.lzz" class Statement : public node::ObjectWrap @@ -644,36 +650,58 @@ class Binder #line 188 "./src/util/binder.lzz" bool success; }; -#line 31 "./src/better_sqlite3.lzz" +#line 1 "./src/util/authorizer-function.lzz" +class Authorizerfunction +{ +#line 2 "./src/util/authorizer-function.lzz" +public: +#line 3 "./src/util/authorizer-function.lzz" + explicit Authorizerfunction (v8::Isolate * _isolate, v8::Local _fn); +#line 6 "./src/util/authorizer-function.lzz" + static int xAuth (void * p, int op, char const * a0, char const * a1, char const * database, char const * trigger); +#line 29 "./src/util/authorizer-function.lzz" +protected: +#line 30 "./src/util/authorizer-function.lzz" + v8::Isolate * const isolate; +#line 31 "./src/util/authorizer-function.lzz" + CopyablePersistent const fn; +}; +#line 1 "./src/util/codes.lzz" +namespace Codes +{ +#line 5 "./src/util/codes.lzz" + v8::Local New (v8::Isolate * isolate, v8::Local context); +} +#line 34 "./src/better_sqlite3.lzz" struct Addon { -#line 32 "./src/better_sqlite3.lzz" +#line 35 "./src/better_sqlite3.lzz" Addon (v8::Isolate * isolate); -#line 34 "./src/better_sqlite3.lzz" +#line 37 "./src/better_sqlite3.lzz" CopyablePersistent Statement; -#line 35 "./src/better_sqlite3.lzz" +#line 38 "./src/better_sqlite3.lzz" CopyablePersistent StatementIterator; -#line 36 "./src/better_sqlite3.lzz" +#line 39 "./src/better_sqlite3.lzz" CopyablePersistent Backup; -#line 37 "./src/better_sqlite3.lzz" +#line 40 "./src/better_sqlite3.lzz" CopyablePersistent SqliteError; -#line 38 "./src/better_sqlite3.lzz" +#line 41 "./src/better_sqlite3.lzz" v8::FunctionCallbackInfo const * privileged_info; -#line 39 "./src/better_sqlite3.lzz" +#line 42 "./src/better_sqlite3.lzz" sqlite3_uint64 bit_field; -#line 40 "./src/better_sqlite3.lzz" +#line 43 "./src/better_sqlite3.lzz" CS cs; -#line 41 "./src/better_sqlite3.lzz" +#line 44 "./src/better_sqlite3.lzz" std::set dbs; -#line 43 "./src/better_sqlite3.lzz" +#line 46 "./src/better_sqlite3.lzz" static void JS_setErrorConstructor (v8::FunctionCallbackInfo const & info); -#line 48 "./src/better_sqlite3.lzz" +#line 51 "./src/better_sqlite3.lzz" static void Cleanup (void * ptr); -#line 55 "./src/better_sqlite3.lzz" - sqlite3_uint64 NextId (); #line 58 "./src/better_sqlite3.lzz" - bool PragmaMode (); + sqlite3_uint64 NextId (); #line 61 "./src/better_sqlite3.lzz" + bool PragmaMode (); +#line 64 "./src/better_sqlite3.lzz" void SetPragmaMode (bool active); }; #line 16 "./src/util/macros.lzz" @@ -757,41 +785,41 @@ LZZ_INLINE int BindMap::GetSize () #line 42 "./src/util/bind-map.lzz" { return length; } -#line 76 "./src/objects/database.lzz" +#line 77 "./src/objects/database.lzz" LZZ_INLINE void Database::AddStatement (Statement * stmt) -#line 76 "./src/objects/database.lzz" +#line 77 "./src/objects/database.lzz" { stmts.insert(stmts.end(), stmt); } -#line 77 "./src/objects/database.lzz" +#line 78 "./src/objects/database.lzz" LZZ_INLINE void Database::RemoveStatement (Statement * stmt) -#line 77 "./src/objects/database.lzz" +#line 78 "./src/objects/database.lzz" { stmts.erase(stmt); } -#line 80 "./src/objects/database.lzz" +#line 81 "./src/objects/database.lzz" LZZ_INLINE void Database::AddBackup (Backup * backup) -#line 80 "./src/objects/database.lzz" +#line 81 "./src/objects/database.lzz" { backups.insert(backups.end(), backup); } -#line 81 "./src/objects/database.lzz" +#line 82 "./src/objects/database.lzz" LZZ_INLINE void Database::RemoveBackup (Backup * backup) -#line 81 "./src/objects/database.lzz" +#line 82 "./src/objects/database.lzz" { backups.erase(backup); } -#line 95 "./src/objects/database.lzz" +#line 96 "./src/objects/database.lzz" LZZ_INLINE Database::State * Database::GetState () -#line 95 "./src/objects/database.lzz" +#line 96 "./src/objects/database.lzz" { return reinterpret_cast(&open); } -#line 98 "./src/objects/database.lzz" +#line 99 "./src/objects/database.lzz" LZZ_INLINE sqlite3 * Database::GetHandle () -#line 98 "./src/objects/database.lzz" +#line 99 "./src/objects/database.lzz" { return db_handle; } -#line 101 "./src/objects/database.lzz" +#line 102 "./src/objects/database.lzz" LZZ_INLINE Addon * Database::GetAddon () -#line 101 "./src/objects/database.lzz" +#line 102 "./src/objects/database.lzz" { return addon; } @@ -869,21 +897,21 @@ LZZ_INLINE void CustomAggregate::xValueBase (sqlite3_context * invocation, bool Data::ResultValueFromJS(isolate, invocation, result, self); if (is_final) DestroyAccumulator(invocation); } -#line 55 "./src/better_sqlite3.lzz" +#line 58 "./src/better_sqlite3.lzz" LZZ_INLINE sqlite3_uint64 Addon::NextId () -#line 55 "./src/better_sqlite3.lzz" +#line 58 "./src/better_sqlite3.lzz" { return (bit_field++ << 1) >> 1; } -#line 58 "./src/better_sqlite3.lzz" +#line 61 "./src/better_sqlite3.lzz" LZZ_INLINE bool Addon::PragmaMode () -#line 58 "./src/better_sqlite3.lzz" +#line 61 "./src/better_sqlite3.lzz" { return bit_field >> 63 != 0; } -#line 61 "./src/better_sqlite3.lzz" +#line 64 "./src/better_sqlite3.lzz" LZZ_INLINE void Addon::SetPragmaMode (bool active) -#line 61 "./src/better_sqlite3.lzz" +#line 64 "./src/better_sqlite3.lzz" { bit_field = (bit_field << 1) >> 1 | static_cast(active) << 63; } diff --git a/src/better_sqlite3.lzz b/src/better_sqlite3.lzz index 73c6fe478..c4018764a 100644 --- a/src/better_sqlite3.lzz +++ b/src/better_sqlite3.lzz @@ -19,6 +19,7 @@ struct Addon; class Statement; class Backup; +class Authorizerfunction; #insert "objects/database.lzz" #insert "objects/statement.lzz" #insert "objects/statement-iterator.lzz" @@ -27,6 +28,8 @@ class Backup; #insert "util/custom-aggregate.lzz" #insert "util/data.lzz" #insert "util/binder.lzz" +#insert "util/authorizer-function.lzz" +#insert "util/codes.lzz" struct Addon { Addon(v8::Isolate* isolate) : privileged_info(NULL), bit_field(0), cs(isolate) {} @@ -79,6 +82,7 @@ NODE_MODULE_INIT(/* exports, context */) { exports->Set(context, InternalizedFromLatin1(isolate, "StatementIterator"), StatementIterator::Init(isolate, data)).FromJust(); exports->Set(context, InternalizedFromLatin1(isolate, "Backup"), Backup::Init(isolate, data)).FromJust(); exports->Set(context, InternalizedFromLatin1(isolate, "setErrorConstructor"), v8::FunctionTemplate::New(isolate, Addon::JS_setErrorConstructor, data)->GetFunction(context).ToLocalChecked()).FromJust(); + exports->Set(context, InternalizedFromLatin1(isolate, "Codes"), Codes::New(isolate, context)).FromJust(); // Store addon instance data. addon->Statement.Reset(isolate, v8::Local::Cast(exports->Get(context, InternalizedFromLatin1(isolate, "Statement")).ToLocalChecked())); diff --git a/src/objects/database.lzz b/src/objects/database.lzz index ae515e2b7..52f0d1655 100644 --- a/src/objects/database.lzz +++ b/src/objects/database.lzz @@ -13,6 +13,7 @@ public: SetPrototypeMethod(isolate, data, t, "close", JS_close); SetPrototypeMethod(isolate, data, t, "defaultSafeIntegers", JS_defaultSafeIntegers); SetPrototypeMethod(isolate, data, t, "unsafeMode", JS_unsafeMode); + SetPrototypeMethod(isolate, data, t, "setAuthorizer", JS_setAuthorizer); SetPrototypeGetter(isolate, data, t, "open", JS_open); SetPrototypeGetter(isolate, data, t, "inTransaction", JS_inTransaction); return t->GetFunction(OnlyContext).ToLocalChecked(); @@ -117,6 +118,7 @@ public: ~Database() { if (open) addon->dbs.erase(this); + if (this->authorizerFunction) delete this->authorizerFunction; CloseHandles(); } @@ -134,7 +136,8 @@ private: addon(_addon), logger(isolate, _logger), stmts(), - backups() { + backups(), + authorizerFunction(NULL) { assert(_db_handle != NULL); addon->dbs.insert(this); } @@ -278,6 +281,33 @@ private: info.GetReturnValue().Set(info.This()); } + NODE_METHOD(JS_setAuthorizer) { + Database* db = Unwrap(info.This()); + REQUIRE_DATABASE_OPEN(db); + REQUIRE_DATABASE_NOT_BUSY(db); + + if (db->authorizerFunction) { + Authorizerfunction* fn = db->authorizerFunction; + db->authorizerFunction = NULL; + delete fn; + } + + UseIsolate; + if(info[first()]->IsNull()) { + // unset the authorizer + if (sqlite3_set_authorizer(db->db_handle, NULL, NULL) != SQLITE_OK) { + return db->ThrowDatabaseError(); + } + } else { + REQUIRE_ARGUMENT_FUNCTION(first, v8::Local fn); + db->authorizerFunction = new Authorizerfunction(isolate, fn); + if (sqlite3_set_authorizer(db->db_handle, Authorizerfunction::xAuth, db->authorizerFunction) != SQLITE_OK) { + return db->ThrowDatabaseError(); + } + } + info.GetReturnValue().Set(info.This()); + } + NODE_METHOD(JS_aggregate) { Database* db = Unwrap(info.This()); REQUIRE_ARGUMENT_ANY(first, v8::Local start); @@ -376,4 +406,5 @@ private: const CopyablePersistent logger; std::set stmts; std::set backups; + Authorizerfunction* authorizerFunction; }; diff --git a/src/util/authorizer-function.lzz b/src/util/authorizer-function.lzz new file mode 100644 index 000000000..cb2ea6208 --- /dev/null +++ b/src/util/authorizer-function.lzz @@ -0,0 +1,32 @@ +class Authorizerfunction { +public: + explicit Authorizerfunction(v8::Isolate* _isolate, v8::Local _fn) + : isolate(_isolate), fn(_isolate, _fn) { } + + static int xAuth(void* p, int op, const char* a0, const char* a1, const char* database, const char* trigger) { + Authorizerfunction* self = static_cast(p); + v8::Isolate* isolate = self->isolate; + v8::HandleScope scope(isolate); + + v8::Local args[5]; + args[0] = v8::Integer::New(isolate, op); + args[1] = a0? StringFromUtf8(isolate, a0, -1) : v8::String::Empty(isolate); + args[2] = a1? StringFromUtf8(isolate, a1, -1) : v8::String::Empty(isolate); + args[3] = database? StringFromUtf8(isolate, database, -1) : v8::String::Empty(isolate); + args[4] = trigger? StringFromUtf8(isolate, trigger, -1) : v8::String::Empty(isolate); + + v8::MaybeLocal maybe_return_value = v8::Local::New(isolate, self->fn)->Call(OnlyContext, v8::Undefined(isolate), 5, args); + if(!maybe_return_value.IsEmpty()) { + v8::Local value = maybe_return_value.ToLocalChecked(); + if(value->IsNumber()) { + return v8::Local::Cast(value)->Value(); + } + } + + return SQLITE_ERROR; + } + +protected: + v8::Isolate* const isolate; + const CopyablePersistent fn; +}; \ No newline at end of file diff --git a/src/util/codes.lzz b/src/util/codes.lzz new file mode 100644 index 000000000..65b059471 --- /dev/null +++ b/src/util/codes.lzz @@ -0,0 +1,53 @@ +namespace Codes { + + #define SQLITE_(name) codes->Set(context, InternalizedFromLatin1(isolate, "SQLITE_" #name), v8::Integer::New(isolate, SQLITE_##name)).FromJust(); + + v8::Local New(v8::Isolate* isolate, v8::Local context) { + v8::Local codes = v8::Object::New(isolate); + + SQLITE_(OK) + SQLITE_(DENY) + SQLITE_(IGNORE) + + //- compile time authorization callback codes + // see: https://sqlite.org/c3ref/c_alter_table.html + SQLITE_(CREATE_INDEX) + SQLITE_(CREATE_TABLE) + SQLITE_(CREATE_TEMP_INDEX) + SQLITE_(CREATE_TEMP_TABLE) + SQLITE_(CREATE_TEMP_TRIGGER) + SQLITE_(CREATE_TEMP_VIEW) + SQLITE_(CREATE_TRIGGER) + SQLITE_(CREATE_VIEW) + SQLITE_(DELETE) + SQLITE_(DROP_INDEX) + SQLITE_(DROP_TABLE) + SQLITE_(DROP_TEMP_INDEX) + SQLITE_(DROP_TEMP_TABLE) + SQLITE_(DROP_TEMP_TRIGGER) + SQLITE_(DROP_TEMP_VIEW) + SQLITE_(DROP_TRIGGER) + SQLITE_(DROP_VIEW) + SQLITE_(INSERT) + SQLITE_(PRAGMA) + SQLITE_(READ) + SQLITE_(SELECT) + SQLITE_(TRANSACTION) + SQLITE_(UPDATE) + SQLITE_(ATTACH) + SQLITE_(DETACH) + SQLITE_(ALTER_TABLE) + SQLITE_(REINDEX) + SQLITE_(ANALYZE) + SQLITE_(CREATE_VTABLE) + SQLITE_(DROP_VTABLE) + SQLITE_(FUNCTION) + SQLITE_(SAVEPOINT) + SQLITE_(COPY) + SQLITE_(RECURSIVE) + + // TODO: define other sqlite3 codes from https://www.sqlite.org/c3ref/constlist.html + + return codes; + } +} \ No newline at end of file diff --git a/test/36.database.set-authorizer.js b/test/36.database.set-authorizer.js new file mode 100644 index 000000000..df00460ab --- /dev/null +++ b/test/36.database.set-authorizer.js @@ -0,0 +1,66 @@ +'use strict'; +const chai = require('chai'); +chai.use(require('chai-spies')) + +const expect = chai.expect +const Database = require('../.'); +const { SQLITE_OK, SQLITE_DENY, SQLITE_SELECT, SQLITE_READ } = require('../lib/codes') +const SqliteError = require('../lib/sqlite-error'); + +describe('Database#setAuthorizer', function(){ + beforeEach(function () { + let db = new Database(util.next()); + db.exec('CREATE TABLE sample (id INTEGER PRIMARY KEY, site)') + + let add = db.prepare('INSERT INTO sample (site) VALUES (?)') + add.run('google.com') + add.run('facebook.com') + add.run('amazon.com') + add.run('netflix.com') + + this.db = db + }); + + afterEach(function () { + this.db.close(); + }); + + it('should throw an exception if the correct arguments are not provided', function () { + expect(() => this.db.setAuthorizer()).to.throw(TypeError) + expect(() => this.db.setAuthorizer(1)).to.throw(TypeError) + expect(() => this.db.setAuthorizer(undefined)).to.throw(TypeError) + expect(() => this.db.setAuthorizer((a, b) => { })).to.throw(RangeError) + }) + + it('should register the given function and return the database object', function () { + expect(this.db.setAuthorizer((op, a0, a1, d, t) => SQLITE_OK)).to.equal(this.db) + }) + + it('should invoke registered authorizer with arguments received from sqlite', function() { + let authorizer = (op, a0, a1, d, t) => SQLITE_OK + let spied = chai.spy(authorizer) + this.db.setAuthorizer(spied) + + this.db.prepare('SELECT site FROM sample').all() // result is not that important + + expect(spied).to.have.been.first.called.with(SQLITE_SELECT, "", "", "", "") // SELECT + expect(spied).to.have.been.second.called.with(SQLITE_READ, "sample", "site", "main", "") // READ + }) + + it('should properly return values from authorizer to ensure actions are blocked / allowed', function() { + // deny read on 'id' column + let authorizer = (op, a0, col, d, t) => { return (op == SQLITE_READ && col == 'id')? 1 : 0} + this.db.setAuthorizer(authorizer) + + expect(() => this.db.prepare('SELECT site FROM sample')).to.not.throw + expect(() => this.db.prepare('SELECT id, site FROM sample')).to.throw(SqliteError) + }) + + it('should unset authorizer if null is passed', function() { + let authorizer = (op, a0, a1, d, t) => SQLITE_DENY // deny all + this.db.setAuthorizer(authorizer) + expect(() => this.db.prepare('SELECT site FROM sample')).to.throw(SqliteError) + this.db.setAuthorizer(null) + expect(() => this.db.prepare('SELECT site FROM sample')).to.not.throw + }) +}) \ No newline at end of file