Files
sovereign_browser/src/profile.c
T

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;
}