@mwguerra/hull 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +631 -0
- package/assets/hull-logo.png +0 -0
- package/assets/hull-logo.svg +5 -0
- package/bin/hull.js +4 -0
- package/devtools/dist/index.html +29 -0
- package/host/CMakeLists.txt +101 -0
- package/host/README.md +94 -0
- package/host/linux.Dockerfile +26 -0
- package/host/src/bindings/credentials.hpp +35 -0
- package/host/src/bindings/database.hpp +51 -0
- package/host/src/bindings/files.hpp +58 -0
- package/host/src/bindings/http.hpp +84 -0
- package/host/src/bindings/printer.hpp +281 -0
- package/host/src/bindings/storage.hpp +71 -0
- package/host/src/db_core.hpp +198 -0
- package/host/src/dispatcher.hpp +81 -0
- package/host/src/file_store.hpp +91 -0
- package/host/src/keychain.hpp +157 -0
- package/host/src/main.cpp +386 -0
- package/host/src/paths.hpp +62 -0
- package/host/src/secure.hpp +124 -0
- package/host/src/serve.hpp +113 -0
- package/host/test/db_test.cpp +80 -0
- package/host/test/secure_files_test.cpp +68 -0
- package/host/third_party/sqlite/sqlite3.c +269376 -0
- package/host/third_party/sqlite/sqlite3.h +14347 -0
- package/package.json +58 -0
- package/src/bridge/bridge-core.js +92 -0
- package/src/bridge/index.js +139 -0
- package/src/bridge/native-store.js +34 -0
- package/src/cli/build.js +122 -0
- package/src/cli/config.js +102 -0
- package/src/cli/dev.js +158 -0
- package/src/cli/eject.js +39 -0
- package/src/cli/host.js +61 -0
- package/src/cli/index.js +54 -0
- package/src/cli/installer.js +265 -0
- package/src/cli/release.js +178 -0
- package/src/cli/start.js +45 -0
- package/src/cli/timing.js +22 -0
- package/src/cli/vite.js +16 -0
- package/src/react/index.js +30 -0
- package/src/vue/index.js +31 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Standalone functional test for the SQLite core (db_core.hpp). Webview-free, so it
|
|
2
|
+
// links just sqlite3 + nlohmann/json. Run via the host's CMake test or directly:
|
|
3
|
+
// g++ -std=c++17 -I../src -I../third_party/sqlite db_test.cpp ../third_party/sqlite/sqlite3.c -lpthread -ldl -o dbtest && ./dbtest
|
|
4
|
+
#include <cstdio>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include "db_core.hpp"
|
|
8
|
+
|
|
9
|
+
static int failures = 0;
|
|
10
|
+
static void check(bool cond, const char* what) {
|
|
11
|
+
std::printf("%s %s\n", cond ? "ok " : "FAIL", what);
|
|
12
|
+
if (!cond) failures++;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
int main() {
|
|
16
|
+
// Use a throwaway file path (also exercises file open + lock_down).
|
|
17
|
+
const std::string path = (fs::temp_directory_path() / "hull_db_test.db").string();
|
|
18
|
+
std::remove(path.c_str());
|
|
19
|
+
std::remove((path + "-wal").c_str());
|
|
20
|
+
std::remove((path + "-shm").c_str());
|
|
21
|
+
hulldb::set_db_path(path);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// migrate-style schema via batch (atomic)
|
|
25
|
+
hulldb::batch(json::array({
|
|
26
|
+
{{"sql", "CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL, pinned INTEGER, score REAL)"}},
|
|
27
|
+
{{"sql", "PRAGMA user_version = 1"}},
|
|
28
|
+
}));
|
|
29
|
+
json uv = hulldb::get("PRAGMA user_version", json::array());
|
|
30
|
+
check(uv["row"]["user_version"] == 1, "migration set user_version");
|
|
31
|
+
|
|
32
|
+
// parameterized insert with mixed types
|
|
33
|
+
json e1 = hulldb::exec("INSERT INTO notes (body, pinned, score) VALUES (?, ?, ?)",
|
|
34
|
+
json::array({"hello", true, 1.5}));
|
|
35
|
+
check(e1["ok"] == true && e1["changes"] == 1, "insert changes=1");
|
|
36
|
+
check(e1["lastInsertRowid"] == 1, "insert rowid=1");
|
|
37
|
+
|
|
38
|
+
// injection attempt is treated as DATA, not SQL (parameter binding)
|
|
39
|
+
hulldb::exec("INSERT INTO notes (body, pinned, score) VALUES (?, ?, ?)",
|
|
40
|
+
json::array({"x'); DROP TABLE notes;--", false, 2.0}));
|
|
41
|
+
json cnt = hulldb::get("SELECT COUNT(*) AS n FROM notes", json::array());
|
|
42
|
+
check(cnt["row"]["n"] == 2, "injection string stored as data (table intact, 2 rows)");
|
|
43
|
+
|
|
44
|
+
// query returns typed rows
|
|
45
|
+
json rows = hulldb::query("SELECT id, body, pinned, score FROM notes ORDER BY id", json::array());
|
|
46
|
+
check(rows["rows"].size() == 2, "query returned 2 rows");
|
|
47
|
+
check(rows["rows"][0]["body"] == "hello", "text column maps to string");
|
|
48
|
+
check(rows["rows"][0]["pinned"] == 1, "boolean stored as int 1");
|
|
49
|
+
check(rows["rows"][0]["score"] == 1.5, "real column maps to float");
|
|
50
|
+
|
|
51
|
+
// get with a bound param
|
|
52
|
+
json one = hulldb::get("SELECT body FROM notes WHERE id = ?", json::array({2}));
|
|
53
|
+
check(one["row"]["body"] == "x'); DROP TABLE notes;--", "param select returns exact stored data");
|
|
54
|
+
|
|
55
|
+
// transaction rollback on error (second statement fails -> first reverts)
|
|
56
|
+
bool threw = false;
|
|
57
|
+
try {
|
|
58
|
+
hulldb::batch(json::array({
|
|
59
|
+
{{"sql", "INSERT INTO notes (body) VALUES (?)"}, {"params", json::array({"temp"})}},
|
|
60
|
+
{{"sql", "INSERT INTO notes (id, body) VALUES (1, 'dup')"}}, // PK conflict -> error
|
|
61
|
+
}));
|
|
62
|
+
} catch (const std::exception&) { threw = true; }
|
|
63
|
+
check(threw, "bad batch threw");
|
|
64
|
+
json cnt2 = hulldb::get("SELECT COUNT(*) AS n FROM notes", json::array());
|
|
65
|
+
check(cnt2["row"]["n"] == 2, "rollback reverted the partial batch (still 2 rows)");
|
|
66
|
+
|
|
67
|
+
// null handling
|
|
68
|
+
hulldb::exec("INSERT INTO notes (body, pinned, score) VALUES (?, ?, ?)",
|
|
69
|
+
json::array({"n", nullptr, nullptr}));
|
|
70
|
+
json nrow = hulldb::get("SELECT pinned, score FROM notes WHERE body = ?", json::array({"n"}));
|
|
71
|
+
check(nrow["row"]["pinned"].is_null() && nrow["row"]["score"].is_null(), "NULL columns map to null");
|
|
72
|
+
} catch (const std::exception& e) {
|
|
73
|
+
std::printf("FAIL exception: %s\n", e.what());
|
|
74
|
+
failures++;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
std::printf("\n%s (%d failure%s)\n", failures ? "FAILED" : "ALL PASSED",
|
|
78
|
+
failures, failures == 1 ? "" : "s");
|
|
79
|
+
return failures ? 1 : 0;
|
|
80
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Standalone test for the secure layer (default = NullCipher) + file store. Webview-
|
|
2
|
+
// free; no external libs. Build/run (default build):
|
|
3
|
+
// g++ -std=c++17 -I../src secure_files_test.cpp -o t && ./t
|
|
4
|
+
#include <cstdio>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include "secure.hpp"
|
|
8
|
+
#include "file_store.hpp"
|
|
9
|
+
|
|
10
|
+
static int failures = 0;
|
|
11
|
+
static void check(bool cond, const char* what) {
|
|
12
|
+
std::printf("%s %s\n", cond ? "ok " : "FAIL", what);
|
|
13
|
+
if (!cond) failures++;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
int main() {
|
|
17
|
+
#if defined(_WIN32)
|
|
18
|
+
_putenv_s("LOCALAPPDATA", (std::string(getenv("TEMP") ? getenv("TEMP") : ".") + "\\hull-sectest").c_str());
|
|
19
|
+
#else
|
|
20
|
+
setenv("XDG_DATA_HOME", "/tmp/hull-sectest", 1);
|
|
21
|
+
#endif
|
|
22
|
+
storage::set_app_name("HullSecTest");
|
|
23
|
+
|
|
24
|
+
// secure layer (default build = NullCipher, but self-describing format)
|
|
25
|
+
check(secure::active() == false, "default build reports secure=false");
|
|
26
|
+
{
|
|
27
|
+
std::string blob = secure::encrypt("hello");
|
|
28
|
+
check(!blob.empty() && (unsigned char)blob[0] == secure::TAG_PLAIN, "NullCipher tags plaintext (0x00)");
|
|
29
|
+
auto back = secure::decrypt(blob);
|
|
30
|
+
check(back && *back == "hello", "encrypt/decrypt round-trip");
|
|
31
|
+
}
|
|
32
|
+
{
|
|
33
|
+
// A 0x01 (AES) blob can't be read by a default build -> clear error.
|
|
34
|
+
bool threw = false;
|
|
35
|
+
try { secure::decrypt(std::string(1, (char)secure::TAG_AES) + "junk"); }
|
|
36
|
+
catch (const std::exception&) { threw = true; }
|
|
37
|
+
check(threw, "default build refuses encrypted (0x01) data");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// base64 round-trip (incl. a NUL byte)
|
|
41
|
+
{
|
|
42
|
+
std::string raw = std::string("a\0b\xff\x10z", 6);
|
|
43
|
+
check(appfiles::b64decode(appfiles::b64encode(raw)) == raw, "base64 round-trip (binary-safe)");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// file store round-trip + path-traversal protection
|
|
47
|
+
try {
|
|
48
|
+
appfiles::write_file("note.txt", "hello files");
|
|
49
|
+
check(appfiles::read_file("note.txt") == "hello files", "file write/read round-trip");
|
|
50
|
+
|
|
51
|
+
std::string bin = std::string("\x00\x01\x02\xfe\xff", 5);
|
|
52
|
+
appfiles::write_file("blob.bin", bin);
|
|
53
|
+
check(appfiles::read_file("blob.bin") == bin, "binary file round-trip");
|
|
54
|
+
|
|
55
|
+
int rejected = 0;
|
|
56
|
+
for (const char* bad : {"../escape", "a/b", "", ".."}) {
|
|
57
|
+
try { appfiles::safe_name(bad); } catch (const std::exception&) { rejected++; }
|
|
58
|
+
}
|
|
59
|
+
check(rejected == 4, "safe_name rejects traversal / bad names");
|
|
60
|
+
} catch (const std::exception& e) {
|
|
61
|
+
std::printf("FAIL file store exception: %s\n", e.what());
|
|
62
|
+
failures++;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
std::printf("\n%s (%d failure%s)\n", failures ? "FAILED" : "ALL PASSED",
|
|
66
|
+
failures, failures == 1 ? "" : "s");
|
|
67
|
+
return failures ? 1 : 0;
|
|
68
|
+
}
|