240 lines
8.3 KiB
C
240 lines
8.3 KiB
C
#define _GNU_SOURCE
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// NIP-09 EVENT DELETION REQUEST HANDLING
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
#include <cjson/cJSON.h>
|
|
#include "debug.h"
|
|
#include "db_ops.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <stdio.h>
|
|
|
|
|
|
// Forward declaration for database functions
|
|
int store_event(cJSON* event);
|
|
|
|
// Forward declarations for deletion functions
|
|
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids);
|
|
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp);
|
|
|
|
// Handle NIP-09 deletion request event (kind 5)
|
|
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size) {
|
|
if (!event) {
|
|
snprintf(error_message, error_size, "invalid: null deletion request");
|
|
return -1;
|
|
}
|
|
|
|
// Extract event details
|
|
cJSON* kind_obj = cJSON_GetObjectItemCaseSensitive(event, "kind");
|
|
cJSON* pubkey_obj = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
|
cJSON* created_at_obj = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
|
cJSON* tags_obj = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
|
cJSON* content_obj = cJSON_GetObjectItemCaseSensitive(event, "content");
|
|
cJSON* event_id_obj = cJSON_GetObjectItemCaseSensitive(event, "id");
|
|
|
|
if (!kind_obj || !pubkey_obj || !created_at_obj || !tags_obj || !event_id_obj) {
|
|
snprintf(error_message, error_size, "invalid: incomplete deletion request");
|
|
return -1;
|
|
}
|
|
|
|
int kind = (int)cJSON_GetNumberValue(kind_obj);
|
|
if (kind != 5) {
|
|
snprintf(error_message, error_size, "invalid: not a deletion request");
|
|
return -1;
|
|
}
|
|
|
|
const char* requester_pubkey = cJSON_GetStringValue(pubkey_obj);
|
|
// Extract deletion event ID and reason (for potential logging)
|
|
const char* deletion_event_id = cJSON_GetStringValue(event_id_obj);
|
|
const char* reason = content_obj ? cJSON_GetStringValue(content_obj) : "";
|
|
(void)deletion_event_id; // Mark as intentionally unused for now
|
|
(void)reason; // Mark as intentionally unused for now
|
|
long deletion_timestamp = (long)cJSON_GetNumberValue(created_at_obj);
|
|
|
|
if (!cJSON_IsArray(tags_obj)) {
|
|
snprintf(error_message, error_size, "invalid: deletion request tags must be an array");
|
|
return -1;
|
|
}
|
|
|
|
// Collect event IDs and addresses from tags
|
|
cJSON* event_ids = cJSON_CreateArray();
|
|
cJSON* addresses = cJSON_CreateArray();
|
|
cJSON* kinds_to_delete = cJSON_CreateArray();
|
|
|
|
int deletion_targets_found = 0;
|
|
|
|
cJSON* tag = NULL;
|
|
cJSON_ArrayForEach(tag, tags_obj) {
|
|
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
|
|
continue;
|
|
}
|
|
|
|
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
|
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
|
|
|
if (!cJSON_IsString(tag_name) || !cJSON_IsString(tag_value)) {
|
|
continue;
|
|
}
|
|
|
|
const char* name = cJSON_GetStringValue(tag_name);
|
|
const char* value = cJSON_GetStringValue(tag_value);
|
|
|
|
if (strcmp(name, "e") == 0) {
|
|
// Event ID reference
|
|
cJSON_AddItemToArray(event_ids, cJSON_CreateString(value));
|
|
deletion_targets_found++;
|
|
} else if (strcmp(name, "a") == 0) {
|
|
// Addressable event reference (kind:pubkey:d-identifier)
|
|
cJSON_AddItemToArray(addresses, cJSON_CreateString(value));
|
|
deletion_targets_found++;
|
|
} else if (strcmp(name, "k") == 0) {
|
|
// Kind hint - store for validation but not required
|
|
int kind_hint = atoi(value);
|
|
if (kind_hint > 0) {
|
|
cJSON_AddItemToArray(kinds_to_delete, cJSON_CreateNumber(kind_hint));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deletion_targets_found == 0) {
|
|
cJSON_Delete(event_ids);
|
|
cJSON_Delete(addresses);
|
|
cJSON_Delete(kinds_to_delete);
|
|
snprintf(error_message, error_size, "invalid: deletion request must contain 'e' or 'a' tags");
|
|
return -1;
|
|
}
|
|
|
|
int deleted_count = 0;
|
|
|
|
// Process event ID deletions
|
|
if (cJSON_GetArraySize(event_ids) > 0) {
|
|
int result = delete_events_by_id(requester_pubkey, event_ids);
|
|
if (result > 0) {
|
|
deleted_count += result;
|
|
}
|
|
}
|
|
|
|
// Process addressable event deletions
|
|
if (cJSON_GetArraySize(addresses) > 0) {
|
|
int result = delete_events_by_address(requester_pubkey, addresses, deletion_timestamp);
|
|
if (result > 0) {
|
|
deleted_count += result;
|
|
}
|
|
}
|
|
|
|
// Clean up
|
|
cJSON_Delete(event_ids);
|
|
cJSON_Delete(addresses);
|
|
cJSON_Delete(kinds_to_delete);
|
|
|
|
// Store the deletion request itself (it should be kept according to NIP-09)
|
|
if (store_event(event) != 0) {
|
|
DEBUG_WARN("Failed to store deletion request event");
|
|
}
|
|
|
|
error_message[0] = '\0'; // Success - empty error message
|
|
return 0;
|
|
}
|
|
|
|
// Delete events by ID (with pubkey authorization)
|
|
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
|
|
if (!db_is_available() || !requester_pubkey || !event_ids || !cJSON_IsArray(event_ids)) {
|
|
return 0;
|
|
}
|
|
|
|
int deleted_count = 0;
|
|
|
|
cJSON* event_id = NULL;
|
|
cJSON_ArrayForEach(event_id, event_ids) {
|
|
if (!cJSON_IsString(event_id)) {
|
|
continue;
|
|
}
|
|
|
|
const char* id = cJSON_GetStringValue(event_id);
|
|
char event_pubkey[65] = {0};
|
|
int exists = db_get_event_pubkey(id, event_pubkey, sizeof(event_pubkey));
|
|
if (exists <= 0) {
|
|
continue;
|
|
}
|
|
|
|
// Only delete if the requester is the author
|
|
if (strcmp(event_pubkey, requester_pubkey) == 0) {
|
|
int changes = db_delete_event_by_id(id, requester_pubkey);
|
|
if (changes > 0) {
|
|
deleted_count += changes;
|
|
}
|
|
} else {
|
|
char warning_msg[128];
|
|
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id);
|
|
DEBUG_WARN(warning_msg);
|
|
}
|
|
}
|
|
|
|
return deleted_count;
|
|
}
|
|
|
|
// Delete events by addressable reference (kind:pubkey:d-identifier)
|
|
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp) {
|
|
if (!db_is_available() || !requester_pubkey || !addresses || !cJSON_IsArray(addresses)) {
|
|
return 0;
|
|
}
|
|
|
|
int deleted_count = 0;
|
|
|
|
cJSON* address = NULL;
|
|
cJSON_ArrayForEach(address, addresses) {
|
|
if (!cJSON_IsString(address)) {
|
|
continue;
|
|
}
|
|
|
|
const char* addr = cJSON_GetStringValue(address);
|
|
|
|
// Parse address format: kind:pubkey:d-identifier
|
|
char* addr_copy = strdup(addr);
|
|
if (!addr_copy) continue;
|
|
|
|
char* kind_str = strtok(addr_copy, ":");
|
|
char* pubkey_str = strtok(NULL, ":");
|
|
char* d_identifier = strtok(NULL, ":");
|
|
|
|
if (!kind_str || !pubkey_str) {
|
|
free(addr_copy);
|
|
continue;
|
|
}
|
|
|
|
int kind = atoi(kind_str);
|
|
|
|
// Only delete if the requester is the author
|
|
if (strcmp(pubkey_str, requester_pubkey) != 0) {
|
|
free(addr_copy);
|
|
char warning_msg[128];
|
|
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for address: %.32s...", addr);
|
|
DEBUG_WARN(warning_msg);
|
|
continue;
|
|
}
|
|
|
|
int changes = db_delete_events_by_address(requester_pubkey, kind, d_identifier, deletion_timestamp);
|
|
if (changes > 0) {
|
|
deleted_count += changes;
|
|
}
|
|
|
|
free(addr_copy);
|
|
}
|
|
|
|
return deleted_count;
|
|
}
|
|
|
|
// Mark event as deleted (alternative to hard deletion - not used in current implementation)
|
|
int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, const char* reason) {
|
|
(void)event_id; (void)deletion_event_id; (void)reason; // Suppress unused warnings
|
|
|
|
// This function could be used if we wanted to implement soft deletion
|
|
// For now, NIP-09 implementation uses hard deletion as specified
|
|
|
|
return 0;
|
|
}
|