240 lines
6.2 KiB
C
240 lines
6.2 KiB
C
/*
|
|
* profile.c — per-user profile directory management for sovereign_browser
|
|
*
|
|
* Each Nostr identity (identified by its 64-char hex pubkey) gets its own
|
|
* profile directory under ~/.sovereign_browser/profiles/<pubkey_hex>/.
|
|
*/
|
|
|
|
#include "profile.h"
|
|
|
|
#include <glib.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "../nostr_core_lib/cjson/cJSON.h"
|
|
|
|
/* ── Internal helpers ──────────────────────────────────────────────── */
|
|
|
|
/* Get ~/.sovereign_browser/ (created if missing). Returns 0 on success. */
|
|
static int sb_home_dir(char *out, size_t out_sz) {
|
|
const char *home = getenv("HOME");
|
|
if (home == NULL || home[0] == '\0') {
|
|
return -1;
|
|
}
|
|
int n = snprintf(out, out_sz, "%s/.sovereign_browser", home);
|
|
if (n < 0 || (size_t)n >= out_sz) {
|
|
return -1;
|
|
}
|
|
if (mkdir(out, 0700) != 0 && errno != EEXIST) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Max path length we use for home-derived paths. The home directory
|
|
* can be long, and we append "/.sovereign_browser/profiles/<64-hex>/".
|
|
* 600 bytes is generous enough for any realistic HOME path. */
|
|
#define SB_HOME_BUF_SZ 600
|
|
|
|
/* mkdir -p style: create a directory and all parent directories. */
|
|
static int mkdir_p(const char *path, mode_t mode) {
|
|
char tmp[512];
|
|
int n = snprintf(tmp, sizeof(tmp), "%s", path);
|
|
if (n < 0 || (size_t)n >= sizeof(tmp)) {
|
|
return -1;
|
|
}
|
|
|
|
/* Remove trailing slash if present. */
|
|
size_t len = strlen(tmp);
|
|
if (len > 0 && tmp[len - 1] == '/') {
|
|
tmp[len - 1] = '\0';
|
|
}
|
|
|
|
/* Walk the path and create each component. */
|
|
for (char *p = tmp + 1; *p; p++) {
|
|
if (*p == '/') {
|
|
*p = '\0';
|
|
if (mkdir(tmp, mode) != 0 && errno != EEXIST) {
|
|
return -1;
|
|
}
|
|
*p = '/';
|
|
}
|
|
}
|
|
if (mkdir(tmp, mode) != 0 && errno != EEXIST) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ── Public API ────────────────────────────────────────────────────── */
|
|
|
|
void profile_get_dir(const char *pubkey_hex, char *out, size_t out_sz) {
|
|
if (pubkey_hex == NULL || out == NULL || out_sz == 0) {
|
|
if (out && out_sz > 0) out[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
char home[600];
|
|
if (sb_home_dir(home, sizeof(home)) != 0) {
|
|
if (out_sz > 0) out[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
int n = snprintf(out, out_sz, "%s/profiles/%s/", home, pubkey_hex);
|
|
if (n < 0 || (size_t)n >= out_sz) {
|
|
if (out_sz > 0) out[0] = '\0';
|
|
}
|
|
}
|
|
|
|
int profile_ensure_dir(const char *pubkey_hex) {
|
|
if (pubkey_hex == NULL || pubkey_hex[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
char dir[SB_HOME_BUF_SZ];
|
|
profile_get_dir(pubkey_hex, dir, sizeof(dir));
|
|
if (dir[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
return mkdir_p(dir, 0700);
|
|
}
|
|
|
|
void profile_get_db_path(const char *pubkey_hex, char *out, size_t out_sz) {
|
|
if (pubkey_hex == NULL || out == NULL || out_sz == 0) {
|
|
if (out && out_sz > 0) out[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
char dir[SB_HOME_BUF_SZ];
|
|
profile_get_dir(pubkey_hex, dir, sizeof(dir));
|
|
if (dir[0] == '\0') {
|
|
if (out_sz > 0) out[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
int n = snprintf(out, out_sz, "%sbrowser.db", dir);
|
|
if (n < 0 || (size_t)n >= out_sz) {
|
|
if (out_sz > 0) out[0] = '\0';
|
|
}
|
|
}
|
|
|
|
void profile_get_identity_path(const char *pubkey_hex, char *out, size_t out_sz) {
|
|
if (pubkey_hex == NULL || out == NULL || out_sz == 0) {
|
|
if (out && out_sz > 0) out[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
char dir[SB_HOME_BUF_SZ];
|
|
profile_get_dir(pubkey_hex, dir, sizeof(dir));
|
|
if (dir[0] == '\0') {
|
|
if (out_sz > 0) out[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
int n = snprintf(out, out_sz, "%sidentity.json", dir);
|
|
if (n < 0 || (size_t)n >= out_sz) {
|
|
if (out_sz > 0) out[0] = '\0';
|
|
}
|
|
}
|
|
|
|
void profile_get_global_identity_path(char *out, size_t out_sz) {
|
|
if (out == NULL || out_sz == 0) {
|
|
return;
|
|
}
|
|
|
|
char home[SB_HOME_BUF_SZ];
|
|
if (sb_home_dir(home, sizeof(home)) != 0) {
|
|
out[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
int n = snprintf(out, out_sz, "%s/identity.json", home);
|
|
if (n < 0 || (size_t)n >= out_sz) {
|
|
if (out_sz > 0) out[0] = '\0';
|
|
}
|
|
}
|
|
|
|
/* ── Last-used pubkey persistence (global identity.json) ───────────── */
|
|
|
|
int profile_save_last_pubkey(const char *pubkey_hex) {
|
|
char path[SB_HOME_BUF_SZ];
|
|
profile_get_global_identity_path(path, sizeof(path));
|
|
if (path[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
cJSON *root = cJSON_CreateObject();
|
|
if (pubkey_hex && pubkey_hex[0]) {
|
|
cJSON_AddStringToObject(root, "pubkey_hex", pubkey_hex);
|
|
} else {
|
|
cJSON_AddNullToObject(root, "pubkey_hex");
|
|
}
|
|
|
|
char *json = cJSON_PrintUnformatted(root);
|
|
cJSON_Delete(root);
|
|
if (json == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
FILE *f = fopen(path, "w");
|
|
if (f == NULL) {
|
|
free(json);
|
|
return -1;
|
|
}
|
|
fputs(json, f);
|
|
fputc('\n', f);
|
|
fclose(f);
|
|
free(json);
|
|
|
|
g_print("[profile] Saved last-used pubkey: %s\n",
|
|
(pubkey_hex && pubkey_hex[0]) ? pubkey_hex : "(cleared)");
|
|
return 0;
|
|
}
|
|
|
|
int profile_load_last_pubkey(char *out, size_t out_sz) {
|
|
if (out == NULL || out_sz < 65) {
|
|
return -1;
|
|
}
|
|
|
|
char path[SB_HOME_BUF_SZ];
|
|
profile_get_global_identity_path(path, sizeof(path));
|
|
if (path[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
FILE *f = fopen(path, "r");
|
|
if (f == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
/* Read the file (small JSON, a few hundred bytes at most). */
|
|
char buf[1024];
|
|
size_t n = fread(buf, 1, sizeof(buf) - 1, f);
|
|
fclose(f);
|
|
buf[n] = '\0';
|
|
|
|
cJSON *root = cJSON_Parse(buf);
|
|
if (root == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
cJSON *pk = cJSON_GetObjectItemCaseSensitive(root, "pubkey_hex");
|
|
const char *hex = cJSON_IsString(pk) ? pk->valuestring : NULL;
|
|
|
|
int rc = -1;
|
|
if (hex && strlen(hex) == 64) {
|
|
snprintf(out, out_sz, "%s", hex);
|
|
rc = 0;
|
|
}
|
|
|
|
cJSON_Delete(root);
|
|
return rc;
|
|
}
|