#define _POSIX_C_SOURCE 200809L #include "cJSON.h" #include "cst_attach.h" #include "cst_agent.h" #include "cst_clipboard.h" #include "cst_test_util.h" #include "os_compat.h" #include #include #include static int nfail; #define EXPECT(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, msg); \ nfail++; \ } \ } while (0) static void put_be32(unsigned char *p, unsigned v) { p[0] = (unsigned char)(v >> 24); p[1] = (unsigned char)(v >> 16); p[2] = (unsigned char)(v >> 8); p[3] = (unsigned char)v; } static void put_le16(unsigned char *p, unsigned v) { p[0] = (unsigned char)(v & 0xFF); p[1] = (unsigned char)((v >> 8) & 0xFF); } /* 8 signature + 4 length + "IHDR" + w + h (what cst_attach_dimensions reads). */ static size_t png_hdr(unsigned char *b, unsigned w, unsigned h) { static const unsigned char sig[8] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'}; memcpy(b, sig, 8); b[8] = b[9] = b[10] = 0; b[11] = 13; memcpy(b + 12, "IHDR", 4); put_be32(b + 16, w); put_be32(b + 20, h); return 32; } static size_t gif_hdr(unsigned char *b, unsigned w, unsigned h) { memcpy(b, "GIF89a", 6); put_le16(b + 6, w); put_le16(b + 8, h); return 16; } static void put_le32(unsigned char *p, unsigned 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 size_t bmp_hdr(unsigned char *b, int w, int h) { memset(b, 0, 64); b[0] = 'B'; b[1] = 'M'; put_le32(b + 18, (unsigned)w); put_le32(b + 22, (unsigned)h); return 64; } /* FFD8 + a filler APP0 segment + SOF0 carrying the frame size. */ static size_t jpeg_hdr(unsigned char *b, unsigned w, unsigned h) { size_t i = 0; b[i++] = 0xFF; b[i++] = 0xD8; b[i++] = 0xFF; b[i++] = 0xE0; b[i++] = 0x00; b[i++] = 0x04; b[i++] = 0x00; b[i++] = 0x00; b[i++] = 0xFF; b[i++] = 0xC0; b[i++] = 0x00; b[i++] = 0x0B; b[i++] = 0x08; b[i++] = (unsigned char)(h >> 8); b[i++] = (unsigned char)h; b[i++] = (unsigned char)(w >> 8); b[i++] = (unsigned char)w; b[i++] = 0x01; /* one component */ b[i++] = 0x01; /* component id */ b[i++] = 0x11; /* sampling */ b[i++] = 0x00; /* quant table */ return i; } static void test_sniff(void) { unsigned char b[64]; char mime[32]; size_t n; n = png_hdr(b, 32, 24); EXPECT(cst_attach_sniff(b, n, mime, sizeof(mime)) == 0 && strcmp(mime, "image/png") == 0, "PNG sniffed"); n = jpeg_hdr(b, 20, 10); EXPECT(cst_attach_sniff(b, n, mime, sizeof(mime)) == 0 && strcmp(mime, "image/jpeg") == 0, "JPEG sniffed"); n = gif_hdr(b, 3, 5); EXPECT(cst_attach_sniff(b, n, mime, sizeof(mime)) == 0 && strcmp(mime, "image/gif") == 0, "GIF sniffed"); memcpy(b, "RIFF\0\0\0\0WEBPVP8 ", 16); EXPECT(cst_attach_sniff(b, 30, mime, sizeof(mime)) == 0 && strcmp(mime, "image/webp") == 0, "WebP sniffed"); n = bmp_hdr(b, 7, 9); EXPECT(cst_attach_sniff(b, n, mime, sizeof(mime)) == 0 && strcmp(mime, "image/bmp") == 0, "BMP sniffed"); /* an extension never decides: text and a PDF are not images */ EXPECT(cst_attach_sniff("hello, world", 12, mime, sizeof(mime)) != 0, "text rejected"); EXPECT(cst_attach_sniff("%PDF-1.7\n", 9, mime, sizeof(mime)) != 0, "PDF rejected"); EXPECT(cst_attach_sniff("\x89PNG", 4, mime, sizeof(mime)) != 0, "truncated PNG rejected"); EXPECT(cst_attach_sniff("", 0, mime, sizeof(mime)) != 0, "empty rejected"); } static void test_dimensions(void) { unsigned char b[64]; int w = 0, h = 0; size_t n; n = png_hdr(b, 1280, 720); EXPECT(cst_attach_dimensions(b, n, &w, &h) == 0 && w == 1280 && h == 720, "PNG dimensions"); n = gif_hdr(b, 16, 40); EXPECT(cst_attach_dimensions(b, n, &w, &h) == 0 && w == 16 && h == 40, "GIF dimensions"); n = bmp_hdr(b, 7, 9); EXPECT(cst_attach_dimensions(b, n, &w, &h) == 0 && w == 7 && h == 9, "BMP dimensions"); n = jpeg_hdr(b, 640, 480); EXPECT(cst_attach_dimensions(b, n, &w, &h) == 0 && w == 640 && h == 480, "JPEG dimensions"); w = h = -1; EXPECT(cst_attach_dimensions("nope", 4, &w, &h) != 0 && w == 0 && h == 0, "unknown format gives 0x0"); } static void test_base64(void) { char *s = cst_base64_encode("Man", 3); EXPECT(s && strcmp(s, "TWFu") == 0, "base64 "); free(s); s = cst_base64_encode("Ma", 2); EXPECT(s && strcmp(s, "TWE=") == 0, "base64 padding 1"); free(s); s = cst_base64_encode("M", 1); EXPECT(s && strcmp(s, "TQ==") == 0, "base64 padding 2"); free(s); s = cst_base64_encode("\x00\xff\xfe", 3); EXPECT(s && strcmp(s, "AP/+") == 0, "base64 binary"); free(s); size_t n = 0; char *d = cst_base64_decode("TWFu", 4, &n); EXPECT(d && n == 3 && memcmp(d, "Man", 3) == 0, "decode "); free(d); d = cst_base64_decode("TQ==", 4, &n); EXPECT(d && n == 1 && d[0] == 'M', "decode with padding"); free(d); d = cst_base64_decode("AP/+\n", 5, &n); EXPECT(d && n == 3 && memcmp(d, "\x00\xff\xfe", 3) == 0, "decode binary, newline ignored"); free(d); d = cst_base64_decode("AP_-", 4, &n); EXPECT(d && n == 3 && memcmp(d, "\x00\xff\xfe", 3) == 0, "URL-safe alphabet"); free(d); EXPECT(cst_base64_decode("A*B=", 4, &n) == NULL, "bad character refused"); unsigned char raw[200]; for (int i = 0; i < 200; i++) raw[i] = (unsigned char)(i * 37); for (size_t len = 0; len < 7; len++) { char *e = cst_base64_encode(raw, 190 + len); d = e ? cst_base64_decode(e, strlen(e), &n) : NULL; EXPECT(d && n == 190 + len && memcmp(d, raw, n) == 0, "round trip"); free(e); free(d); } } static void test_rel_ok(const char *root) { EXPECT(cst_attach_rel_ok("img-0a1b2c3d.png"), "normal name accepted"); EXPECT(!cst_attach_rel_ok("../x.png"), "parent dir refused"); EXPECT(!cst_attach_rel_ok("a/../../b.png"), "slash refused"); EXPECT(!cst_attach_rel_ok("/etc/passwd"), "absolute refused"); EXPECT(!cst_attach_rel_ok("a\\b.png"), "backslash refused"); EXPECT(!cst_attach_rel_ok(".hidden"), "dot file refused"); EXPECT(!cst_attach_rel_ok(""), "empty refused"); cJSON *arr = cJSON_Parse("[{\"id\":\"x\",\"rel\":\"../../secret.png\"}," "{\"id\":\"y\",\"rel\":\"img-1.png\"}]"); int n = 0; cst_attachment_t *back = cst_attachments_from_json(arr, &n); EXPECT(n == 1 && back && strcmp(back[0].id, "y") == 0, "escaping rel dropped on load"); cst_attachments_free(back, n); cJSON_Delete(arr); /* a stored file swapped for non-image bytes is not sent */ char session[1024], full[1024], err[256]; snprintf(session, sizeof(session), "%.900s/sessions/r.jsonl", root); unsigned char b[64]; size_t bn = png_hdr(b, 4, 4); cst_attachment_t *a = cst_attach_import_bytes(session, "x.png", (const char *)b, bn, err, sizeof(err)); EXPECT(a != NULL, "stored"); if (!a) return; EXPECT(cst_attach_full_path(session, a, full, sizeof(full)) == 0, "path"); FILE *f = fopen(full, "wb"); if (f) { fputs("plain text now", f); fclose(f); } size_t rn = 0; EXPECT(cst_attach_read(session, a, &rn, err, sizeof(err)) == NULL && strstr(err, "no longer an image"), "read re-checks the bytes"); EXPECT(cst_attach_data_url(session, a) == NULL, "no data url for it"); snprintf(a->rel, sizeof(a->rel), "../r.jsonl"); EXPECT(cst_attach_full_path(session, a, full, sizeof(full)) != 0, "full path refuses an escaping rel"); cst_attach_free(a); } static void test_store(const char *root) { char session[1024], dir[1024]; snprintf(session, sizeof(session), "%.900s/sessions/a.jsonl", root); unsigned char b[64]; size_t n = png_hdr(b, 32, 24); char err[256]; cst_attachment_t *a = cst_attach_import_bytes(session, "shot.png", (const char *)b, n, err, sizeof(err)); EXPECT(a != NULL, "import stored"); if (!a) { fprintf(stderr, " err: %s\n", err); return; } EXPECT(strcmp(a->mime, "image/png") == 0, "stored mime"); EXPECT(a->width == 32 && a->height == 24, "stored dimensions"); EXPECT(a->bytes == (long long)n, "stored size"); EXPECT(strcmp(a->name, "shot.png") == 0, "stored name"); EXPECT(strcmp(a->origin, "user") == 0, "origin=user"); EXPECT(strcmp(a->rel, "img-") != 0 && strstr(a->rel, ".png") != NULL, "rel has a .png extension"); EXPECT(cst_attach_dir(session, dir, sizeof(dir)) == 0, "attach dir"); char full[1024]; EXPECT(cst_attach_full_path(session, a, full, sizeof(full)) == 0 && strstr(full, "attachments/") != NULL, "full path inside attachments/"); FILE *f = fopen(full, "rb"); EXPECT(f != NULL, "stored file exists"); if (f) fclose(f); char desc[128]; cst_attach_describe(a, desc, sizeof(desc)); EXPECT(strstr(desc, "32x24") && strstr(desc, "png"), "describe has size"); size_t rn = 0; char *data = cst_attach_read(session, a, &rn, err, sizeof(err)); EXPECT(data && rn == n && memcmp(data, b, n) == 0, "read back exact bytes"); free(data); char *url = cst_attach_data_url(session, a); EXPECT(url && strncmp(url, "data:image/png;base64,", 22) == 0, "data url prefix"); if (url) EXPECT(strlen(url) == 22 + ((n + 2) / 3) * 4, "data url length"); free(url); cst_attach_free(a); /* a text file named .png is still refused */ EXPECT(cst_attach_import_bytes(session, "fake.png", "not an image", 12, err, sizeof(err)) == NULL, "content decides, not the name"); EXPECT(strstr(err, "not a supported image") != NULL, "err explains why"); EXPECT(cst_attach_import_bytes(session, "empty.png", "", 0, err, sizeof(err)) == NULL, "empty data refused"); } static void test_import_file(const char *root) { char session[1024], src[1024]; snprintf(session, sizeof(session), "%.900s/sessions/b.jsonl", root); snprintf(src, sizeof(src), "%.900s/logo.gif", root); unsigned char b[64]; size_t n = gif_hdr(b, 8, 6); FILE *f = fopen(src, "wb"); EXPECT(f != NULL, "write source image"); if (f) { fwrite(b, 1, n, f); fclose(f); } char err[256]; cst_attachment_t *a = cst_attach_import_file(session, src, err, sizeof(err)); EXPECT(a != NULL, "file imported"); if (a) { EXPECT(strcmp(a->mime, "image/gif") == 0, "imported mime"); EXPECT(strcmp(a->name, "logo.gif") == 0, "imported keeps the leaf name"); EXPECT(a->width == 8 && a->height == 6, "imported dimensions"); cst_attach_free(a); } EXPECT(cst_attach_import_file(session, "/nonexistent/x.png", err, sizeof(err)) == NULL, "missing file refused"); } /* Documents: text is stored as text, a PDF as its text layer while the mime * still says PDF, and neither is an image as far as the wire is concerned. */ static void test_documents(const char *root) { char session[1024], src[1024]; snprintf(session, sizeof(session), "%.900s/sessions/doc.jsonl", root); char err[256] = ""; const char *text = "# Notes\n\nFive painters, one canvas.\n"; snprintf(src, sizeof(src), "%.900s/notes.md", root); FILE *f = fopen(src, "wb"); EXPECT(f != NULL, "write the text file"); if (f) { fwrite(text, 1, strlen(text), f); fclose(f); } cst_attachment_t *a = cst_attach_import_file(session, src, err, sizeof(err)); EXPECT(a != NULL, "text file imported"); if (a) { EXPECT(strcmp(a->mime, "text/plain") == 0, "text mime"); EXPECT(!cst_attach_is_image(a) && cst_attach_is_text(a), "not an image"); EXPECT(a->pages == 0, "no pages for text"); EXPECT(strstr(a->rel, ".txt") != NULL, "text stored as .txt"); EXPECT(a->bytes == (long long)strlen(text), "text size"); size_t n = 0; char *back = cst_attach_read(session, a, &n, err, sizeof(err)); EXPECT(back && n == strlen(text) && memcmp(back, text, n) == 0, "text reads back exactly"); free(back); EXPECT(cst_attach_data_url(session, a) == NULL, "a document has no data url"); char desc[128]; cst_attach_describe(a, desc, sizeof(desc)); EXPECT(strstr(desc, "text") != NULL, "describe says text"); cst_attach_free(a); } /* a one-page PDF with a text layer */ static const char PDF[] = "%PDF-1.4\n" "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n" "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n" "3 0 obj\n<< /Type /Page /Parent 2 0 R /Resources << /Font << /F1 4 0 R " ">> >> /Contents 5 0 R >>\nendobj\n" "4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\n" "endobj\n" "5 0 obj\n<< >>\nstream\n" "BT /F1 12 Tf 72 720 Td (Attached paper abstract) Tj ET\n" "endstream\nendobj\n" "trailer\n<< /Root 1 0 R >>\n%%EOF\n"; snprintf(src, sizeof(src), "%.900s/paper.pdf", root); f = fopen(src, "wb"); EXPECT(f != NULL, "write the PDF"); if (f) { fwrite(PDF, 1, strlen(PDF), f); fclose(f); } err[0] = 0; a = cst_attach_import_file(session, src, err, sizeof(err)); EXPECT(a != NULL, "PDF imported"); if (a) { EXPECT(strcmp(a->mime, "application/pdf") == 0, "the PDF keeps its mime"); EXPECT(a->pages == 1, "page count comes from the extractor"); EXPECT(strstr(a->rel, ".txt") != NULL, "the text layer is what is stored"); EXPECT(cst_attach_is_text(a), "documents are text on the wire"); char desc[128]; cst_attach_describe(a, desc, sizeof(desc)); EXPECT(strstr(desc, "pdf") != NULL && strstr(desc, "1 page") != NULL, "describe names the PDF and its pages"); size_t n = 0; char *back = cst_attach_read(session, a, &n, err, sizeof(err)); EXPECT(back && strstr(back, "Attached paper abstract") != NULL, "the extracted text reads back"); free(back); cst_attach_free(a); } err[0] = 0; EXPECT(cst_attach_import_file(session, "/nonexistent/x.pdf", err, sizeof(err)) == NULL, "a missing PDF is refused"); /* neither image, PDF nor text: a binary blob */ static const char binary[] = "\x01\x02\x03\x00\x04\x05"; snprintf(src, sizeof(src), "%.900s/blob.bin", root); f = fopen(src, "wb"); if (f) { fwrite(binary, 1, sizeof(binary), f); fclose(f); } err[0] = 0; EXPECT(cst_attach_import_file(session, src, err, sizeof(err)) == NULL, "a binary file is refused"); EXPECT(strstr(err, "not an image") != NULL, "err says what was expected"); } static void test_json_roundtrip(void) { cst_attachment_t list[3]; memset(list, 0, sizeof(list)); snprintf(list[0].id, sizeof(list[0].id), "img-1"); snprintf(list[0].name, sizeof(list[0].name), "a.png"); snprintf(list[0].mime, sizeof(list[0].mime), "image/png"); snprintf(list[0].rel, sizeof(list[0].rel), "img-1.png"); list[0].bytes = 12345; list[0].width = 640; list[0].height = 480; snprintf(list[0].origin, sizeof(list[0].origin), "user"); snprintf(list[1].id, sizeof(list[1].id), "img-2"); snprintf(list[1].name, sizeof(list[1].name), "b.jpg"); snprintf(list[1].mime, sizeof(list[1].mime), "image/jpeg"); snprintf(list[1].rel, sizeof(list[1].rel), "img-2.jpg"); list[1].bytes = 4096; snprintf(list[1].detail, sizeof(list[1].detail), "high"); snprintf(list[1].origin, sizeof(list[1].origin), "view_image"); snprintf(list[1].mime, sizeof(list[1].mime), "application/pdf"); list[1].pages = 12; snprintf(list[2].id, sizeof(list[2].id), "img-3"); snprintf(list[2].name, sizeof(list[2].name), "c.webp"); snprintf(list[2].mime, sizeof(list[2].mime), "image/webp"); snprintf(list[2].rel, sizeof(list[2].rel), "img-3.webp"); list[2].bytes = 7; char *json = cst_attachments_to_json(list, 3); EXPECT(json != NULL, "to_json"); int n = 0; cJSON *parsed = cJSON_Parse(json); cst_attachment_t *back = cst_attachments_from_json(parsed, &n); cJSON_Delete(parsed); EXPECT(back != NULL && n == 3, "from_json count"); if (back && n == 3) { EXPECT(strcmp(back[0].id, "img-1") == 0 && back[0].bytes == 12345 && back[0].width == 640 && back[0].height == 480, "first entry survives"); EXPECT(strcmp(back[1].detail, "high") == 0 && strcmp(back[1].origin, "view_image") == 0, "detail and origin survive"); EXPECT(strcmp(back[1].mime, "application/pdf") == 0 && back[1].pages == 12, "a document keeps its mime and page count"); EXPECT(back[2].pages == 0, "pages default to 0"); EXPECT(strcmp(back[2].mime, "image/webp") == 0 && back[2].width == 0, "entry without dimensions"); EXPECT(strcmp(back[0].detail, "auto") == 0, "missing detail defaults"); cst_attachments_free(back, n); } free(json); /* entries without rel/id are dropped; a non-array is NULL */ cJSON *arr = cJSON_Parse("[{\"id\":\"x\"},{\"id\":\"y\",\"rel\":\"y.png\"}]"); back = cst_attachments_from_json(arr, &n); EXPECT(back != NULL && n == 1 && strcmp(back[0].id, "y") == 0, "incomplete entries skipped"); cst_attachments_free(back, n); cJSON_Delete(arr); cJSON *obj = cJSON_Parse("{}"); n = 7; EXPECT(cst_attachments_from_json(obj, &n) == NULL && n == 0, "object is not an attachment array"); cJSON_Delete(obj); EXPECT(cst_attachments_to_json(NULL, 0) == NULL, "empty list is NULL"); } static void test_lists(void) { cst_attachment_t src; memset(&src, 0, sizeof(src)); snprintf(src.id, sizeof(src.id), "img-x"); snprintf(src.mime, sizeof(src.mime), "image/png"); cst_attachment_t *list = NULL; int n = 0, cap = 0; for (int i = 0; i < 10; i++) EXPECT(cst_attachments_push(&list, &n, &cap, &src) == 0, "push"); EXPECT(n == 10 && cap >= 10, "list grew"); EXPECT(strcmp(list[9].id, "img-x") == 0, "last entry copied"); cst_attachment_t *dup = cst_attachments_dup(list, n); EXPECT(dup && strcmp(dup[3].mime, "image/png") == 0, "dup copies fields"); dup[3].id[0] = 'Z'; EXPECT(list[3].id[0] == 'i', "dup is a deep copy"); cst_attachments_free(dup, n); EXPECT(cst_attachments_dup(NULL, 0) == NULL, "dup of nothing is NULL"); cst_attachments_free(list, n); } static void test_agent_pending(const char *root) { cst_agent_config_t cfg; memset(&cfg, 0, sizeof(cfg)); snprintf(cfg.workspace_root, sizeof(cfg.workspace_root), "%s", root); snprintf(cfg.session_path, sizeof(cfg.session_path), "%s/sessions/c.jsonl", root); cst_agent_t *a = cst_agent_create(&cfg); EXPECT(a != NULL, "agent created"); if (!a) return; char err[256], line[128]; EXPECT(cst_agent_pending_count(a) == 0, "no pending at start"); EXPECT(cst_agent_pending_describe(a, 0, line, sizeof(line)) == -1, "describe out of range"); unsigned char b[32]; size_t n = png_hdr(b, 4, 2); char src[1024]; snprintf(src, sizeof(src), "%.900s/pic.png", root); FILE *f = fopen(src, "wb"); EXPECT(f != NULL, "write the source png"); if (f) { fwrite(b, 1, n, f); fclose(f); } EXPECT(cst_agent_attach_file(a, src, err, sizeof(err)) == 0, "attach through the agent"); EXPECT(cst_agent_pending_count(a) == 1, "one pending"); EXPECT(cst_agent_pending_describe(a, 0, line, sizeof(line)) == 0 && strstr(line, "4x2") != NULL, "pending describe shows the size"); EXPECT(cst_agent_pending_is_image(a, 0) == 1, "a pending image says that it is one"); EXPECT(cst_agent_pending_rel(a, 0) != NULL && cst_attach_rel_ok(cst_agent_pending_rel(a, 0)), "its stored name is one a view may serve"); EXPECT(cst_agent_pending_rel(a, -1) == NULL && cst_agent_pending_rel(a, 99) == NULL && cst_agent_pending_is_image(a, 99) == 0, "out of range has no name and is no image"); EXPECT(cst_agent_attach_file(a, "/nonexistent/x.png", err, sizeof(err)) == -1, "missing file refused"); EXPECT(cst_agent_pending_count(a) == 1, "a failure keeps the queue"); for (int i = 1; i < CST_ATTACH_MAX_COUNT; i++) EXPECT(cst_agent_attach_file(a, src, err, sizeof(err)) == 0, "attach more"); EXPECT(cst_agent_pending_count(a) == CST_ATTACH_MAX_COUNT, "queue full"); EXPECT(cst_agent_attach_file(a, src, err, sizeof(err)) == -1 && strstr(err, "at most") != NULL, "one over the limit is refused"); cst_agent_pending_clear(a); EXPECT(cst_agent_pending_count(a) == 0, "cleared"); cst_agent_destroy(a); } static void test_paste_paths(void) { static char p[CST_PASTE_MAX_PATHS][CST_PASTE_PATH_MAX]; const char *s = "/tmp/a.png"; EXPECT(cst_paste_paths(s, strlen(s), p, 8) == 1 && strcmp(p[0], "/tmp/a.png") == 0, "single path"); s = " '/home/u/my shot.png' \"/x/y z.jpg\"\n/plain.gif\r\n"; EXPECT(cst_paste_paths(s, strlen(s), p, 8) == 3, "quoted paths count"); EXPECT(strcmp(p[0], "/home/u/my shot.png") == 0, "single quotes"); EXPECT(strcmp(p[1], "/x/y z.jpg") == 0, "double quotes"); EXPECT(strcmp(p[2], "/plain.gif") == 0, "newline separated"); s = "file:///home/u/a%20b.png file://localhost/c.png"; EXPECT(cst_paste_paths(s, strlen(s), p, 8) == 2, "file uris"); EXPECT(strcmp(p[0], "/home/u/a b.png") == 0, "percent decoded"); EXPECT(strcmp(p[1], "/c.png") == 0, "localhost host"); s = "file://evil.example/c.png"; EXPECT(cst_paste_paths(s, strlen(s), p, 8) == -1, "remote host refused"); #ifndef _WIN32 s = "/home/u/my\\ shot.png"; EXPECT(cst_paste_paths(s, strlen(s), p, 8) == 1 && strcmp(p[0], "/home/u/my shot.png") == 0, "backslash escape"); #endif s = "'/unterminated.png"; EXPECT(cst_paste_paths(s, strlen(s), p, 8) == -1, "unbalanced quote"); s = " \n "; EXPECT(cst_paste_paths(s, strlen(s), p, 8) == -1, "blank"); s = "a b c"; EXPECT(cst_paste_paths(s, strlen(s), p, 2) == -1, "too many"); } static void test_dib(void) { /* 3x2, 24-bit, bottom-up: stride 12 (9 bytes + pad). */ unsigned char dib[40 + 24]; memset(dib, 0, sizeof(dib)); put_le32(dib, 40); put_le32(dib + 4, 3); put_le32(dib + 8, 2); put_le16(dib + 12, 1); put_le16(dib + 14, 24); dib[40] = 0xff; /* bottom-left pixel blue (BGR) */ size_t n = 0; char *img = cst_dib_to_image(dib, sizeof(dib), &n); char mime[32]; int w = 0, h = 0; EXPECT(img && cst_attach_sniff(img, n, mime, sizeof(mime)) == 0 && strcmp(mime, "image/png") == 0, "24-bit DIB becomes a PNG"); EXPECT(img && cst_attach_dimensions(img, n, &w, &h) == 0 && w == 3 && h == 2, "PNG keeps the size"); free(img); put_le16(dib + 14, 8); /* palette needs 256 entries: past the end */ EXPECT(cst_dib_to_image(dib, sizeof(dib), &n) == NULL, "short DIB refused"); put_le32(dib, 4); EXPECT(cst_dib_to_image(dib, sizeof(dib), &n) == NULL, "bad header size"); } int main(void) { char root[1024]; if (test_tmpdir(root, sizeof(root), "cst-attach") != 0) { fprintf(stderr, "cannot create a scratch dir\n"); return 1; } test_sniff(); test_dimensions(); test_base64(); test_rel_ok(root); test_paste_paths(); test_dib(); test_store(root); test_import_file(root); test_documents(root); test_json_roundtrip(); test_lists(); test_agent_pending(root); test_rmrf(root); if (nfail) { fprintf(stderr, "test_attach: %d failed\n", nfail); return 1; } printf("test_attach: OK\n"); return 0; }