Working mode: --mode coding|general (CST_MODE, settings.json, /mode, WebUI run settings). general swaps the default framing for a general assistant one -- workflow, safety notes, tool wording (run_shell, the subagents) -- and /status shows it; minimal (the paper profile) stays byte-frozen. The prompt state carries the mode so a switch leaves a system note in history. Clipboard: clipboard_read / clipboard_write tools (16 MiB cap read, default 64 KiB window; writing is a WRITE permission with a preview) on pbcopy / wl-copy / xclip / pbpaste / wl-paste / xclip and the Win32 clipboard. In the TUI, /attach paste falls back to clipboard text when there is no image on it. PDF: a dependency-free text extractor (src/pdf_text.c): inflate, Flate/ASCIIHex/ASCII85, PNG/TIFF predictors, layout-independent object scan, object streams, page tree walk with inherited resources, content lexer (Tj/TJ/'/" and the Td/TD/T*/Tm spacing heuristics), per-font ToUnicode CMaps and inline images skipped. Encrypted files and pages with no text layer are refused with the reason. Attachments: /attach (a path or files dropped on the terminal) now take images, PDFs and text. A PDF is stored as its text layer with the mime still saying application/pdf and its page count; anything else has to be UTF-8 text. Documents travel inline as text parts, so CST_NO_IMAGES and text-only models no longer drop them, while images keep their old path. Tests: test_pdf_text (plain, bare and zlib-framed Flate, inline image, CID + ToUnicode, object stream, error paths), documents in test_attach, inlined documents in test_llm_body, mode in test_config.
652 lines
17 KiB
C
652 lines
17 KiB
C
#include "cst_clipboard.h"
|
|
#include "cst_attach.h"
|
|
#include "os_compat.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
static void set_err(char *err, size_t err_sz, const char *msg) {
|
|
if (err && err_sz)
|
|
snprintf(err, err_sz, "%s", msg);
|
|
}
|
|
|
|
static void put_le32(unsigned char *p, unsigned long v) {
|
|
p[0] = (unsigned char)(v & 0xff);
|
|
p[1] = (unsigned char)((v >> 8) & 0xff);
|
|
p[2] = (unsigned char)((v >> 16) & 0xff);
|
|
p[3] = (unsigned char)((v >> 24) & 0xff);
|
|
}
|
|
|
|
static void put_be32(unsigned char *p, unsigned long v) {
|
|
p[0] = (unsigned char)((v >> 24) & 0xff);
|
|
p[1] = (unsigned char)((v >> 16) & 0xff);
|
|
p[2] = (unsigned char)((v >> 8) & 0xff);
|
|
p[3] = (unsigned char)(v & 0xff);
|
|
}
|
|
|
|
static unsigned long get_le32(const unsigned char *p) {
|
|
return (unsigned long)p[0] | (unsigned long)p[1] << 8 |
|
|
(unsigned long)p[2] << 16 | (unsigned long)p[3] << 24;
|
|
}
|
|
|
|
static unsigned long crc32_update(unsigned long crc, const unsigned char *p,
|
|
size_t n) {
|
|
static unsigned long table[256];
|
|
static int ready;
|
|
if (!ready) {
|
|
for (unsigned long i = 0; i < 256; i++) {
|
|
unsigned long c = i;
|
|
for (int k = 0; k < 8; k++)
|
|
c = c & 1 ? 0xedb88320UL ^ (c >> 1) : c >> 1;
|
|
table[i] = c;
|
|
}
|
|
ready = 1;
|
|
}
|
|
crc ^= 0xffffffffUL;
|
|
for (size_t i = 0; i < n; i++)
|
|
crc = table[(crc ^ p[i]) & 0xff] ^ (crc >> 8);
|
|
return crc ^ 0xffffffffUL;
|
|
}
|
|
|
|
static unsigned char *png_chunk(unsigned char *o, const char *type,
|
|
const unsigned char *data, size_t n) {
|
|
put_be32(o, (unsigned long)n);
|
|
memcpy(o + 4, type, 4);
|
|
if (n)
|
|
memcpy(o + 8, data, n);
|
|
put_be32(o + 8 + n, crc32_update(0, o + 4, n + 4));
|
|
return o + 12 + n;
|
|
}
|
|
|
|
/* RGB rows (filter byte 0 each) in stored deflate blocks: no compressor in
|
|
* the tree, and clipboard screenshots stay well under the attach cap. */
|
|
static char *png_from_raw(const unsigned char *raw, size_t raw_n, long w,
|
|
long h, size_t *n) {
|
|
size_t blocks = raw_n / 65535 + 1;
|
|
size_t z_n = 2 + raw_n + blocks * 5 + 4;
|
|
size_t total = 8 + (12 + 13) + (12 + z_n) + 12;
|
|
unsigned char *out = malloc(total);
|
|
unsigned char *z = malloc(z_n);
|
|
if (!out || !z) {
|
|
free(out);
|
|
free(z);
|
|
return NULL;
|
|
}
|
|
unsigned char *q = z;
|
|
*q++ = 0x78;
|
|
*q++ = 0x01;
|
|
unsigned long a = 1, b = 0;
|
|
size_t left = raw_n, pos = 0;
|
|
do {
|
|
size_t len = left > 65535 ? 65535 : left;
|
|
*q++ = left == len ? 1 : 0;
|
|
q[0] = (unsigned char)(len & 0xff);
|
|
q[1] = (unsigned char)(len >> 8);
|
|
q[2] = (unsigned char)(~len & 0xff);
|
|
q[3] = (unsigned char)((~len >> 8) & 0xff);
|
|
q += 4;
|
|
memcpy(q, raw + pos, len);
|
|
for (size_t i = 0; i < len; i++) {
|
|
a = (a + raw[pos + i]) % 65521;
|
|
b = (b + a) % 65521;
|
|
}
|
|
q += len;
|
|
pos += len;
|
|
left -= len;
|
|
} while (left);
|
|
put_be32(q, b << 16 | a);
|
|
q += 4;
|
|
|
|
unsigned char ihdr[13];
|
|
put_be32(ihdr, (unsigned long)w);
|
|
put_be32(ihdr + 4, (unsigned long)h);
|
|
ihdr[8] = 8; /* bit depth */
|
|
ihdr[9] = 2; /* RGB */
|
|
ihdr[10] = ihdr[11] = ihdr[12] = 0;
|
|
unsigned char *o = out;
|
|
memcpy(o, "\x89PNG\r\n\x1a\n", 8);
|
|
o = png_chunk(o + 8, "IHDR", ihdr, sizeof(ihdr));
|
|
o = png_chunk(o, "IDAT", z, (size_t)(q - z));
|
|
o = png_chunk(o, "IEND", NULL, 0);
|
|
free(z);
|
|
*n = (size_t)(o - out);
|
|
return (char *)out;
|
|
}
|
|
|
|
/* CF_DIB is a BMP without its 14-byte file header; the pixel offset depends
|
|
* on the header size, BI_BITFIELDS masks and the color table. */
|
|
char *cst_dib_to_image(const void *dib_v, size_t sz, size_t *n) {
|
|
const unsigned char *dib = dib_v;
|
|
if (!dib || sz < 40)
|
|
return NULL;
|
|
unsigned long hsize = get_le32(dib);
|
|
long w = (long)(int)get_le32(dib + 4);
|
|
long hh = (long)(int)get_le32(dib + 8);
|
|
unsigned bits = (unsigned)(dib[14] | dib[15] << 8);
|
|
unsigned long comp = get_le32(dib + 16);
|
|
unsigned long used = get_le32(dib + 32);
|
|
if (hsize < 40 || hsize > sz)
|
|
return NULL;
|
|
size_t off = hsize;
|
|
if (hsize == 40 && comp == 3 /* BI_BITFIELDS */)
|
|
off += 12;
|
|
size_t colors = used;
|
|
if (!colors && bits <= 8)
|
|
colors = (size_t)1 << bits;
|
|
if (colors > 256)
|
|
return NULL;
|
|
off += colors * 4;
|
|
if (off > sz)
|
|
return NULL;
|
|
|
|
long h = hh < 0 ? -hh : hh;
|
|
if ((bits == 24 || bits == 32) && (comp == 0 || comp == 3) && w > 0 &&
|
|
h > 0 && w <= 32768 && h <= 32768) {
|
|
size_t stride = ((size_t)w * bits / 8 + 3) & ~(size_t)3;
|
|
size_t raw_n = (size_t)h * (1 + (size_t)w * 3);
|
|
if (stride * (size_t)h <= sz - off && raw_n < CST_ATTACH_MAX_BYTES) {
|
|
unsigned char *raw = malloc(raw_n);
|
|
if (!raw)
|
|
return NULL;
|
|
size_t bpp = bits / 8;
|
|
for (long y = 0; y < h; y++) {
|
|
long src_y = hh > 0 ? h - 1 - y : y; /* positive height = bottom-up */
|
|
const unsigned char *s = dib + off + (size_t)src_y * stride;
|
|
unsigned char *d = raw + (size_t)y * (1 + (size_t)w * 3);
|
|
*d++ = 0;
|
|
for (long x = 0; x < w; x++, s += bpp) {
|
|
*d++ = s[2];
|
|
*d++ = s[1];
|
|
*d++ = s[0];
|
|
}
|
|
}
|
|
char *png = png_from_raw(raw, raw_n, w, h, n);
|
|
free(raw);
|
|
return png;
|
|
}
|
|
}
|
|
|
|
char *out = malloc(14 + sz);
|
|
if (!out)
|
|
return NULL;
|
|
unsigned char *hd = (unsigned char *)out;
|
|
hd[0] = 'B';
|
|
hd[1] = 'M';
|
|
put_le32(hd + 2, (unsigned long)(14 + sz));
|
|
put_le32(hd + 6, 0);
|
|
put_le32(hd + 10, (unsigned long)(14 + off));
|
|
memcpy(out + 14, dib, sz);
|
|
*n = 14 + sz;
|
|
return out;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
int cst_clipboard_image(char **data, size_t *n, char *err, size_t err_sz) {
|
|
*data = NULL;
|
|
*n = 0;
|
|
UINT png = RegisterClipboardFormatA("PNG");
|
|
if (!OpenClipboard(NULL)) {
|
|
set_err(err, err_sz, "could not open the clipboard");
|
|
return -1;
|
|
}
|
|
HANDLE h = NULL;
|
|
int is_png = 0;
|
|
if (png && IsClipboardFormatAvailable(png)) {
|
|
h = GetClipboardData(png);
|
|
is_png = 1;
|
|
} else if (IsClipboardFormatAvailable(CF_DIB)) {
|
|
h = GetClipboardData(CF_DIB);
|
|
}
|
|
const unsigned char *p = h ? GlobalLock(h) : NULL;
|
|
if (!p) {
|
|
CloseClipboard();
|
|
set_err(err, err_sz, "no image on the clipboard");
|
|
return -1;
|
|
}
|
|
size_t sz = (size_t)GlobalSize(h);
|
|
char *out = NULL;
|
|
if (sz > CST_ATTACH_MAX_BYTES) {
|
|
set_err(err, err_sz, "clipboard image is too large");
|
|
} else if (is_png) {
|
|
out = malloc(sz ? sz : 1);
|
|
if (out) {
|
|
memcpy(out, p, sz);
|
|
*n = sz;
|
|
}
|
|
} else {
|
|
out = cst_dib_to_image(p, sz, n);
|
|
if (!out)
|
|
set_err(err, err_sz, "clipboard bitmap is malformed");
|
|
}
|
|
GlobalUnlock(h);
|
|
CloseClipboard();
|
|
if (!out)
|
|
return -1;
|
|
*data = out;
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
/* Runs cmd (stderr already redirected by the caller); 127 = not installed. */
|
|
static int run_bytes(const char *cmd, char **data, size_t *n) {
|
|
os_run_result_t r;
|
|
memset(&r, 0, sizeof(r));
|
|
if (os_run_shell_c(NULL, cmd, CST_ATTACH_MAX_BYTES + 1, &r) != 0) {
|
|
os_run_result_free(&r);
|
|
return -1;
|
|
}
|
|
if (r.exit_code != 0 || r.out_len == 0) {
|
|
int code = r.exit_code == 127 ? 127 : -1;
|
|
os_run_result_free(&r);
|
|
return code;
|
|
}
|
|
*data = r.out;
|
|
*n = r.out_len;
|
|
return 0;
|
|
}
|
|
|
|
static int type_ok(const char *s, size_t n) {
|
|
if (n < 7 || n > 63 || strncmp(s, "image/", 6) != 0)
|
|
return 0;
|
|
for (size_t i = 0; i < n; i++)
|
|
if (!isalnum((unsigned char)s[i]) && !strchr("/.+-", s[i]))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/* Best image MIME type from a newline-separated list: PNG first. */
|
|
static int pick_type(const char *list, size_t len, char *out, size_t out_sz) {
|
|
out[0] = 0;
|
|
size_t i = 0;
|
|
while (i < len) {
|
|
size_t j = i;
|
|
while (j < len && list[j] != '\n' && list[j] != '\r')
|
|
j++;
|
|
if (type_ok(list + i, j - i)) {
|
|
int is_png = j - i == 9 && strncmp(list + i, "image/png", 9) == 0;
|
|
if (is_png || !out[0])
|
|
snprintf(out, out_sz, "%.*s", (int)(j - i), list + i);
|
|
if (is_png)
|
|
break;
|
|
}
|
|
i = j + 1;
|
|
}
|
|
return out[0] ? 0 : -1;
|
|
}
|
|
|
|
static int via_lister(const char *list_cmd, const char *get_fmt,
|
|
const char *tool, char **data, size_t *n, char *err,
|
|
size_t err_sz) {
|
|
char *types = NULL;
|
|
size_t tn = 0;
|
|
int rc = run_bytes(list_cmd, &types, &tn);
|
|
if (rc == 127) {
|
|
char msg[96];
|
|
snprintf(msg, sizeof(msg), "reading the clipboard needs %s", tool);
|
|
set_err(err, err_sz, msg);
|
|
return -1;
|
|
}
|
|
char type[64];
|
|
int ok = rc == 0 && pick_type(types, tn, type, sizeof(type)) == 0;
|
|
free(types);
|
|
if (!ok) {
|
|
set_err(err, err_sz, "no image on the clipboard");
|
|
return -1;
|
|
}
|
|
char cmd[256];
|
|
snprintf(cmd, sizeof(cmd), get_fmt, type);
|
|
if (run_bytes(cmd, data, n) != 0) {
|
|
set_err(err, err_sz, "could not read the clipboard image");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
static int mac_image(char **data, size_t *n, char *err, size_t err_sz) {
|
|
int rc = run_bytes("pngpaste - 2>/dev/null", data, n);
|
|
if (rc == 0)
|
|
return 0;
|
|
char path[1024];
|
|
int fd = os_mkstemp(path, sizeof(path), "cst-clip");
|
|
if (fd < 0) {
|
|
set_err(err, err_sz, "could not create a temp file");
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
char cmd[1600];
|
|
snprintf(cmd, sizeof(cmd),
|
|
"osascript -e 'set f to open for access (POSIX file \"%s\") with "
|
|
"write permission' -e 'set eof f to 0' -e 'write (the clipboard as "
|
|
"\xc2\xabclass PNGf\xc2\xbb) to f' -e 'close access f' "
|
|
">/dev/null 2>&1",
|
|
path);
|
|
os_run_result_t r;
|
|
memset(&r, 0, sizeof(r));
|
|
int ran = os_run_shell_c(NULL, cmd, 4096, &r);
|
|
int code = r.exit_code;
|
|
os_run_result_free(&r);
|
|
char *buf = NULL;
|
|
size_t len = 0;
|
|
if (ran == 0 && code == 0) {
|
|
FILE *f = fopen(path, "rb");
|
|
if (f) {
|
|
char chunk[65536];
|
|
size_t got;
|
|
while ((got = fread(chunk, 1, sizeof(chunk), f)) > 0 &&
|
|
len <= CST_ATTACH_MAX_BYTES) {
|
|
char *nb = realloc(buf, len + got);
|
|
if (!nb)
|
|
break;
|
|
buf = nb;
|
|
memcpy(buf + len, chunk, got);
|
|
len += got;
|
|
}
|
|
fclose(f);
|
|
}
|
|
}
|
|
remove(path);
|
|
if (!buf || !len) {
|
|
free(buf);
|
|
set_err(err, err_sz, "no image on the clipboard");
|
|
return -1;
|
|
}
|
|
*data = buf;
|
|
*n = len;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int cst_clipboard_image(char **data, size_t *n, char *err, size_t err_sz) {
|
|
*data = NULL;
|
|
*n = 0;
|
|
#ifdef __APPLE__
|
|
return mac_image(data, n, err, err_sz);
|
|
#else
|
|
const char *wl = getenv("WAYLAND_DISPLAY");
|
|
if (wl && *wl)
|
|
return via_lister("wl-paste --list-types 2>/dev/null",
|
|
"wl-paste --no-newline --type '%s' 2>/dev/null",
|
|
"wl-clipboard (wl-paste)", data, n, err, err_sz);
|
|
const char *x = getenv("DISPLAY");
|
|
if (x && *x)
|
|
return via_lister("xclip -selection clipboard -t TARGETS -o 2>/dev/null",
|
|
"xclip -selection clipboard -t '%s' -o 2>/dev/null",
|
|
"xclip", data, n, err, err_sz);
|
|
set_err(err, err_sz, "no clipboard here (no Wayland or X11 display)");
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
|
|
/* --- text clipboard ------------------------------------------------------ */
|
|
|
|
#ifdef _WIN32
|
|
|
|
int cst_clipboard_text(char **data, size_t *n, char *err, size_t err_sz) {
|
|
*data = NULL;
|
|
*n = 0;
|
|
if (!IsClipboardFormatAvailable(CF_UNICODETEXT) || !OpenClipboard(NULL)) {
|
|
set_err(err, err_sz, "no text on the clipboard");
|
|
return -1;
|
|
}
|
|
HANDLE h = GetClipboardData(CF_UNICODETEXT);
|
|
const wchar_t *w = h ? GlobalLock(h) : NULL;
|
|
if (!w) {
|
|
if (h)
|
|
GlobalUnlock(h);
|
|
CloseClipboard();
|
|
set_err(err, err_sz, "no text on the clipboard");
|
|
return -1;
|
|
}
|
|
char *utf8 = os_wide_to_utf8(w);
|
|
size_t len = utf8 ? strlen(utf8) : 0;
|
|
GlobalUnlock(h);
|
|
CloseClipboard();
|
|
if (!utf8 || !len) {
|
|
free(utf8);
|
|
set_err(err, err_sz, "no text on the clipboard");
|
|
return -1;
|
|
}
|
|
if (len > CST_CLIPBOARD_TEXT_MAX) {
|
|
free(utf8);
|
|
set_err(err, err_sz, "clipboard text is too large");
|
|
return -1;
|
|
}
|
|
*data = utf8;
|
|
*n = len;
|
|
return 0;
|
|
}
|
|
|
|
int cst_clipboard_set_text(const char *text, size_t n, char *err,
|
|
size_t err_sz) {
|
|
if (!text || os_clipboard_set(text, n) != 0) {
|
|
set_err(err, err_sz, "could not put text on the clipboard");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
/* stdout of a clipboard reader, capped and NUL-terminated. */
|
|
static int clip_read(const char *cmd, const char *tool, char **data, size_t *n,
|
|
char *err, size_t err_sz) {
|
|
os_run_result_t r;
|
|
memset(&r, 0, sizeof(r));
|
|
if (os_run_shell_c(NULL, cmd, CST_CLIPBOARD_TEXT_MAX + 1, &r) != 0) {
|
|
os_run_result_free(&r);
|
|
set_err(err, err_sz, "could not read the clipboard");
|
|
return -1;
|
|
}
|
|
if (r.exit_code != 0 || r.out_len == 0) {
|
|
int missing = r.exit_code == 127;
|
|
os_run_result_free(&r);
|
|
if (missing) {
|
|
char msg[96];
|
|
snprintf(msg, sizeof(msg), "reading the clipboard needs %s", tool);
|
|
set_err(err, err_sz, msg);
|
|
} else {
|
|
set_err(err, err_sz, "no text on the clipboard");
|
|
}
|
|
return -1;
|
|
}
|
|
if (r.out_len > CST_CLIPBOARD_TEXT_MAX) {
|
|
os_run_result_free(&r);
|
|
set_err(err, err_sz, "clipboard text is too large");
|
|
return -1;
|
|
}
|
|
char *copy = malloc(r.out_len + 1);
|
|
if (!copy) {
|
|
os_run_result_free(&r);
|
|
set_err(err, err_sz, "out of memory");
|
|
return -1;
|
|
}
|
|
memcpy(copy, r.out, r.out_len);
|
|
copy[r.out_len] = 0;
|
|
os_run_result_free(&r);
|
|
*data = copy;
|
|
*n = r.out_len;
|
|
return 0;
|
|
}
|
|
|
|
int cst_clipboard_text(char **data, size_t *n, char *err, size_t err_sz) {
|
|
*data = NULL;
|
|
*n = 0;
|
|
#ifdef __APPLE__
|
|
return clip_read("pbpaste 2>/dev/null", "pbpaste", data, n, err, err_sz);
|
|
#else
|
|
const char *wl = getenv("WAYLAND_DISPLAY");
|
|
if (wl && *wl)
|
|
return clip_read("wl-paste --type text/plain --no-newline 2>/dev/null",
|
|
"wl-clipboard (wl-paste)", data, n, err, err_sz);
|
|
const char *x = getenv("DISPLAY");
|
|
if (x && *x)
|
|
return clip_read("xclip -selection clipboard -o 2>/dev/null", "xclip",
|
|
data, n, err, err_sz);
|
|
set_err(err, err_sz, "no clipboard here (no Wayland or X11 display)");
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
int cst_clipboard_set_text(const char *text, size_t n, char *err,
|
|
size_t err_sz) {
|
|
if (!text)
|
|
return -1;
|
|
if (n > CST_CLIPBOARD_TEXT_MAX) {
|
|
set_err(err, err_sz, "text is too large for the clipboard");
|
|
return -1;
|
|
}
|
|
const char *fmt = NULL, *tool = NULL;
|
|
#ifdef __APPLE__
|
|
fmt = "pbcopy < '%s'";
|
|
tool = "pbcopy";
|
|
#else
|
|
if (getenv("WAYLAND_DISPLAY")) {
|
|
fmt = "wl-copy < '%s'";
|
|
tool = "wl-clipboard (wl-copy)";
|
|
} else if (getenv("DISPLAY")) {
|
|
fmt = "xclip -selection clipboard -i < '%s'";
|
|
tool = "xclip";
|
|
}
|
|
#endif
|
|
if (!fmt) {
|
|
set_err(err, err_sz, "no clipboard here (no Wayland or X11 display)");
|
|
return -1;
|
|
}
|
|
char tmp[OS_PATH_MAX];
|
|
int fd = os_mkstemp(tmp, sizeof(tmp), "cst-clip");
|
|
if (fd < 0) {
|
|
set_err(err, err_sz, "could not create a temporary file");
|
|
return -1;
|
|
}
|
|
FILE *fp = fdopen(fd, "wb");
|
|
int ok = fp && fwrite(text, 1, n, fp) == n && fclose(fp) == 0;
|
|
if (!ok) {
|
|
if (fp)
|
|
fclose(fp);
|
|
remove(tmp);
|
|
set_err(err, err_sz, "could not write the text");
|
|
return -1;
|
|
}
|
|
char cmd[OS_PATH_MAX + 64];
|
|
snprintf(cmd, sizeof(cmd), fmt, tmp);
|
|
os_run_result_t r;
|
|
memset(&r, 0, sizeof(r));
|
|
int rc = os_run_shell_c(NULL, cmd, 4096, &r);
|
|
int code = r.exit_code;
|
|
os_run_result_free(&r);
|
|
remove(tmp);
|
|
if (rc != 0 || code != 0) {
|
|
char msg[96];
|
|
snprintf(msg, sizeof(msg), "writing the clipboard needs %s", tool);
|
|
set_err(err, err_sz, msg);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int hexval(int c) {
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
if (c >= 'a' && c <= 'f')
|
|
return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'F')
|
|
return c - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
/* file://[host]/path -> /path, percent-decoded; 0 on success. */
|
|
static int from_file_uri(char *s) {
|
|
const char *p = s + 7;
|
|
const char *slash = strchr(p, '/');
|
|
if (!slash)
|
|
return -1;
|
|
if (slash != p && !(slash - p == 9 && strncmp(p, "localhost", 9) == 0))
|
|
return -1;
|
|
char *o = s;
|
|
for (const char *q = slash; *q; q++) {
|
|
if (*q == '%' && hexval(q[1]) >= 0 && hexval(q[2]) >= 0) {
|
|
*o++ = (char)(hexval(q[1]) * 16 + hexval(q[2]));
|
|
q += 2;
|
|
} else {
|
|
*o++ = *q;
|
|
}
|
|
}
|
|
*o = 0;
|
|
#ifdef _WIN32
|
|
if (s[0] == '/' && isalpha((unsigned char)s[1]) && s[2] == ':')
|
|
memmove(s, s + 1, strlen(s));
|
|
#endif
|
|
return s[0] ? 0 : -1;
|
|
}
|
|
|
|
static int is_space(char c) {
|
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
|
}
|
|
|
|
int cst_paste_paths(const char *text, size_t len,
|
|
char out[][CST_PASTE_PATH_MAX], int max) {
|
|
int n = 0;
|
|
size_t i = 0;
|
|
while (i < len) {
|
|
while (i < len && is_space(text[i]))
|
|
i++;
|
|
if (i >= len)
|
|
break;
|
|
if (n >= max)
|
|
return -1;
|
|
char *tok = out[n];
|
|
size_t k = 0;
|
|
char q = 0;
|
|
while (i < len) {
|
|
char c = text[i];
|
|
if (!c)
|
|
return -1;
|
|
if (q) {
|
|
if (c == q) {
|
|
q = 0;
|
|
i++;
|
|
continue;
|
|
}
|
|
} else {
|
|
if (c == '\'' || c == '"') {
|
|
q = c;
|
|
i++;
|
|
continue;
|
|
}
|
|
if (is_space(c))
|
|
break;
|
|
#ifndef _WIN32
|
|
if (c == '\\' && i + 1 < len)
|
|
c = text[++i];
|
|
#endif
|
|
}
|
|
if (k + 1 >= CST_PASTE_PATH_MAX)
|
|
return -1;
|
|
tok[k++] = c;
|
|
i++;
|
|
}
|
|
if (q || !k)
|
|
return -1;
|
|
tok[k] = 0;
|
|
if (strncmp(tok, "file://", 7) == 0 && from_file_uri(tok) != 0)
|
|
return -1;
|
|
n++;
|
|
}
|
|
return n > 0 ? n : -1;
|
|
}
|