Files
ginxsom/src/bud08.c
2025-11-11 07:08:27 -04:00

293 lines
9.1 KiB
C

/*
* BUD-08 NIP-94 File Metadata Tags Implementation
* Handles NIP-94 metadata generation for blob descriptors
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <stdint.h>
#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 ]");
}