|
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 |
@@ -1444,6 +1445,31 @@ static char *call_snippet(cbm_mcp_server_t *srv, const char *args_json) { |
1444 | 1445 | return text; |
1445 | 1446 | } |
1446 | 1447 |
|
| 1448 | +static bool is_valid_json_response(const char *json) { |
| 1449 | + if (!json) { |
| 1450 | + return false; |
| 1451 | + } |
| 1452 | + yyjson_doc *doc = yyjson_read(json, strlen(json), 0); |
| 1453 | + if (!doc) { |
| 1454 | + return false; |
| 1455 | + } |
| 1456 | + yyjson_doc_free(doc); |
| 1457 | + return true; |
| 1458 | +} |
| 1459 | + |
| 1460 | +static bool snippet_source_has_replacement(const char *json) { |
| 1461 | + yyjson_doc *doc = yyjson_read(json, strlen(json), 0); |
| 1462 | + if (!doc) { |
| 1463 | + return false; |
| 1464 | + } |
| 1465 | + yyjson_val *root = yyjson_doc_get_root(doc); |
| 1466 | + yyjson_val *source = yyjson_obj_get(root, "source"); |
| 1467 | + const char *source_str = yyjson_get_str(source); |
| 1468 | + bool found = source_str && strstr(source_str, "\xEF\xBF\xBD"); |
| 1469 | + yyjson_doc_free(doc); |
| 1470 | + return found; |
| 1471 | +} |
| 1472 | + |
1447 | 1473 | /* ── TestSnippet_ExactQN ──────────────────────────────────────── */ |
1448 | 1474 |
|
1449 | 1475 | TEST(snippet_exact_qn) { |
@@ -1730,6 +1756,46 @@ TEST(snippet_include_neighbors_enabled) { |
1730 | 1756 | PASS(); |
1731 | 1757 | } |
1732 | 1758 |
|
| 1759 | +/* ── TestSnippet_SourceInvalidUtf8 ────────────────────────────── */ |
| 1760 | + |
| 1761 | +TEST(snippet_source_invalid_utf8) { |
| 1762 | + char tmp[256]; |
| 1763 | + cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp)); |
| 1764 | + ASSERT_NOT_NULL(srv); |
| 1765 | + |
| 1766 | + char src_path[512]; |
| 1767 | + snprintf(src_path, sizeof(src_path), "%s/project/main.go", tmp); |
| 1768 | + FILE *fp = fopen(src_path, "wb"); |
| 1769 | + ASSERT_NOT_NULL(fp); |
| 1770 | + const unsigned char source[] = { |
| 1771 | + 'p', 'a', 'c', 'k', 'a', 'g', 'e', ' ', 'm', 'a', 'i', 'n', '\n', '\n', |
| 1772 | + 'f', 'u', 'n', 'c', ' ', 'H', 'a', 'n', 'd', 'l', 'e', 'R', 'e', 'q', |
| 1773 | + 'u', 'e', 's', 't', '(', ')', ' ', 'e', 'r', 'r', 'o', 'r', ' ', '{', |
| 1774 | + '\n', '\t', '/', '/', ' ', 0xC0, 0xD4, 0xB7, 0xC2, '\n', '\t', 'r', 'e', 't', |
| 1775 | + 'u', 'r', 'n', ' ', 'n', 'i', 'l', '\n', '}', '\n'}; |
| 1776 | + ASSERT_EQ(fwrite(source, 1, sizeof(source), fp), sizeof(source)); |
| 1777 | + ASSERT_EQ(fclose(fp), 0); |
| 1778 | + |
| 1779 | + char *raw = |
| 1780 | + cbm_mcp_handle_tool(srv, "get_code_snippet", |
| 1781 | + "{\"qualified_name\":\"test-project.cmd.server.main.HandleRequest\"," |
| 1782 | + "\"project\":\"test-project\"}"); |
| 1783 | + ASSERT_TRUE(is_valid_json_response(raw)); |
| 1784 | + char *resp = extract_text_content(raw); |
| 1785 | + ASSERT_NOT_NULL(resp); |
| 1786 | + ASSERT_TRUE(is_valid_json_response(resp)); |
| 1787 | + ASSERT_NULL(strstr(resp, "\xC0\xD4")); |
| 1788 | + ASSERT_NOT_NULL(strstr(resp, "HandleRequest")); |
| 1789 | + ASSERT_NOT_NULL(strstr(resp, "return nil")); |
| 1790 | + ASSERT_TRUE(snippet_source_has_replacement(resp)); |
| 1791 | + |
| 1792 | + free(resp); |
| 1793 | + free(raw); |
| 1794 | + cbm_mcp_server_free(srv); |
| 1795 | + cleanup_snippet_dir(tmp); |
| 1796 | + PASS(); |
| 1797 | +} |
| 1798 | + |
1733 | 1799 | /* ══════════════════════════════════════════════════════════════════ |
1734 | 1800 | * JSON-RPC PARSING — EDGE CASES |
1735 | 1801 | * ══════════════════════════════════════════════════════════════════ */ |
@@ -2285,5 +2351,6 @@ SUITE(mcp) { |
2285 | 2351 | RUN_TEST(snippet_auto_resolve_enabled); |
2286 | 2352 | RUN_TEST(snippet_include_neighbors_default); |
2287 | 2353 | RUN_TEST(snippet_include_neighbors_enabled); |
| 2354 | + RUN_TEST(snippet_source_invalid_utf8); |
2288 | 2355 | RUN_TEST(tool_bad_project_name_no_overflow_issue235); |
2289 | 2356 | } |
0 commit comments