|
11 | 11 | #include <yyjson/yyjson.h> |
12 | 12 | #include <string.h> |
13 | 13 | #include <stdlib.h> |
| 14 | +#include <stdbool.h> |
14 | 15 |
|
15 | 16 | /* ══════════════════════════════════════════════════════════════════ |
16 | 17 | * JSON-RPC PARSING |
@@ -1291,6 +1292,31 @@ static char *call_snippet(cbm_mcp_server_t *srv, const char *args_json) { |
1291 | 1292 | return text; |
1292 | 1293 | } |
1293 | 1294 |
|
| 1295 | +static bool is_valid_json_response(const char *json) { |
| 1296 | + if (!json) { |
| 1297 | + return false; |
| 1298 | + } |
| 1299 | + yyjson_doc *doc = yyjson_read(json, strlen(json), 0); |
| 1300 | + if (!doc) { |
| 1301 | + return false; |
| 1302 | + } |
| 1303 | + yyjson_doc_free(doc); |
| 1304 | + return true; |
| 1305 | +} |
| 1306 | + |
| 1307 | +static bool snippet_source_has_replacement(const char *json) { |
| 1308 | + yyjson_doc *doc = yyjson_read(json, strlen(json), 0); |
| 1309 | + if (!doc) { |
| 1310 | + return false; |
| 1311 | + } |
| 1312 | + yyjson_val *root = yyjson_doc_get_root(doc); |
| 1313 | + yyjson_val *source = yyjson_obj_get(root, "source"); |
| 1314 | + const char *source_str = yyjson_get_str(source); |
| 1315 | + bool found = source_str && strstr(source_str, "\xEF\xBF\xBD"); |
| 1316 | + yyjson_doc_free(doc); |
| 1317 | + return found; |
| 1318 | +} |
| 1319 | + |
1294 | 1320 | /* ── TestSnippet_ExactQN ──────────────────────────────────────── */ |
1295 | 1321 |
|
1296 | 1322 | TEST(snippet_exact_qn) { |
@@ -1577,6 +1603,46 @@ TEST(snippet_include_neighbors_enabled) { |
1577 | 1603 | PASS(); |
1578 | 1604 | } |
1579 | 1605 |
|
| 1606 | +/* ── TestSnippet_SourceInvalidUtf8 ────────────────────────────── */ |
| 1607 | + |
| 1608 | +TEST(snippet_source_invalid_utf8) { |
| 1609 | + char tmp[256]; |
| 1610 | + cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp)); |
| 1611 | + ASSERT_NOT_NULL(srv); |
| 1612 | + |
| 1613 | + char src_path[512]; |
| 1614 | + snprintf(src_path, sizeof(src_path), "%s/project/main.go", tmp); |
| 1615 | + FILE *fp = fopen(src_path, "wb"); |
| 1616 | + ASSERT_NOT_NULL(fp); |
| 1617 | + const unsigned char source[] = { |
| 1618 | + 'p', 'a', 'c', 'k', 'a', 'g', 'e', ' ', 'm', 'a', 'i', 'n', '\n', '\n', |
| 1619 | + 'f', 'u', 'n', 'c', ' ', 'H', 'a', 'n', 'd', 'l', 'e', 'R', 'e', 'q', |
| 1620 | + 'u', 'e', 's', 't', '(', ')', ' ', 'e', 'r', 'r', 'o', 'r', ' ', '{', |
| 1621 | + '\n', '\t', '/', '/', ' ', 0xC0, 0xD4, 0xB7, 0xC2, '\n', '\t', 'r', 'e', 't', |
| 1622 | + 'u', 'r', 'n', ' ', 'n', 'i', 'l', '\n', '}', '\n'}; |
| 1623 | + ASSERT_EQ(fwrite(source, 1, sizeof(source), fp), sizeof(source)); |
| 1624 | + ASSERT_EQ(fclose(fp), 0); |
| 1625 | + |
| 1626 | + char *raw = |
| 1627 | + cbm_mcp_handle_tool(srv, "get_code_snippet", |
| 1628 | + "{\"qualified_name\":\"test-project.cmd.server.main.HandleRequest\"," |
| 1629 | + "\"project\":\"test-project\"}"); |
| 1630 | + ASSERT_TRUE(is_valid_json_response(raw)); |
| 1631 | + char *resp = extract_text_content(raw); |
| 1632 | + ASSERT_NOT_NULL(resp); |
| 1633 | + ASSERT_TRUE(is_valid_json_response(resp)); |
| 1634 | + ASSERT_NULL(strstr(resp, "\xC0\xD4")); |
| 1635 | + ASSERT_NOT_NULL(strstr(resp, "HandleRequest")); |
| 1636 | + ASSERT_NOT_NULL(strstr(resp, "return nil")); |
| 1637 | + ASSERT_TRUE(snippet_source_has_replacement(resp)); |
| 1638 | + |
| 1639 | + free(resp); |
| 1640 | + free(raw); |
| 1641 | + cbm_mcp_server_free(srv); |
| 1642 | + cleanup_snippet_dir(tmp); |
| 1643 | + PASS(); |
| 1644 | +} |
| 1645 | + |
1580 | 1646 | /* ══════════════════════════════════════════════════════════════════ |
1581 | 1647 | * JSON-RPC PARSING — EDGE CASES |
1582 | 1648 | * ══════════════════════════════════════════════════════════════════ */ |
@@ -2129,5 +2195,6 @@ SUITE(mcp) { |
2129 | 2195 | RUN_TEST(snippet_auto_resolve_enabled); |
2130 | 2196 | RUN_TEST(snippet_include_neighbors_default); |
2131 | 2197 | RUN_TEST(snippet_include_neighbors_enabled); |
| 2198 | + RUN_TEST(snippet_source_invalid_utf8); |
2132 | 2199 | RUN_TEST(tool_bad_project_name_no_overflow_issue235); |
2133 | 2200 | } |
0 commit comments