1 /* radare - LGPL - Copyright 2009-2020 - pancake, nibble */
2 
3 #include <r_cons.h>
4 #include <r_util/r_print.h>
5 #include <sdb.h>
6 
7 #define I(x) r_cons_singleton ()->x
8 
strchr_ns(char * s,const char ch)9 static char *strchr_ns (char *s, const char ch) {
10 	char *p = strchr (s, ch);
11 	if (p && p > s) {
12 		char *prev = p - 1;
13 		if (*prev == '\\') {
14 			memmove (prev, p, strlen (p) + 1);
15 			return strchr_ns (p, ch);
16 		}
17 	}
18 	return p;
19 }
20 
21 static const char *help_detail_tilde[] = {
22 	"Usage: [command]~[modifier][word,word][endmodifier][[column]][:line]\n"
23 	"modifier:", "", "",
24 	" &",        "", "all words must match to grep the line",
25 	" $[n]",     "", "sort numerically / alphabetically the Nth column",
26 	" $!",       "", "sort in inverse order",
27 	" ,",        "", "token to define another keyword",
28 	" +",        "", "case insensitive grep (grep -i)",
29 	" ^",        "", "words must be placed at the beginning of line",
30 	" <",        "", "perform zoom operation on the buffer",
31 	" !",        "", "negate grep",
32 	" ?",        "", "count number of matching lines",
33 	" ?.",       "", "count number chars",
34 	" ??",       "", "show this help message",
35 	" :s..e",    "", "show lines s-e",
36 	" ..",       "", "internal 'less'",
37 	" ...",      "", "internal 'hud' (like V_)",
38 	" {:",       "", "human friendly indentation (yes, it's a smiley)",
39 	" {:..",     "", "less the output of {:",
40 	" {:...",    "", "hud the output of {:",
41 	" {}",       "", "json indentation",
42 	" {}..",     "", "less json indentation",
43 	" {}...",    "", "hud json indentation",
44 	" {path}",   "", "json path grep",
45 	"endmodifier:", "", "",
46 	" $",        "", "words must be placed at the end of line",
47 	"column:", "", "",
48 	" [n]",      "", "show only column n",
49 	" [n-m]",    "", "show column n to m",
50 	" [n-]",     "", "show all columns starting from column n",
51 	" [i,j,k]",  "", "show the columns i, j and k",
52 	"Examples:", "", "",
53 	" i~:0",     "", "show first line of 'i' output",
54 	" i~:-2",    "", "show the second to last line of 'i' output",
55 	" i~:0..3",  "", "show first three lines of 'i' output",
56 	" pd~mov",   "", "disasm and grep for mov",
57 	" pi~[0]",   "", "show only opcode",
58 	" i~0x400$", "", "show lines ending with 0x400",
59 	NULL
60 };
61 
62 /* TODO: remove globals */
63 static RList *sorted_lines = NULL;
64 static RList *unsorted_lines = NULL;
65 static int sorted_column = -1;
66 
r_cons_grep_help(void)67 R_API void r_cons_grep_help(void) {
68 	r_cons_cmd_help (help_detail_tilde, true);
69 }
70 
71 #define R_CONS_GREP_BUFSIZE 4096
72 
parse_grep_expression(const char * str)73 static void parse_grep_expression(const char *str) {
74 	static char buf[R_CONS_GREP_BUFSIZE];
75 	int wlen, len, is_range, num_is_parsed, fail = 0;
76 	char *ptr, *optr, *ptr2, *ptr3, *end_ptr = NULL, last;
77 	ut64 range_begin, range_end;
78 
79 	if (!str || !*str) {
80 		return;
81 	}
82 	RCons *cons = r_cons_singleton ();
83 	RConsGrep *grep = &cons->context->grep;
84 	sorted_column = 0;
85 	bool first = true;
86 	while (*str) {
87 		switch (*str) {
88 		case '.':
89 			if (str[1] == '.') {
90 				if (str[2] == '.') {
91 					grep->less = 2;
92 				} else {
93 					grep->less = 1;
94 				}
95 				return;
96 			}
97 			str++;
98 			break;
99 		case '{':
100 			if (str[1] == ':') {
101 				grep->human = true; // human friendly indentation ij~{:
102 				grep->json = 1;
103 				if (!strncmp (str, "{:...", 5)) {
104 					grep->hud = true;
105 				} else if (!strncmp (str, "{:..", 4)) {
106 					grep->less = 1;
107 				}
108 			} else if (str[1] == '}') {
109 				// standard json indentation
110 				grep->json = 1;
111 				if (!strncmp (str, "{}...", 5)) {
112 					grep->hud = true;
113 				} else if (!strncmp (str, "{}..", 4)) {
114 					grep->less = 1;
115 				}
116 			} else {
117 				char *jsonPath = strdup (str + 1);
118 				char *jsonPathEnd = strchr (jsonPath, '}');
119 				if (jsonPathEnd) {
120 					*jsonPathEnd = 0;
121 					free (grep->json_path);
122 					grep->json_path = jsonPath;
123 					grep->json = 1;
124 				} else {
125 					free (jsonPath);
126 				}
127 				return;
128 			}
129 			str++;
130 			break;
131 		case '$':
132 			str++;
133 			if (*str == '!') {
134 				grep->sort_invert = true;
135 				str++;
136 			} else {
137 				grep->sort_invert = false;
138 			}
139 			grep->sort = atoi (str);
140 			while (IS_DIGIT (*str)) {
141 				str++;
142 			}
143 			if (*str == ':') {
144 				grep->sort_row = atoi (++str);
145 				str++;
146 			}
147 			break;
148 		case '&':
149 			str++;
150 			grep->amp = 1;
151 			break;
152 		case '<':
153 			grep->zoom = atoi (++str);
154 			//grep->zoomy = atoi (arg);
155 			break;
156 		case '+':
157 			if (first) {
158 				str++;
159 				grep->icase = 1;
160 			} else {
161 				goto while_end;
162 			}
163 			break;
164 		case '^':
165 			str++;
166 			grep->begin = 1;
167 			break;
168 		case '!':
169 			str++;
170 			grep->neg = 1;
171 			break;
172 		case '?':
173 			str++;
174 			grep->counter = 1;
175 			if (*str == '.') {
176 				grep->charCounter = true;
177 				str++;
178 			} else if (*str == '?') {
179 				cons->filter = true;
180 				r_cons_grep_help ();
181 				return;
182 			}
183 			break;
184 		default:
185 			goto while_end;
186 		}
187 		first = false;
188 	}
189 while_end:
190 
191 	len = strlen (str) - 1;
192 	if (len > R_CONS_GREP_BUFSIZE - 1) {
193 		eprintf ("r_cons_grep: too long!\n");
194 		return;
195 	}
196 	if (len > 0 && str[len] == '?') {
197 		grep->counter = 1;
198 		r_str_ncpy (buf, str, R_MIN (len, sizeof (buf) - 1));
199 		buf[len] = 0;
200 		len--;
201 	} else {
202 		r_str_ncpy (buf, str, sizeof (buf) - 1);
203 	}
204 
205 	ptr = buf;
206 	ptr2 = strchr (ptr, '[');
207 	ptr3 = strchr (ptr, ']');
208 	is_range = 0;
209 	num_is_parsed = 0;
210 	fail = 0;
211 	range_begin = range_end = -1;
212 
213 	if (ptr2 && ptr3) {
214 		end_ptr = ptr2;
215 		last = ptr3[1];
216 		ptr3[1] = '\0';
217 		ptr2++;
218 		for (; ptr2 <= ptr3; ptr2++) {
219 			if (fail) {
220 				ZERO_FILL (grep->tokens);
221 				grep->tokens_used = 0;
222 				break;
223 			}
224 			switch (*ptr2) {
225 			case '-':
226 				is_range = 1;
227 				num_is_parsed = 0;
228 				range_end = -1;
229 				break;
230 			case ']':  // fallthrough to handle ']' like ','
231 			case ',':
232 				for (; range_begin <= range_end; range_begin++) {
233 					if (range_begin >= R_CONS_GREP_TOKENS) {
234 						fail = 1;
235 						break;
236 					}
237 					grep->tokens[range_begin] = 1;
238 					grep->tokens_used = 1;
239 				}
240 				// case of [n-]
241 				if (*ptr2 == ']' && is_range && !num_is_parsed) {
242 					num_is_parsed = 1;
243 					range_end = -1;
244 				} else {
245 					is_range = 0;
246 					num_is_parsed = 0;
247 				}
248 				break;
249 			default:
250 				if (!num_is_parsed) {
251 					if (is_range) {
252 						range_end = r_num_get (cons->num, ptr2);
253 						// check for bad value, if range_end == 0, we check if ptr2 == '0'
254 						if (range_end == 0 && *ptr != '0') {
255 							range_end = -1; // this allow [n- ]
256 						}
257 					} else {
258 						range_begin = range_end = r_num_get (cons->num, ptr2);
259 					}
260 					num_is_parsed = 1;
261 				}
262 			}
263 		}
264 		ptr3[1] = last;
265 	}
266 
267 	ptr2 = strchr_ns (ptr, ':'); // line number
268 	grep->range_line = 2; // there is not :
269 	if (ptr2 && ptr2[1] != ':' && ptr2[1] && (IS_DIGIT (ptr2[1]) || ptr2[1] == '-' || ptr2[1] == '.')) {
270 		end_ptr = end_ptr ? R_MIN (end_ptr, ptr2) : ptr2;
271 		char *p, *token = ptr2 + 1;
272 		p = strstr (token, "..");
273 		if (!p) {
274 			grep->line = r_num_get (cons->num, ptr2 + 1);
275 			grep->range_line = 0;
276 		} else {
277 			*p = '\0';
278 			grep->range_line = 1;
279 			if (*token) {
280 				grep->f_line = r_num_get (cons->num, token);
281 			} else {
282 				grep->f_line = 0;
283 			}
284 			if (p[2]) {
285 				grep->l_line = r_num_get (cons->num, p + 2);
286 			} else {
287 				grep->l_line = 0;
288 			}
289 		}
290 	}
291 	if (end_ptr) {
292 		*end_ptr = '\0';
293 	}
294 
295 	len = strlen (buf) - 1;
296 	if (len > 1 && buf[len] == '$' && buf[len - 1] != '\\') {
297 		grep->end = 1;
298 		buf[len] = '\0';
299 	}
300 
301 	free (grep->str);
302 	if (*ptr) {
303 		grep->str = (char *) strdup (ptr);
304 		do {
305 			optr = ptr;
306 			ptr = strchr (ptr, ','); // grep keywords
307 			if (ptr) {
308 				*ptr++ = '\0';
309 			}
310 			wlen = strlen (optr);
311 			if (!wlen) {
312 				continue;
313 			}
314 			if (wlen >= R_CONS_GREP_WORD_SIZE - 1) {
315 				eprintf ("grep string too long\n");
316 				continue;
317 			}
318 			grep->nstrings++;
319 			if (grep->nstrings > R_CONS_GREP_WORDS - 1) {
320 				eprintf ("too many grep strings\n");
321 				break;
322 			}
323 			r_str_ncpy (grep->strings[grep->nstrings - 1],
324 				optr, R_CONS_GREP_WORD_SIZE);
325 		} while (ptr);
326 	} else {
327 		grep->str = strdup (ptr);
328 		grep->nstrings++;
329 		grep->strings[0][0] = 0;
330 	}
331 }
332 
333 // Finds and returns next intgerp expression,
334 // unescapes escaped twiddles
find_next_intgrep(char * cmd,const char * quotes)335 static char *find_next_intgrep(char *cmd, const char *quotes) {
336 	char *p;
337 	do {
338 		p = (char *)r_str_firstbut (cmd, '~', quotes);
339 		if (!p) {
340 			break;
341 		}
342 		if (p == cmd || *(p - 1) != '\\') {
343 			return (char*)p;
344 		}
345 		//twiddle unescape
346 		memmove (p - 1, p, strlen(p) + 1);
347 		cmd = p + 1;
348 	} while (*cmd);
349 	return NULL;
350 }
351 
352 /*
353  * Removes grep part from *cmd* and returns newly allocated string
354  * with reshaped grep expression.
355  *
356  * Function converts multiple twiddle expressions into internal representation.
357  * For example:
358  * converts "~str1~str2~str3~?" into "?&str1,str2,str3"
359  */
preprocess_filter_expr(char * cmd,const char * quotes)360 static char *preprocess_filter_expr(char *cmd, const char *quotes) {
361 	char *p1, *p2, *ns = NULL;
362 	const char *strsep = "&";
363 	int len;
364 	int i;
365 
366 	p1 = find_next_intgrep (cmd, quotes);
367 	if (!p1) {
368 		return NULL;
369 	}
370 
371 	len = strlen (p1);
372 	if (len > 4 && r_str_endswith (p1, "~?") && p1[len - 3] != '\\') {
373 		p1[len - 2] = '\0';
374 		ns = r_str_append (ns, "?");
375 	}
376 
377 	*p1 = '\0'; // remove grep part from cmd
378 
379 	i = 0;
380 	// parse words between '~'
381 	while ((p2 = find_next_intgrep (p1 + 1, quotes))) {
382 		ns = r_str_append (ns, strsep);
383 		ns = r_str_appendlen (ns, p1 + 1, (int)(p2 - p1 - 1));
384 		p1 = p2;
385 		strsep = ",";
386 		i++;
387 	}
388 
389 	if (i > 0) {
390 		ns = r_str_append (ns, ",");
391 	}
392 
393 	ns = r_str_append (ns, p1 + 1);
394 
395 	return ns;
396 }
397 
r_cons_grep_parsecmd(char * cmd,const char * quotestr)398 R_API void r_cons_grep_parsecmd(char *cmd, const char *quotestr) {
399 	r_return_if_fail (cmd && quotestr);
400 	char *ptr = preprocess_filter_expr (cmd, quotestr);
401 	if (ptr) {
402 		r_str_trim (cmd);
403 		parse_grep_expression (ptr);
404 		free (ptr);
405 	}
406 }
407 
r_cons_grep_strip(char * cmd,const char * quotestr)408 R_API char *r_cons_grep_strip(char *cmd, const char *quotestr) {
409 	char *ptr = NULL;
410 
411 	if (cmd) {
412 		ptr = preprocess_filter_expr (cmd, quotestr);
413 		r_str_trim (cmd);
414 	}
415 	return ptr;
416 }
417 
r_cons_grep_process(char * grep)418 R_API void r_cons_grep_process(char * grep) {
419 	if (grep) {
420 		parse_grep_expression (grep);
421 		free (grep);
422 	}
423 }
424 
cmp(const void * a,const void * b)425 static int cmp(const void *a, const void *b) {
426 	char *da = NULL;
427 	char *db = NULL;
428 	const char *ca = r_str_trim_head_ro (a);
429 	const char *cb = r_str_trim_head_ro (b);
430 	if (!a || !b) {
431 		return (int) (size_t) ((char*) a - (char*) b);
432 	}
433 	if (sorted_column > 0) {
434 		da = strdup (ca);
435 		db = strdup (cb);
436 		int colsa = r_str_word_set0 (da);
437 		int colsb = r_str_word_set0 (db);
438 		ca = (colsa > sorted_column)? r_str_word_get0 (da, sorted_column): "";
439 		cb = (colsb > sorted_column)? r_str_word_get0 (db, sorted_column): "";
440 	}
441 	if (IS_DIGIT (*ca) && IS_DIGIT (*cb)) {
442 		ut64 na = r_num_get (NULL, ca);
443 		ut64 nb = r_num_get (NULL, cb);
444 		int ret = (na > nb) - (na < nb);
445 		free (da);
446 		free (db);
447 		return ret;
448 	}
449 	if (da && db) {
450 		int ret = strcmp (ca, cb);
451 		free (da);
452 		free (db);
453 		return ret;
454 	}
455 	free (da);
456 	free (db);
457 	return strcmp (a, b);
458 }
459 
r_cons_grepbuf(void)460 R_API void r_cons_grepbuf(void) {
461 	RCons *cons = r_cons_singleton ();
462 	const char *buf = cons->context->buffer;
463 	const int len = cons->context->buffer_len;
464 	RConsGrep *grep = &cons->context->grep;
465 	const char *in = buf;
466 	int ret, total_lines = 0, buffer_len = 0, l = 0, tl = 0;
467 	bool show = false;
468 	if (cons->filter) {
469 		cons->context->buffer_len = 0;
470 		R_FREE (cons->context->buffer);
471 		return;
472 	}
473 
474 	if ((!len || !buf || buf[0] == '\0') && (grep->json || grep->less)) {
475 		grep->json = 0;
476 		grep->less = 0;
477 		grep->hud = 0;
478 		return;
479 	}
480 
481 	if (grep->zoom) {
482 		char *in = calloc (cons->context->buffer_len + 2, 4);
483 		strcpy (in, cons->context->buffer);
484 		char *out = r_str_scale (in, grep->zoom * 2, grep->zoomy?grep->zoomy:grep->zoom);
485 		if (out) {
486 			free (cons->context->buffer);
487 			cons->context->buffer = out;
488 			cons->context->buffer_len = strlen (out);
489 			cons->context->buffer_sz = cons->context->buffer_len;
490 		}
491 		grep->zoom = 0;
492 		grep->zoomy = 0;
493 		free (in);
494 		return;
495 	}
496 	if (grep->json) {
497 		if (grep->json_path) {
498 			char *u = sdb_json_get_str (cons->context->buffer, grep->json_path);
499 			if (u) {
500 				cons->context->buffer = u;
501 				cons->context->buffer_len = strlen (u);
502 				cons->context->buffer_sz = cons->context->buffer_len + 1;
503 				grep->json = 0;
504 				r_cons_newline ();
505 			}
506 			R_FREE (grep->json_path);
507 		} else {
508 			const char *palette[] = {
509 				cons->context->pal.graph_false, // f
510 				cons->context->pal.graph_true, // t
511 				cons->context->pal.num, // k
512 				cons->context->pal.comment, // v
513 				Color_RESET,
514 				NULL
515 			};
516 			char *bb = strdup (buf);
517 			r_str_ansi_filter (bb, NULL, NULL, -1);
518 			char *out = (cons->context->grep.human)
519 				? r_print_json_human (bb)
520 				: r_print_json_indent (bb, I (context->color_mode), "  ", palette);
521 			free (bb);
522 			if (!out) {
523 				return;
524 			}
525 			free (cons->context->buffer);
526 			cons->context->buffer = out;
527 			cons->context->buffer_len = strlen (out);
528 			cons->context->buffer_sz = cons->context->buffer_len + 1;
529 			grep->json = 0;
530 			if (grep->hud) {
531 				grep->hud = false;
532 				r_cons_hud_string (cons->context->buffer);
533 			} else if (grep->less) {
534 				grep->less = 0;
535 				r_cons_less_str (cons->context->buffer, NULL);
536 			}
537 		}
538 		return;
539 		// cons->lines = ?? return 3;
540 	}
541 	if (grep->less) {
542 		int less = grep->less;
543 		grep->less = 0;
544 		if (less == 2) {
545 			char *res = r_cons_hud_string (buf);
546 			if (res) {
547 				r_cons_println (res);
548 				free (res);
549 			}
550 		} else {
551 			r_cons_less_str (buf, NULL);
552 			cons->context->buffer_len = 0;
553 			if (cons->context->buffer) {
554 				cons->context->buffer[0] = 0;
555 			}
556 			R_FREE (cons->context->buffer);
557 		}
558 		return;
559 	}
560 	if (!cons->context->buffer) {
561 		cons->context->buffer_len = len + 20;
562 		cons->context->buffer = malloc (cons->context->buffer_len);
563 		cons->context->buffer[0] = 0;
564 	}
565 	RStrBuf *ob = r_strbuf_new ("");
566 	// if we modify cons->lines we should update I.context->buffer too
567 	cons->lines = 0;
568 	// used to count lines and change negative grep.line values
569 	while ((int) (size_t) (in - buf) < len) {
570 		char *p = strchr (in, '\n');
571 		if (!p) {
572 			break;
573 		}
574 		l = p - in;
575 		if (l > 0) {
576 			in += l + 1;
577 		} else {
578 			in++;
579 		}
580 		total_lines++;
581 	}
582 	if (!grep->range_line && grep->line < 0) {
583 		grep->line = total_lines + grep->line;
584 	}
585 	if (grep->range_line == 1) {
586 		if (grep->f_line < 0) {
587 			grep->f_line = total_lines + grep->f_line;
588 		}
589 		if (grep->l_line <= 0) {
590 			grep->l_line = total_lines + grep->l_line;
591 		}
592 	}
593 	bool is_range_line_grep_only = grep->range_line != 2 && !*grep->str;
594 	in = buf;
595 	while ((int) (size_t) (in - buf) < len) {
596 		char *p = strchr (in, '\n');
597 		if (!p) {
598 			break;
599 		}
600 		l = p - in;
601 		if ((!l && is_range_line_grep_only) || l > 0) {
602 			char *tline = r_str_ndup (in, l);
603 			if (cons->grep_color) {
604 				tl = l;
605 			} else {
606 				tl = r_str_ansi_filter (tline, NULL, NULL, l);
607 			}
608 			if (tl < 0) {
609 				ret = -1;
610 			} else {
611 				ret = r_cons_grep_line (tline, tl);
612 				if (!grep->range_line) {
613 					if (grep->line == cons->lines) {
614 						show = true;
615 					}
616 				} else if (grep->range_line == 1) {
617 					if (grep->f_line == cons->lines) {
618 						show = true;
619 					}
620 					if (grep->l_line == cons->lines) {
621 						show = false;
622 					}
623 				} else {
624 					show = true;
625 				}
626 			}
627 			if ((!ret && is_range_line_grep_only) || ret > 0) {
628 				if (show) {
629 					char *str = r_str_ndup (tline, ret);
630 					if (cons->grep_highlight) {
631 						int i;
632 						for (i = 0; i < grep->nstrings; i++) {
633 							char *newstr = r_str_newf (Color_INVERT"%s"Color_RESET, grep->strings[i]);
634 							if (str && newstr) {
635 								if (grep->icase) {
636 									str = r_str_replace_icase (str, grep->strings[i], newstr, 1, 1);
637 								} else {
638 									str = r_str_replace (str, grep->strings[i], newstr, 1);
639 								}
640 							}
641 							free (newstr);
642 						}
643 					}
644 					if (str) {
645 						r_strbuf_append (ob, str);
646 						r_strbuf_append (ob, "\n");
647 					}
648 					buffer_len += ret + 1;
649 					free (str);
650 				}
651 				if (!grep->range_line) {
652 					show = false;
653 				}
654 				cons->lines++;
655 			} else if (ret < 0) {
656 				free (tline);
657 				return;
658 			}
659 			free (tline);
660 			in += l + 1;
661 		} else {
662 			in++;
663 		}
664 	}
665 
666 	cons->context->buffer_len = r_strbuf_length (ob);
667 	if (grep->counter) {
668 		int cnt = grep->charCounter? strlen (cons->context->buffer): cons->lines;
669 		if (cons->context->buffer_len < 10) {
670 			cons->context->buffer_len = 10; // HACK
671 		}
672 		snprintf (cons->context->buffer, cons->context->buffer_len, "%d\n", cnt);
673 		cons->context->buffer_len = strlen (cons->context->buffer);
674 		cons->num->value = cons->lines;
675 		r_strbuf_free (ob);
676 		return;
677 	}
678 
679 	const int ob_len = r_strbuf_length (ob);
680 	if (ob_len >= cons->context->buffer_sz) {
681 		cons->context->buffer_sz = ob_len + 1;
682 		cons->context->buffer = r_strbuf_drain (ob);
683 	} else {
684 		memcpy (cons->context->buffer, r_strbuf_getbin (ob, NULL), ob_len);
685 		cons->context->buffer[ob_len] = 0;
686 		r_strbuf_free (ob);
687 	}
688 	cons->context->buffer_len = ob_len;
689 
690 	if (grep->sort != -1) {
691 #define INSERT_LINES(list)\
692 		do {\
693 			r_list_foreach (list, iter, str) {\
694 				int len = strlen (str);\
695 				memcpy (ptr, str, len);\
696 				memcpy (ptr + len, "\n", 2);\
697 				ptr += len + 1;\
698 				nl++;\
699 			}\
700 		}\
701 		while (false)
702 
703 		RListIter *iter;
704 		int nl = 0;
705 		char *ptr = cons->context->buffer;
706 		char *str;
707 		sorted_column = grep->sort;
708 		r_list_sort (sorted_lines, cmp);
709 		if (grep->sort_invert) {
710 			r_list_reverse (sorted_lines);
711 		}
712 		INSERT_LINES (unsorted_lines);
713 		INSERT_LINES (sorted_lines);
714 		cons->lines = nl;
715 		r_list_free (sorted_lines);
716 		sorted_lines = NULL;
717 		r_list_free (unsorted_lines);
718 		unsorted_lines = NULL;
719 	}
720 }
721 
r_cons_grep_line(char * buf,int len)722 R_API int r_cons_grep_line(char *buf, int len) {
723 	RCons *cons = r_cons_singleton ();
724 	RConsGrep *grep = &cons->context->grep;
725 	const char *delims = " |,;=\t";
726 	char *tok = NULL;
727 	bool hit = grep->neg;
728 	int outlen = 0;
729 	bool use_tok = false;
730 	size_t i;
731 
732 	char *in = calloc (1, len + 1);
733 	if (!in) {
734 		return 0;
735 	}
736 	char *out = calloc (1, len + 2);
737 	if (!out) {
738 		free (in);
739 		return 0;
740 	}
741 	memcpy (in, buf, len);
742 
743 	if (grep->nstrings > 0) {
744 		int ampfail = grep->amp;
745 		if (grep->icase) {
746 			r_str_case (in, false);
747 		}
748 		for (i = 0; i < grep->nstrings; i++) {
749 			char *str = grep->strings[i];
750 			if (grep->icase) {
751 				r_str_case (str, false);
752 			}
753 			const char *p = r_strstr_ansi (in, grep->strings[i]);
754 			if (!p) {
755 				ampfail = 0;
756 				continue;
757 			}
758 			if (grep->begin) {
759 				hit = (p == in);
760 			} else {
761 				hit = !grep->neg;
762 			}
763 			// TODO: optimize without strlen without breaking t/feat_grep (grep end)
764 			if (grep->end && (strlen (grep->strings[i]) != strlen (p))) {
765 				hit = 0;
766 			}
767 			if (!grep->amp) {
768 				break;
769 			}
770 		}
771 		if (grep->amp) {
772 			hit = ampfail;
773 		}
774 	} else {
775 		hit = 1;
776 	}
777 
778 	if (hit) {
779 		if (!grep->range_line) {
780 			if (grep->line == cons->lines) {
781 				use_tok = true;
782 			}
783 		} else if (grep->range_line == 1) {
784 			use_tok = R_BETWEEN (grep->f_line, cons->lines, grep->l_line);
785 		} else {
786 			use_tok = true;
787 		}
788 		if (use_tok && grep->tokens_used) {
789 			for (i = 0; i < R_CONS_GREP_TOKENS; i++) {
790 				tok = strtok (i? NULL: in, delims);
791 				if (tok) {
792 					if (grep->tokens[i]) {
793 						int toklen = strlen (tok);
794 						memcpy (out + outlen, tok, toklen);
795 						memcpy (out + outlen + toklen, " ", 2);
796 						outlen += toklen + 1;
797 						if (!(*out)) {
798 							free (in);
799 							free (out);
800 							return -1;
801 						}
802 					}
803 				} else {
804 					if ((*out)) {
805 						break;
806 					}
807 					free (in);
808 					free (out);
809 					return 0;
810 				}
811 			}
812 			outlen = outlen > 0? outlen - 1: 0;
813 			if (outlen > len) { // should never happen
814 				eprintf ("r_cons_grep_line: wtf, how you reach this?\n");
815 				free (in);
816 				free (out);
817 				return -1;
818 			}
819 			memcpy (buf, out, len);
820 			len = outlen;
821 		}
822 	} else {
823 		len = 0;
824 	}
825 	free (in);
826 	free (out);
827 	if (grep->sort != -1) {
828 		char ch = buf[len];
829 		buf[len] = 0;
830 		if (!sorted_lines) {
831 			sorted_lines = r_list_newf (free);
832 		}
833 		if (!unsorted_lines) {
834 			unsorted_lines = r_list_newf (free);
835 		}
836 		if (cons->lines >= grep->sort_row) {
837 			r_list_append (sorted_lines, strdup (buf));
838 		} else {
839 			r_list_append (unsorted_lines, strdup (buf));
840 		}
841 		buf[len] = ch;
842 	}
843 
844 	return len;
845 }
846 
r_cons_grep(const char * grep)847 R_API void r_cons_grep(const char *grep) {
848 	parse_grep_expression (grep);
849 	r_cons_grepbuf ();
850 }
851