/* * BUD-08 NIP-94 File Metadata Tags Implementation * Handles NIP-94 metadata generation for blob descriptors */ #include #include #include #include #include #include "ginxsom.h" // Database path #define DB_PATH "db/ginxsom.db" // Check if NIP-94 metadata emission is enabled int nip94_is_enabled(void) { sqlite3* db; sqlite3_stmt* stmt; int rc, enabled = 1; // Default enabled rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); if (rc) { return 1; // Default enabled on DB error } const char* sql = "SELECT value FROM config WHERE key = 'nip94_enabled'"; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc == SQLITE_OK) { rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { const char* value = (const char*)sqlite3_column_text(stmt, 0); enabled = (value && strcmp(value, "true") == 0) ? 1 : 0; } sqlite3_finalize(stmt); } sqlite3_close(db); return enabled; } // Get CDN origin for blob URLs int nip94_get_origin(char* out, size_t out_size) { if (!out || out_size == 0) { return 0; } // Check database config first for custom origin sqlite3* db; sqlite3_stmt* stmt; int rc; rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); if (rc == SQLITE_OK) { const char* sql = "SELECT value FROM config WHERE key = 'cdn_origin'"; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc == SQLITE_OK) { rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { const char* value = (const char*)sqlite3_column_text(stmt, 0); if (value) { strncpy(out, value, out_size - 1); out[out_size - 1] = '\0'; sqlite3_finalize(stmt); sqlite3_close(db); return 1; } } sqlite3_finalize(stmt); } sqlite3_close(db); } // Check if request came over HTTPS (nginx sets HTTPS=on for SSL requests) const char* https_env = getenv("HTTPS"); const char* server_name = getenv("SERVER_NAME"); // Use production domain if SERVER_NAME is set and not localhost if (server_name && strcmp(server_name, "localhost") != 0) { if (https_env && strcmp(https_env, "on") == 0) { snprintf(out, out_size, "https://%s", server_name); } else { snprintf(out, out_size, "http://%s", server_name); } return 1; } // Fallback to localhost for development if (https_env && strcmp(https_env, "on") == 0) { strncpy(out, "https://localhost:9443", out_size - 1); } else { strncpy(out, "http://localhost:9001", out_size - 1); } out[out_size - 1] = '\0'; return 1; } // Centralized MIME type to file extension mapping const char* mime_to_extension(const char* mime_type) { if (!mime_type) { return ".bin"; } if (strstr(mime_type, "image/jpeg")) { return ".jpg"; } else if (strstr(mime_type, "image/webp")) { return ".webp"; } else if (strstr(mime_type, "image/png")) { return ".png"; } else if (strstr(mime_type, "image/gif")) { return ".gif"; } else if (strstr(mime_type, "video/mp4")) { return ".mp4"; } else if (strstr(mime_type, "video/webm")) { return ".webm"; } else if (strstr(mime_type, "audio/mpeg")) { return ".mp3"; } else if (strstr(mime_type, "audio/ogg")) { return ".ogg"; } else if (strstr(mime_type, "text/plain")) { return ".txt"; } else if (strstr(mime_type, "application/pdf")) { return ".pdf"; } else { return ".bin"; } } // Build canonical blob URL from origin + sha256 + extension void nip94_build_blob_url(const char* origin, const char* sha256, const char* mime_type, char* out, size_t out_size) { if (!origin || !sha256 || !out || out_size == 0) { return; } const char* extension = mime_to_extension(mime_type); snprintf(out, out_size, "%s/%s%s", origin, sha256, extension); } // Parse PNG dimensions from IHDR chunk int parse_png_dimensions(const unsigned char* data, size_t size, int* width, int* height) { if (!data || size < 24 || !width || !height) { return 0; } // Verify PNG signature if (memcmp(data, "\x89PNG\r\n\x1a\n", 8) != 0) { return 0; } // IHDR chunk should start at offset 8 // Skip chunk length (4 bytes) and chunk type "IHDR" (4 bytes) // Width is at offset 16 (4 bytes, big-endian) // Height is at offset 20 (4 bytes, big-endian) if (size >= 24) { *width = (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19]; *height = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23]; return 1; } return 0; } // Parse JPEG dimensions from SOF0 or SOF2 markers int parse_jpeg_dimensions(const unsigned char* data, size_t size, int* width, int* height) { if (!data || size < 10 || !width || !height) { return 0; } // Verify JPEG signature if (size < 3 || memcmp(data, "\xff\xd8\xff", 3) != 0) { return 0; } size_t pos = 2; while (pos < size - 1) { // Look for marker if (data[pos] != 0xff) { pos++; continue; } unsigned char marker = data[pos + 1]; pos += 2; // SOF0 (0xc0) or SOF2 (0xc2) if (marker == 0xc0 || marker == 0xc2) { // Skip length (2 bytes) and precision (1 byte) if (pos + 5 < size) { pos += 3; // Height (2 bytes, big-endian) *height = (data[pos] << 8) | data[pos + 1]; pos += 2; // Width (2 bytes, big-endian) *width = (data[pos] << 8) | data[pos + 1]; return 1; } return 0; } else if ((marker >= 0xe0 && marker <= 0xef) || (marker >= 0xc4 && marker <= 0xdf && marker != 0xc8)) { // Skip over other segments if (pos + 1 < size) { size_t seg_len = (data[pos] << 8) | data[pos + 1]; pos += seg_len; } else { break; } } else { pos++; } } return 0; } // Parse WebP dimensions from VP8/VP8L/VP8X chunks int parse_webp_dimensions(const unsigned char* data, size_t size, int* width, int* height) { if (!data || size < 20 || !width || !height) { return 0; } // Verify RIFF/WEBP header if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "WEBP", 4) != 0) { return 0; } // Check chunk type at offset 12 if (memcmp(data + 12, "VP8 ", 4) == 0) { // VP8 lossy format if (size >= 30) { // Skip to frame header (offset 26) *width = ((data[28] | (data[29] << 8)) & 0x3fff); *height = ((data[26] | (data[27] << 8)) & 0x3fff); return 1; } } else if (memcmp(data + 12, "VP8L", 4) == 0) { // VP8L lossless format if (size >= 25) { // Width and height are in bits 0-13 and 14-27 of a 32-bit value at offset 21 uint32_t dim_data = data[21] | (data[22] << 8) | (data[23] << 16) | (data[24] << 24); *width = (dim_data & 0x3fff) + 1; *height = ((dim_data >> 14) & 0x3fff) + 1; return 1; } } else if (memcmp(data + 12, "VP8X", 4) == 0) { // VP8X extended format if (size >= 30) { // Width (3 bytes, little-endian) at offset 24 // Height (3 bytes, little-endian) at offset 27 *width = (data[24] | (data[25] << 8) | (data[26] << 16)) + 1; *height = (data[27] | (data[28] << 8) | (data[29] << 16)) + 1; return 1; } } return 0; } // Get file dimensions based on MIME type int nip94_get_dimensions(const unsigned char* data, size_t size, const char* mime_type, int* width, int* height) { if (!data || !mime_type || !width || !height) { return 0; } if (strstr(mime_type, "image/png")) { return parse_png_dimensions(data, size, width, height); } else if (strstr(mime_type, "image/jpeg")) { return parse_jpeg_dimensions(data, size, width, height); } else if (strstr(mime_type, "image/webp")) { return parse_webp_dimensions(data, size, width, height); } return 0; } // Emit NIP-94 metadata field to stdout void nip94_emit_field(const char* url, const char* mime, const char* sha256, long size, int width, int height) { if (!url || !mime || !sha256) { return; } printf(" \"nip94\": [\n"); printf(" [\"url\", \"%s\"],\n", url); printf(" [\"m\", \"%s\"],\n", mime); printf(" [\"x\", \"%s\"],\n", sha256); printf(" [\"size\", \"%ld\"]", size); // Add dim tag if dimensions are available if (width > 0 && height > 0) { printf(",\n [\"dim\", \"%dx%d\"]", width, height); } printf("\n ]"); }