Files
didactyl/src/tools/tool_blossom.c
T

305 lines
12 KiB
C

#define _POSIX_C_SOURCE 200809L
#include "tools_internal.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "cjson/cJSON.h"
#include "../../nostr_core_lib/nostr_core/blossom_client.h"
static char* json_error_local(const char* msg) {
cJSON* root = cJSON_CreateObject();
if (!root) return NULL;
cJSON_AddBoolToObject(root, "success", 0);
cJSON_AddStringToObject(root, "error", msg ? msg : "unknown error");
char* out = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return out;
}
static cJSON* parse_args_local(const char* args_json) {
const char* raw = args_json ? args_json : "{}";
cJSON* args = cJSON_Parse(raw);
if (!args || !cJSON_IsObject(args)) {
cJSON_Delete(args);
return NULL;
}
return args;
}
static int is_safe_relative_path_local(const char* path) {
if (!path || path[0] == '\0') return 0;
if (path[0] == '/') return 0;
if (strstr(path, "..") != NULL) return 0;
if (strchr(path, '\\') != NULL) return 0;
return 1;
}
static int build_tool_path_local(tools_context_t* ctx, const char* rel_path, char* out, size_t out_size) {
if (!ctx || !ctx->cfg || !rel_path || !out || out_size == 0) return -1;
if (!is_safe_relative_path_local(rel_path)) return -1;
const char* cwd = ctx->cfg->tools.shell.working_directory[0] != '\0' ? ctx->cfg->tools.shell.working_directory : ".";
int n = (strcmp(cwd, ".") == 0) ? snprintf(out, out_size, "%s", rel_path)
: snprintf(out, out_size, "%s/%s", cwd, rel_path);
if (n < 0 || (size_t)n >= out_size) return -1;
return 0;
}
static int is_https_server_local(const char* server) {
return server && strncmp(server, "https://", 8) == 0;
}
static int file_exists_local(const char* path) {
if (!path || path[0] == '\0') return 0;
return access(path, F_OK) == 0;
}
static int file_size_local(const char* path, size_t* out_size) {
if (!path || !out_size) return -1;
struct stat st;
if (stat(path, &st) != 0) return -1;
if (st.st_size < 0) return -1;
*out_size = (size_t)st.st_size;
return 0;
}
static void descriptor_to_json(cJSON* obj, const blossom_blob_descriptor_t* d) {
if (!obj || !d) return;
cJSON_AddStringToObject(obj, "sha256", d->sha256);
cJSON_AddStringToObject(obj, "url", d->url);
cJSON_AddNumberToObject(obj, "size", d->size);
cJSON_AddStringToObject(obj, "content_type", d->content_type);
cJSON_AddNumberToObject(obj, "created", d->created);
}
char* execute_blossom_upload(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
cJSON* args = parse_args_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON* server = cJSON_GetObjectItemCaseSensitive(args, "server");
cJSON* file_path = cJSON_GetObjectItemCaseSensitive(args, "file_path");
cJSON* content_type = cJSON_GetObjectItemCaseSensitive(args, "content_type");
if (!server || !cJSON_IsString(server) || !server->valuestring || server->valuestring[0] == '\0' ||
!file_path || !cJSON_IsString(file_path) || !file_path->valuestring || file_path->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error_local("blossom_upload requires server and file_path");
}
if (!is_https_server_local(server->valuestring)) {
cJSON_Delete(args);
return json_error_local("blossom_upload requires an https:// server URL");
}
char full_path[PATH_MAX];
if (build_tool_path_local(ctx, file_path->valuestring, full_path, sizeof(full_path)) != 0) {
cJSON_Delete(args);
return json_error_local("blossom_upload file_path is not allowed");
}
size_t input_size = 0;
if (file_size_local(full_path, &input_size) != 0) {
cJSON_Delete(args);
return json_error_local("blossom_upload could not stat file_path");
}
int upload_max = ctx->cfg->tools.blossom_max_upload_bytes > 0 ? ctx->cfg->tools.blossom_max_upload_bytes : (16 * 1024 * 1024);
if (input_size > (size_t)upload_max) {
cJSON_Delete(args);
return json_error_local("blossom_upload file exceeds configured max upload size");
}
blossom_blob_descriptor_t d;
int rc = blossom_upload_file(server->valuestring,
full_path,
(content_type && cJSON_IsString(content_type) && content_type->valuestring) ? content_type->valuestring : NULL,
ctx->cfg->keys.private_key,
30,
&d);
cJSON_Delete(args);
if (rc != NOSTR_SUCCESS) return json_error_local("blossom_upload failed");
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "server", server->valuestring);
descriptor_to_json(out, &d);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
char* execute_blossom_download(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
cJSON* args = parse_args_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON* server = cJSON_GetObjectItemCaseSensitive(args, "server");
cJSON* sha = cJSON_GetObjectItemCaseSensitive(args, "sha256");
cJSON* output_path = cJSON_GetObjectItemCaseSensitive(args, "output_path");
if (!server || !cJSON_IsString(server) || !server->valuestring || server->valuestring[0] == '\0' ||
!sha || !cJSON_IsString(sha) || !sha->valuestring ||
!output_path || !cJSON_IsString(output_path) || !output_path->valuestring) {
cJSON_Delete(args);
return json_error_local("blossom_download requires server, sha256, and output_path");
}
if (!is_https_server_local(server->valuestring)) {
cJSON_Delete(args);
return json_error_local("blossom_download requires an https:// server URL");
}
char full_path[PATH_MAX];
if (build_tool_path_local(ctx, output_path->valuestring, full_path, sizeof(full_path)) != 0) {
cJSON_Delete(args);
return json_error_local("blossom_download output_path is not allowed");
}
cJSON* overwrite = cJSON_GetObjectItemCaseSensitive(args, "overwrite");
int allow_overwrite = (overwrite && cJSON_IsBool(overwrite) && cJSON_IsTrue(overwrite)) ? 1 : 0;
if (!allow_overwrite && file_exists_local(full_path)) {
cJSON_Delete(args);
return json_error_local("blossom_download output_path exists (set overwrite=true to replace)");
}
int download_max = ctx->cfg->tools.blossom_max_download_bytes > 0 ? ctx->cfg->tools.blossom_max_download_bytes : (16 * 1024 * 1024);
blossom_blob_descriptor_t d;
int rc = blossom_download_to_file(server->valuestring, sha->valuestring, full_path, 30, (size_t)download_max, &d);
cJSON_Delete(args);
if (rc != NOSTR_SUCCESS) return json_error_local("blossom_download failed");
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "output_path", full_path);
descriptor_to_json(out, &d);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
char* execute_blossom_head(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
cJSON* args = parse_args_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON* server = cJSON_GetObjectItemCaseSensitive(args, "server");
cJSON* sha = cJSON_GetObjectItemCaseSensitive(args, "sha256");
if (!server || !cJSON_IsString(server) || !server->valuestring || !sha || !cJSON_IsString(sha) || !sha->valuestring) {
cJSON_Delete(args);
return json_error_local("blossom_head requires server and sha256");
}
if (!is_https_server_local(server->valuestring)) {
cJSON_Delete(args);
return json_error_local("blossom_head requires an https:// server URL");
}
blossom_blob_descriptor_t d;
int rc = blossom_head(server->valuestring, sha->valuestring, 15, &d);
cJSON_Delete(args);
if (rc != NOSTR_SUCCESS) return json_error_local("blossom_head failed");
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddBoolToObject(out, "exists", 1);
descriptor_to_json(out, &d);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
char* execute_blossom_delete(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
cJSON* args = parse_args_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON* server = cJSON_GetObjectItemCaseSensitive(args, "server");
cJSON* sha = cJSON_GetObjectItemCaseSensitive(args, "sha256");
if (!server || !cJSON_IsString(server) || !server->valuestring || !sha || !cJSON_IsString(sha) || !sha->valuestring) {
cJSON_Delete(args);
return json_error_local("blossom_delete requires server and sha256");
}
if (!is_https_server_local(server->valuestring)) {
cJSON_Delete(args);
return json_error_local("blossom_delete requires an https:// server URL");
}
int rc = blossom_delete(server->valuestring, sha->valuestring, ctx->cfg->keys.private_key, 15);
cJSON_Delete(args);
if (rc != NOSTR_SUCCESS) return json_error_local("blossom_delete failed");
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddBoolToObject(out, "deleted", 1);
cJSON_AddStringToObject(out, "sha256", sha->valuestring);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
char* execute_blossom_list(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
cJSON* args = parse_args_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON* server = cJSON_GetObjectItemCaseSensitive(args, "server");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
if (!server || !cJSON_IsString(server) || !server->valuestring) {
cJSON_Delete(args);
return json_error_local("blossom_list requires server");
}
if (!is_https_server_local(server->valuestring)) {
cJSON_Delete(args);
return json_error_local("blossom_list requires an https:// server URL");
}
const char* pk = (pubkey && cJSON_IsString(pubkey) && pubkey->valuestring && pubkey->valuestring[0] != '\0')
? pubkey->valuestring
: ctx->cfg->keys.public_key_hex;
blossom_blob_descriptor_t* items = NULL;
int count = 0;
int rc = blossom_list(server->valuestring, pk, 20, &items, &count);
cJSON_Delete(args);
if (rc != NOSTR_SUCCESS) return json_error_local("blossom_list failed");
cJSON* out = cJSON_CreateObject();
cJSON* arr = cJSON_CreateArray();
if (!out || !arr) {
free(items);
cJSON_Delete(out);
cJSON_Delete(arr);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "pubkey", pk);
cJSON_AddNumberToObject(out, "count", count);
for (int i = 0; i < count; i++) {
cJSON* it = cJSON_CreateObject();
if (!it) continue;
descriptor_to_json(it, &items[i]);
cJSON_AddItemToArray(arr, it);
}
free(items);
cJSON_AddItemToObject(out, "blobs", arr);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}