Attach source or context lines to a search result JSON item. */
| 4591 | |
| 4592 | /* Attach source or context lines to a search result JSON item. */ |
| 4593 | static void attach_result_source(yyjson_mut_doc *doc, yyjson_mut_val *item, search_result_t *r, |
| 4594 | int mode, int context_lines, const char *root_path) { |
| 4595 | enum { MODE_FULL = 1 }; |
| 4596 | if (r->start_line <= 0 || r->end_line <= 0) { |
| 4597 | return; |
| 4598 | } |
| 4599 | char abs_path[CBM_SZ_1K]; |
| 4600 | snprintf(abs_path, sizeof(abs_path), "%s/%s", root_path, r->file); |
| 4601 | |
| 4602 | /* Containment: a search result whose indexed path resolves outside the |
| 4603 | * project root (a `..` segment, or a symlink/junction that discovery |
| 4604 | * followed) must not be read back into the response. Same guard the |
| 4605 | * snippet path already uses. */ |
| 4606 | if (!cbm_path_within_root(root_path, abs_path)) { |
| 4607 | return; |
| 4608 | } |
| 4609 | |
| 4610 | if (mode == MODE_FULL) { |
| 4611 | char *source = read_file_lines(abs_path, r->start_line, r->end_line); |
| 4612 | if (source) { |
| 4613 | sanitize_ascii(source); |
| 4614 | yyjson_mut_obj_add_strcpy(doc, item, "source", source); |
| 4615 | free(source); |
| 4616 | } |
| 4617 | } else if (context_lines > 0 && r->match_count > 0) { |
| 4618 | int ctx_start = r->match_lines[0] - context_lines; |
| 4619 | int ctx_end = r->match_lines[r->match_count - SKIP_ONE] + context_lines; |
| 4620 | if (ctx_start < SKIP_ONE) { |
| 4621 | ctx_start = SKIP_ONE; |
| 4622 | } |
| 4623 | char *ctx = read_file_lines(abs_path, ctx_start, ctx_end); |
| 4624 | if (ctx) { |
| 4625 | sanitize_ascii(ctx); |
| 4626 | yyjson_mut_obj_add_strcpy(doc, item, "context", ctx); |
| 4627 | yyjson_mut_obj_add_int(doc, item, "context_start", ctx_start); |
| 4628 | free(ctx); |
| 4629 | } |
| 4630 | } |
| 4631 | } |
| 4632 | |
| 4633 | /* Build directory distribution object from search results (top-level dir → count). */ |
| 4634 | static yyjson_mut_val *build_dir_distribution(yyjson_mut_doc *doc, search_result_t *sr, |
no test coverage detected