/* * DataTransfer.cpp * Multi-format payloads shared by the clipboard and drag and drop * Copyright (c) 2026 Daniel Hammer */ #include "DataTransfer.hpp" #include "UserMemory.hpp" #include namespace DataTransfer { using namespace montauk::abi; static bool MimeEquals(const char* a, const char* b) { int i = 0; for (; a[i] && b[i]; i++) { char ca = (a[i] >= 'A' && a[i] <= 'Z') ? a[i] + 32 : a[i]; char cb = (b[i] >= 'A' && b[i] <= 'Z') ? b[i] + 32 : b[i]; if (ca != cb) return false; } return a[i] == b[i]; } // Length of the "type/subtype" part, before any ";parameters". static int MimeBaseLen(const char* mime) { int n = 0; while (mime[n] && mime[n] != ';') n++; while (n > 0 && mime[n - 1] == ' ') n--; return n; } static bool MimeBaseEquals(const char* a, const char* b) { int la = MimeBaseLen(a); int lb = MimeBaseLen(b); if (la != lb) return false; for (int i = 0; i < la; i++) { char ca = (a[i] >= 'A' && a[i] <= 'Z') ? a[i] + 32 : a[i]; char cb = (b[i] >= 'A' && b[i] <= 'Z') ? b[i] + 32 : b[i]; if (ca != cb) return false; } return true; } bool ValidMime(const char* mime) { if (mime == nullptr) return false; bool slash = false; uint32_t i = 0; for (; mime[i]; i++) { if (i + 1 >= TRANSFER_MIME_MAX) return false; char c = mime[i]; if (c < 0x20 || c > 0x7E) return false; if (c == '/') slash = true; } return i > 0 && slash; } void Free(Transfer& transfer) { for (uint32_t i = 0; i < transfer.count; i++) { if (transfer.formats[i].data != nullptr) Memory::g_heap->Free(transfer.formats[i].data); transfer.formats[i].data = nullptr; transfer.formats[i].len = 0; } transfer.count = 0; } static int AddFormat(Transfer& transfer, const char* mime, const uint8_t* data, uint32_t len) { if (transfer.count >= TRANSFER_MAX_FORMATS) return InvalidArgument; Format& f = transfer.formats[transfer.count]; uint32_t m = 0; for (; mime[m] && m + 1 < TRANSFER_MIME_MAX; m++) f.mime[m] = mime[m]; f.mime[m] = '\0'; // Never hand out a null pointer for an empty format; one byte keeps // "present but empty" distinct from "absent". f.data = (uint8_t*)Memory::g_heap->Request(len > 0 ? len : 1); if (f.data == nullptr) return OutOfMemory; for (uint32_t i = 0; i < len; i++) f.data[i] = data[i]; f.len = len; transfer.count++; return Success; } int BuildFromUser(const TransferItem* items, uint32_t count, Transfer& out) { out.count = 0; if (items == nullptr || count == 0 || count > TRANSFER_MAX_FORMATS) return InvalidArgument; if (Memory::g_heap == nullptr) return OutOfMemory; // Validate everything before allocating anything. uint64_t total = 0; for (uint32_t i = 0; i < count; i++) { if (!UserMemory::String(items[i].mime, TRANSFER_MIME_MAX)) return BadPointer; if (!ValidMime((const char*)items[i].mime)) return InvalidArgument; if (items[i].len > 0 && !UserMemory::Range(items[i].data, items[i].len, false)) return BadPointer; total += items[i].len; for (uint32_t j = 0; j < i; j++) { if (MimeEquals((const char*)items[i].mime, (const char*)items[j].mime)) return InvalidArgument; } } if (total > TRANSFER_MAX_BYTES) return InvalidArgument; for (uint32_t i = 0; i < count; i++) { int rc = AddFormat(out, (const char*)items[i].mime, (const uint8_t*)items[i].data, items[i].len); if (rc != Success) { Free(out); return rc; } } return Success; } int BuildText(const char* text, uint32_t len, Transfer& out) { out.count = 0; if (Memory::g_heap == nullptr) return OutOfMemory; int rc = AddFormat(out, TRANSFER_MIME_TEXT, (const uint8_t*)text, len); if (rc != Success) Free(out); return rc; } const Format* Find(const Transfer& transfer, const char* mime) { if (mime == nullptr) return nullptr; for (uint32_t i = 0; i < transfer.count; i++) { if (MimeEquals(transfer.formats[i].mime, mime)) return &transfer.formats[i]; } bool has_params = false; for (int i = 0; mime[i]; i++) { if (mime[i] == ';') { has_params = true; break; } } if (has_params) return nullptr; for (uint32_t i = 0; i < transfer.count; i++) { if (MimeBaseEquals(transfer.formats[i].mime, mime)) return &transfer.formats[i]; } return nullptr; } uint32_t ListFormats(const Transfer& transfer, TransferFormatInfo* out, uint32_t max) { uint32_t n = 0; for (; n < transfer.count && n < max; n++) { for (uint32_t c = 0; c < TRANSFER_MIME_MAX; c++) out[n].mime[c] = transfer.formats[n].mime[c]; out[n].len = transfer.formats[n].len; out[n]._reserved = 0; } return n; } int CopyOut(const Format& format, void* buffer, uint32_t bufferLen, uint32_t* outLen) { if (outLen != nullptr) *outLen = format.len; if (format.len == 0) return Success; if (buffer == nullptr || bufferLen < format.len) return BufferTooSmall; uint8_t* dst = (uint8_t*)buffer; for (uint32_t i = 0; i < format.len; i++) dst[i] = format.data[i]; return Success; } }