1+ #define _GNU_SOURCE
12#include <stdio.h>
23#include <stdlib.h>
34#include <string.h>
5+ #include <strings.h> // for strcasestr
46#include <pthread.h>
57#include <dirent.h>
68#include <ncurses.h>
1012#define MAX_PATH 512
1113#define MAX_TITLE 512
1214#define MAX_SOURCE 32
13- #define TITLE_WRAP 50 // width for title column
15+ #define TITLE_WRAP 50 // preferred width for title column
1416
1517typedef struct {
1618 char source [MAX_SOURCE ];
@@ -53,16 +55,23 @@ const char *exploitDirs[] = {
5355
5456char search_term [128 ];
5557
56- // Check if all ^-separated parts exist in str
58+
59+ static void trim_newline (char * s ) {
60+ if (!s ) return ;
61+ size_t n = strlen (s );
62+ while (n > 0 && (s [n - 1 ] == '\n' || s [n - 1 ] == '\r' )) { s [n - 1 ] = '\0' ; n -- ; }
63+ }
64+
65+ // Check if all ^-separated parts exist in str (case-insensitive)
5766int matches_search (const char * str , const char * term ) {
67+ if (!str || !term ) return 0 ;
5868 char tmp [128 ];
5969 strncpy (tmp , term , sizeof (tmp ));
6070 tmp [sizeof (tmp )- 1 ] = 0 ;
6171
6272 char * part = strtok (tmp , "^" );
6373 while (part != NULL ) {
64- char * found = strcasestr (str , part );
65- if (!found ) return 0 ;
74+ if (!strcasestr (str , part )) return 0 ;
6675 part = strtok (NULL , "^" );
6776 }
6877 return 1 ;
@@ -83,22 +92,26 @@ void add_result(const char *source, const char *title, const char *path) {
8392 pthread_mutex_unlock (& lock );
8493}
8594
86- // CSV thread
95+
8796void * load_csv_thread (void * arg ) {
8897 const char * path = (const char * )arg ;
8998 FILE * f = fopen (path , "r" );
9099 if (!f ) return NULL ;
91100
92- char line [1024 ];
101+ char line [2048 ];
93102 while (fgets (line , sizeof (line ), f )) {
94103 char * cols [10 ];
95104 int col = 0 ;
96105 char * p = strtok (line , "," );
97106 while (p && col < 10 ) {
107+ while (* p == ' ' || * p == '\t' ) p ++ ;
108+ trim_newline (p );
98109 cols [col ++ ] = p ;
99110 p = strtok (NULL , "," );
100111 }
101112 if (col >= 3 && matches_search (cols [2 ], search_term )) {
113+ trim_newline (cols [1 ]);
114+ trim_newline (cols [2 ]);
102115 add_result ("CSV" , cols [2 ], cols [1 ]);
103116 }
104117 }
@@ -134,22 +147,28 @@ void *scan_dir_thread(void *arg) {
134147 return NULL ;
135148}
136149
137-
138150int wrap_text (const char * text , char lines [][MAX_TITLE ], int width ) {
151+ if (!text ) {
152+ lines [0 ][0 ] = '\0' ;
153+ return 1 ;
154+ }
139155 int len = strlen (text );
156+ if (len == 0 ) {
157+ lines [0 ][0 ] = '\0' ;
158+ return 1 ;
159+ }
140160 int count = 0 ;
141161 int i = 0 ;
142- while (i < len ) {
162+ while (i < len && count < 10 ) {
143163 int chunk = (len - i > width ) ? width : len - i ;
144164 strncpy (lines [count ], text + i , chunk );
145165 lines [count ][chunk ] = '\0' ;
146166 count ++ ;
147167 i += chunk ;
148168 }
149- return count ;
169+ return ( count == 0 ) ? 1 : count ;
150170}
151171
152-
153172void display_results () {
154173 initscr ();
155174 noecho ();
@@ -159,68 +178,127 @@ void display_results() {
159178 if (has_colors ()) {
160179 start_color ();
161180 use_colors = 1 ;
162- // color pairs
163181 init_pair (1 , COLOR_YELLOW , COLOR_BLACK ); // source
164182 init_pair (2 , COLOR_CYAN , COLOR_BLACK ); // title
165183 init_pair (3 , COLOR_GREEN , COLOR_BLACK ); // path
166184 init_pair (4 , COLOR_MAGENTA , COLOR_BLACK ); // header
185+ } else {
186+ use_colors = 0 ;
167187 }
168188
169189 int ch , start = 0 , rows , cols ;
170190 getmaxyx (stdscr , rows , cols );
171191
172- int col_source = 12 ;
173- int col_title = TITLE_WRAP ;
174- int col_path = cols - col_source - col_title - 5 ;
192+
193+ int content_source = 10 ;
194+ int content_title = TITLE_WRAP ;
195+ int content_path = cols - (content_source + content_title + 10 ); // 10 = extra chars from separators/spaces
196+ if (content_path < 10 ) {
197+ // shrink title to make space, but keep minimum widths
198+ content_title = cols - (content_source + 10 + 10 );
199+ if (content_title < 10 ) content_title = 10 ;
200+ content_path = cols - (content_source + content_title + 10 );
201+ if (content_path < 10 ) content_path = 10 ;
202+ }
203+
204+ int x_source_field = 2 ;
205+ int x_title_field = x_source_field + content_source + 3 ;
206+ int x_path_field = x_title_field + content_title + 3 ;
175207
176208 while (1 ) {
177209 clear ();
178210
179- if (use_colors ) attron (COLOR_PAIR (4 ) | A_BOLD );
180- mvprintw (0 , 0 , "| %-10s | %-*s | %-*s |" , "Source" , col_title , "Title" , col_path , "Path" );
181- if (use_colors ) attroff (COLOR_PAIR (4 ) | A_BOLD );
211+ char header_buf [1024 ];
212+ snprintf (header_buf , sizeof (header_buf ),
213+ "| %-*.*s | %-*.*s | %-*.*s |" ,
214+ content_source , content_source , "Source" ,
215+ content_title , content_title , "Title" ,
216+ content_path , content_path , "Path" );
217+
218+ // clear header line then print
219+ move (0 ,0 ); clrtoeol ();
220+ mvprintw (0 , 0 , "%s" , header_buf );
221+ if (use_colors ) {
222+ attron (COLOR_PAIR (4 ) | A_BOLD );
223+ mvprintw (0 , x_source_field , "%-*.*s" , content_source , content_source , "Source" );
224+ mvprintw (0 , x_title_field , "%-*.*s" , content_title , content_title , "Title" );
225+ mvprintw (0 , x_path_field , "%-*.*s" , content_path , content_path , "Path" );
226+ attroff (COLOR_PAIR (4 ) | A_BOLD );
227+ }
182228
229+ // separator line under header
183230 mvhline (1 , 0 , '-' , cols );
184231
185232 int printed_rows = 0 ;
186233 for (int i = start ; i < result_count && printed_rows < rows - 3 ; i ++ ) {
187234 char title_lines [10 ][MAX_TITLE ];
188235 char path_lines [10 ][MAX_PATH ];
189- int title_count = wrap_text (results [i ].title , title_lines , col_title );
190- int path_count = wrap_text (results [i ].path , path_lines , col_path );
191- int max_lines = title_count > path_count ? title_count : path_count ;
236+ int title_count = wrap_text (results [i ].title , title_lines , content_title );
237+ int path_count = wrap_text (results [i ].path , path_lines , content_path );
238+ int max_lines = ( title_count > path_count ) ? title_count : path_count ;
192239
193240 for (int l = 0 ; l < max_lines && printed_rows < rows - 3 ; l ++ ) {
194241 int line_y = printed_rows + 2 ;
195-
196- // Source column
197- if (use_colors ) attron (COLOR_PAIR (1 ));
198- mvprintw (line_y , 0 , "| %-10.10s " , l == 0 ? results [i ].source : "" );
199- if (use_colors ) attroff (COLOR_PAIR (1 ));
200-
201- // Title column
202- if (use_colors ) attron (COLOR_PAIR (2 ));
203- mvprintw (line_y , 13 , "| %-*s " , col_title , l < title_count ? title_lines [l ] : "" );
204- if (use_colors ) attroff (COLOR_PAIR (2 ));
205-
206- // Path column
207- if (use_colors ) attron (COLOR_PAIR (3 ));
208- mvprintw (line_y , 15 + col_title , "| %-*s |" , col_path , l < path_count ? path_lines [l ] : "" );
209- if (use_colors ) attroff (COLOR_PAIR (3 ));
242+ char row_buf [2048 ];
243+
244+ const char * src_display = (l == 0 ) ? results [i ].source : "" ;
245+ const char * title_display = (l < title_count ) ? title_lines [l ] : "" ;
246+ const char * path_display = (l < path_count ) ? path_lines [l ] : "" ;
247+
248+ // build base row string with exact widths
249+ snprintf (row_buf , sizeof (row_buf ),
250+ "| %-*.*s | %-*.*s | %-*.*s |" ,
251+ content_source , content_source , src_display ,
252+ content_title , content_title , title_display ,
253+ content_path , content_path , path_display );
254+
255+ // clear line then print base row
256+ move (line_y , 0 ); clrtoeol ();
257+ mvprintw (line_y , 0 , "%s" , row_buf );
258+
259+ if (use_colors ) {
260+ // Source
261+ attron (COLOR_PAIR (1 ));
262+ mvprintw (line_y , x_source_field , "%-*.*s" , content_source , content_source , src_display );
263+ attroff (COLOR_PAIR (1 ));
264+
265+ // Title
266+ attron (COLOR_PAIR (2 ));
267+ mvprintw (line_y , x_title_field , "%-*.*s" , content_title , content_title , title_display );
268+ attroff (COLOR_PAIR (2 ));
269+
270+ // Path
271+ attron (COLOR_PAIR (3 ));
272+ mvprintw (line_y , x_path_field , "%-*.*s" , content_path , content_path , path_display );
273+ attroff (COLOR_PAIR (3 ));
274+ }
210275
211276 printed_rows ++ ;
212277 }
213278 }
214279
215- mvprintw (rows - 1 , 0 , "Use Up/Down arrows to scroll, q to quit. Showing %d of %d results" ,
216- start + 1 , result_count );
280+ // footer / help line
281+ move (rows - 1 , 0 ); clrtoeol ();
282+ mvprintw (rows - 1 , 0 , "Use Up/Down/PageUp/PageDown arrows to scroll, q to quit. Showing %d of %d results" ,
283+ (result_count == 0 ? 0 : start + 1 ), result_count );
284+
217285 refresh ();
218286
219287 ch = getch ();
220288 if (ch == 'q' ) break ;
221- else if (ch == KEY_DOWN ) { if (start + 1 < result_count ) start ++ ; }
222- else if (ch == KEY_UP ) { if (start > 0 ) start -- ; }
289+ else if (ch == KEY_DOWN ) {
290+ if (start + 1 < result_count ) start ++ ;
291+ } else if (ch == KEY_UP ) {
292+ if (start > 0 ) start -- ;
293+ } else if (ch == KEY_NPAGE ) { // page down
294+ start += (rows - 4 );
295+ if (start >= result_count ) start = (result_count > 0 ) ? result_count - 1 : 0 ;
296+ } else if (ch == KEY_PPAGE ) { // page up
297+ start -= (rows - 4 );
298+ if (start < 0 ) start = 0 ;
299+ }
223300 }
301+
224302 endwin ();
225303}
226304
@@ -240,11 +318,11 @@ int main(int argc, char *argv[]) {
240318 pthread_t threads [64 ];
241319
242320 // CSV threads
243- for (int i = 0 ; i < sizeof (exploitDBPaths )/sizeof (exploitDBPaths [0 ]); i ++ )
321+ for (int i = 0 ; i < ( int )( sizeof (exploitDBPaths )/sizeof (exploitDBPaths [0 ]) ); i ++ )
244322 pthread_create (& threads [num_threads ++ ], NULL , load_csv_thread , (void * )exploitDBPaths [i ]);
245323
246324 // Directory threads
247- for (int i = 0 ; i < sizeof (exploitDirs )/sizeof (exploitDirs [0 ]); i ++ )
325+ for (int i = 0 ; i < ( int )( sizeof (exploitDirs )/sizeof (exploitDirs [0 ]) ); i ++ )
248326 pthread_create (& threads [num_threads ++ ], NULL , scan_dir_thread , (void * )exploitDirs [i ]);
249327
250328 // Join all threads
0 commit comments