sqlite,test,doc: allow Buffer and URL as database location · nodejs/node@e80bbb7
@@ -7,6 +7,7 @@
77#include "node.h"
88#include "node_errors.h"
99#include "node_mem-inl.h"
10+#include "node_url.h"
1011#include "sqlite3.h"
1112#include "threadpoolwork-inl.h"
1213#include "util-inl.h"
@@ -181,10 +182,11 @@ class BackupJob : public ThreadPoolWork {
181182void ScheduleBackup() {
182183 Isolate* isolate = env()->isolate();
183184 HandleScope handle_scope(isolate);
184- backup_status_ = sqlite3_open_v2(destination_name_.c_str(),
185- &dest_,
186-SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
187-nullptr);
185+ backup_status_ = sqlite3_open_v2(
186+ destination_name_.c_str(),
187+ &dest_,
188+SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
189+nullptr);
188190 Local<Promise::Resolver> resolver =
189191 Local<Promise::Resolver>::New(env()->isolate(), resolver_);
190192if (backup_status_ != SQLITE_OK) {
@@ -503,11 +505,14 @@ bool DatabaseSync::Open() {
503505 }
504506505507// TODO(cjihrig): Support additional flags.
508+int default_flags = SQLITE_OPEN_URI;
506509int flags = open_config_.get_read_only()
507510 ? SQLITE_OPEN_READONLY
508511 : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
509-int r = sqlite3_open_v2(
510- open_config_.location().c_str(), &connection_, flags, nullptr);
512+int r = sqlite3_open_v2(open_config_.location().c_str(),
513+ &connection_,
514+ flags | default_flags,
515+nullptr);
511516CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
512517513518 r = sqlite3_db_config(connection_,
@@ -585,27 +590,85 @@ bool DatabaseSync::ShouldIgnoreSQLiteError() {
585590return ignore_next_sqlite_error_;
586591}
587592593+std::optional<std::string> ValidateDatabasePath(Environment* env,
594+ Local<Value> path,
595+const std::string& field_name) {
596+auto has_null_bytes = [](const std::string& str) {
597+return str.find('\0') != std::string::npos;
598+ };
599+ std::string location;
600+if (path->IsString()) {
601+ location = Utf8Value(env->isolate(), path.As<String>()).ToString();
602+if (!has_null_bytes(location)) {
603+return location;
604+ }
605+ }
606+607+if (path->IsUint8Array()) {
608+ Local<Uint8Array> buffer = path.As<Uint8Array>();
609+size_t byteOffset = buffer->ByteOffset();
610+size_t byteLength = buffer->ByteLength();
611+auto data =
612+static_cast<const uint8_t*>(buffer->Buffer()->Data()) + byteOffset;
613+if (!(std::find(data, data + byteLength, 0) != data + byteLength)) {
614+ Local<Value> out;
615+if (String::NewFromUtf8(env->isolate(),
616+reinterpret_cast<const char*>(data),
617+ NewStringType::kNormal,
618+static_cast<int>(byteLength))
619+ .ToLocal(&out)) {
620+return Utf8Value(env->isolate(), out.As<String>()).ToString();
621+ }
622+ }
623+ }
624+625+// When is URL
626+if (path->IsObject()) {
627+ Local<Object> url = path.As<Object>();
628+ Local<Value> href;
629+ Local<Value> protocol;
630+if (url->Get(env->context(), env->href_string()).ToLocal(&href) &&
631+ href->IsString() &&
632+ url->Get(env->context(), env->protocol_string()).ToLocal(&protocol) &&
633+ protocol->IsString()) {
634+ location = Utf8Value(env->isolate(), href.As<String>()).ToString();
635+if (!has_null_bytes(location)) {
636+auto file_url = ada::parse(location);
637+CHECK(file_url);
638+if (file_url->type != ada::scheme::FILE) {
639+THROW_ERR_INVALID_URL_SCHEME(env->isolate());
640+return std::nullopt;
641+ }
642+643+return location;
644+ }
645+ }
646+ }
647+648+THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
649+"The \"%s\" argument must be a string, "
650+"Uint8Array, or URL without null bytes.",
651+ field_name.c_str());
652+653+return std::nullopt;
654+}
655+588656void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
589657 Environment* env = Environment::GetCurrent(args);
590-591658if (!args.IsConstructCall()) {
592659THROW_ERR_CONSTRUCT_CALL_REQUIRED(env);
593660return;
594661 }
595662596-if (!args[0]->IsString()) {
597-THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
598- "The \"path\" argument must be a string.");
663+std::optional<std::string> location =
664+ ValidateDatabasePath(env, args[0], "path");
665+if (!location.has_value()) {
599666return;
600667 }
601668602- std::string location =
603-Utf8Value(env->isolate(), args[0].As<String>()).ToString();
604- DatabaseOpenConfiguration open_config(std::move(location));
605-669+ DatabaseOpenConfiguration open_config(std::move(location.value()));
606670bool open = true;
607671bool allow_load_extension = false;
608-609672if (args.Length() > 1) {
610673if (!args[1]->IsObject()) {
611674THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
@@ -984,17 +1047,15 @@ void Backup(const FunctionCallbackInfo<Value>& args) {
9841047 DatabaseSync* db;
9851048ASSIGN_OR_RETURN_UNWRAP(&db, args[0].As<Object>());
9861049THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
987-if (!args[1]->IsString()) {
988-THROW_ERR_INVALID_ARG_TYPE(
989- env->isolate(), "The \"destination\" argument must be a string.");
1050+std::optional<std::string> dest_path =
1051+ ValidateDatabasePath(env, args[1], "path");
1052+if (!dest_path.has_value()) {
9901053return;
9911054 }
99210559931056int rate = 100;
9941057 std::string source_db = "main";
9951058 std::string dest_db = "main";
996-997- Utf8Value dest_path(env->isolate(), args[1].As<String>());
9981059 Local<Function> progressFunc = Local<Function>();
999106010001061if (args.Length() > 2) {
@@ -1077,12 +1138,11 @@ void Backup(const FunctionCallbackInfo<Value>& args) {
10771138 }
1078113910791140 args.GetReturnValue().Set(resolver->GetPromise());
1080-10811141 BackupJob* job = new BackupJob(env,
10821142 db,
10831143 resolver,
10841144std::move(source_db),
1085-*dest_path,
1145+ dest_path.value(),
10861146std::move(dest_db),
10871147 rate,
10881148 progressFunc);