157 lines
5.7 KiB
C++
157 lines
5.7 KiB
C++
/*
|
|
* 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 <Memory/Heap.hpp>
|
|
|
|
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;
|
|
}
|
|
}
|