Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <errno.h>
4 : : #include <stdarg.h>
5 : : #include <stdint.h>
6 : : #include <stdio.h>
7 : : #include <stdlib.h>
8 : : #include <string.h>
9 : :
10 : : #include "alloc-util.h"
11 : : #include "escape.h"
12 : : #include "fileio.h"
13 : : #include "gunicode.h"
14 : : #include "locale-util.h"
15 : : #include "macro.h"
16 : : #include "memory-util.h"
17 : : #include "string-util.h"
18 : : #include "terminal-util.h"
19 : : #include "utf8.h"
20 : : #include "util.h"
21 : :
22 : 4910137 : int strcmp_ptr(const char *a, const char *b) {
23 : :
24 : : /* Like strcmp(), but tries to make sense of NULL pointers */
25 [ + + + + ]: 4910137 : if (a && b)
26 : 4761407 : return strcmp(a, b);
27 : :
28 [ + + + + ]: 148730 : if (!a && b)
29 : 111422 : return -1;
30 : :
31 [ + + + - ]: 37308 : if (a && !b)
32 : 36360 : return 1;
33 : :
34 : 948 : return 0;
35 : : }
36 : :
37 : 5089043 : char* endswith(const char *s, const char *postfix) {
38 : : size_t sl, pl;
39 : :
40 [ - + ]: 5089043 : assert(s);
41 [ - + ]: 5089043 : assert(postfix);
42 : :
43 : 5089043 : sl = strlen(s);
44 : 5089043 : pl = strlen(postfix);
45 : :
46 [ + + ]: 5089043 : if (pl == 0)
47 : 8 : return (char*) s + sl;
48 : :
49 [ + + ]: 5089035 : if (sl < pl)
50 : 372 : return NULL;
51 : :
52 [ + + ]: 5088663 : if (memcmp(s + sl - pl, postfix, pl) != 0)
53 : 4973907 : return NULL;
54 : :
55 : 114756 : return (char*) s + sl - pl;
56 : : }
57 : :
58 : 6652 : char* endswith_no_case(const char *s, const char *postfix) {
59 : : size_t sl, pl;
60 : :
61 [ - + ]: 6652 : assert(s);
62 [ - + ]: 6652 : assert(postfix);
63 : :
64 : 6652 : sl = strlen(s);
65 : 6652 : pl = strlen(postfix);
66 : :
67 [ + + ]: 6652 : if (pl == 0)
68 : 8 : return (char*) s + sl;
69 : :
70 [ + + ]: 6644 : if (sl < pl)
71 : 504 : return NULL;
72 : :
73 [ + + ]: 6140 : if (strcasecmp(s + sl - pl, postfix) != 0)
74 : 3682 : return NULL;
75 : :
76 : 2458 : return (char*) s + sl - pl;
77 : : }
78 : :
79 : 28912 : char* first_word(const char *s, const char *word) {
80 : : size_t sl, wl;
81 : : const char *p;
82 : :
83 [ - + ]: 28912 : assert(s);
84 [ - + ]: 28912 : assert(word);
85 : :
86 : : /* Checks if the string starts with the specified word, either
87 : : * followed by NUL or by whitespace. Returns a pointer to the
88 : : * NUL or the first character after the whitespace. */
89 : :
90 : 28912 : sl = strlen(s);
91 : 28912 : wl = strlen(word);
92 : :
93 [ + + ]: 28912 : if (sl < wl)
94 : 2656 : return NULL;
95 : :
96 [ + + ]: 26256 : if (wl == 0)
97 : 4 : return (char*) s;
98 : :
99 [ + + ]: 26252 : if (memcmp(s, word, wl) != 0)
100 : 26140 : return NULL;
101 : :
102 : 112 : p = s + wl;
103 [ + + ]: 112 : if (*p == 0)
104 : 4 : return (char*) p;
105 : :
106 [ + + ]: 108 : if (!strchr(WHITESPACE, *p))
107 : 4 : return NULL;
108 : :
109 : 104 : p += strspn(p, WHITESPACE);
110 : 104 : return (char*) p;
111 : : }
112 : :
113 : 128 : static size_t strcspn_escaped(const char *s, const char *reject) {
114 : 128 : bool escaped = false;
115 : : int n;
116 : :
117 [ + + ]: 1344 : for (n=0; s[n]; n++) {
118 [ - + ]: 1320 : if (escaped)
119 : 0 : escaped = false;
120 [ - + ]: 1320 : else if (s[n] == '\\')
121 : 0 : escaped = true;
122 [ + + ]: 1320 : else if (strchr(reject, s[n]))
123 : 104 : break;
124 : : }
125 : :
126 : : /* if s ends in \, return index of previous char */
127 : 128 : return n - escaped;
128 : : }
129 : :
130 : : /* Split a string into words. */
131 : 9080 : const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags) {
132 : : const char *current;
133 : :
134 : 9080 : current = *state;
135 : :
136 [ + + ]: 9080 : if (!*current) {
137 [ - + ]: 2384 : assert(**state == '\0');
138 : 2384 : return NULL;
139 : : }
140 : :
141 : 6696 : current += strspn(current, separator);
142 [ + + ]: 6696 : if (!*current) {
143 : 60 : *state = current;
144 : 60 : return NULL;
145 : : }
146 : :
147 [ + + + - ]: 6636 : if (flags & SPLIT_QUOTES && strchr("\'\"", *current)) {
148 : 128 : char quotechars[2] = {*current, '\0'};
149 : :
150 : 128 : *l = strcspn_escaped(current + 1, quotechars);
151 [ + + + - ]: 128 : if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
152 [ + + + + ]: 104 : (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
153 : : /* right quote missing or garbage at the end */
154 [ + + ]: 40 : if (flags & SPLIT_RELAX) {
155 : 32 : *state = current + *l + 1 + (current[*l + 1] != '\0');
156 : 40 : return current + 1;
157 : : }
158 : 8 : *state = current;
159 : 8 : return NULL;
160 : : }
161 : 88 : *state = current++ + *l + 2;
162 [ - + ]: 6508 : } else if (flags & SPLIT_QUOTES) {
163 : 0 : *l = strcspn_escaped(current, separator);
164 [ # # # # : 0 : if (current[*l] && !strchr(separator, current[*l]) && !(flags & SPLIT_RELAX)) {
# # ]
165 : : /* unfinished escape */
166 : 0 : *state = current;
167 : 0 : return NULL;
168 : : }
169 : 0 : *state = current + *l;
170 : : } else {
171 : 6508 : *l = strcspn(current, separator);
172 : 6508 : *state = current + *l;
173 : : }
174 : :
175 : 6596 : return current;
176 : : }
177 : :
178 : 588 : char *strnappend(const char *s, const char *suffix, size_t b) {
179 : : size_t a;
180 : : char *r;
181 : :
182 [ + + - + ]: 588 : if (!s && !suffix)
183 : 0 : return strdup("");
184 : :
185 [ + + ]: 588 : if (!s)
186 : 260 : return strndup(suffix, b);
187 : :
188 [ - + ]: 328 : if (!suffix)
189 : 0 : return strdup(s);
190 : :
191 [ - + ]: 328 : assert(s);
192 [ - + ]: 328 : assert(suffix);
193 : :
194 : 328 : a = strlen(s);
195 [ - + ]: 328 : if (b > ((size_t) -1) - a)
196 : 0 : return NULL;
197 : :
198 : 328 : r = new(char, a+b+1);
199 [ - + ]: 328 : if (!r)
200 : 0 : return NULL;
201 : :
202 : 328 : memcpy(r, s, a);
203 : 328 : memcpy(r+a, suffix, b);
204 : 328 : r[a+b] = 0;
205 : :
206 : 328 : return r;
207 : : }
208 : :
209 : 93444 : char *strjoin_real(const char *x, ...) {
210 : : va_list ap;
211 : : size_t l;
212 : : char *r, *p;
213 : :
214 : 93444 : va_start(ap, x);
215 : :
216 [ + + ]: 93444 : if (x) {
217 : 93364 : l = strlen(x);
218 : :
219 : 172544 : for (;;) {
220 : : const char *t;
221 : : size_t n;
222 : :
223 : 265908 : t = va_arg(ap, const char *);
224 [ + + ]: 265908 : if (!t)
225 : 93364 : break;
226 : :
227 : 172544 : n = strlen(t);
228 [ - + ]: 172544 : if (n > ((size_t) -1) - l) {
229 : 0 : va_end(ap);
230 : 0 : return NULL;
231 : : }
232 : :
233 : 172544 : l += n;
234 : : }
235 : : } else
236 : 80 : l = 0;
237 : :
238 : 93444 : va_end(ap);
239 : :
240 : 93444 : r = new(char, l+1);
241 [ - + ]: 93444 : if (!r)
242 : 0 : return NULL;
243 : :
244 [ + + ]: 93444 : if (x) {
245 : 93364 : p = stpcpy(r, x);
246 : :
247 : 93364 : va_start(ap, x);
248 : :
249 : 172544 : for (;;) {
250 : : const char *t;
251 : :
252 : 265908 : t = va_arg(ap, const char *);
253 [ + + ]: 265908 : if (!t)
254 : 93364 : break;
255 : :
256 : 172544 : p = stpcpy(p, t);
257 : : }
258 : :
259 : 93364 : va_end(ap);
260 : : } else
261 : 80 : r[0] = 0;
262 : :
263 : 93444 : return r;
264 : : }
265 : :
266 : 85024 : char *strstrip(char *s) {
267 [ - + ]: 85024 : if (!s)
268 : 0 : return NULL;
269 : :
270 : : /* Drops trailing whitespace. Modifies the string in place. Returns pointer to first non-space character */
271 : :
272 : 85024 : return delete_trailing_chars(skip_leading_chars(s, WHITESPACE), WHITESPACE);
273 : : }
274 : :
275 : 36 : char *delete_chars(char *s, const char *bad) {
276 : : char *f, *t;
277 : :
278 : : /* Drops all specified bad characters, regardless where in the string */
279 : :
280 [ - + ]: 36 : if (!s)
281 : 0 : return NULL;
282 : :
283 [ - + ]: 36 : if (!bad)
284 : 0 : bad = WHITESPACE;
285 : :
286 [ + + ]: 524 : for (f = s, t = s; *f; f++) {
287 [ + + ]: 488 : if (strchr(bad, *f))
288 : 268 : continue;
289 : :
290 : 220 : *(t++) = *f;
291 : : }
292 : :
293 : 36 : *t = 0;
294 : :
295 : 36 : return s;
296 : : }
297 : :
298 : 95616 : char *delete_trailing_chars(char *s, const char *bad) {
299 : 95616 : char *p, *c = s;
300 : :
301 : : /* Drops all specified bad characters, at the end of the string */
302 : :
303 [ - + ]: 95616 : if (!s)
304 : 0 : return NULL;
305 : :
306 [ - + ]: 95616 : if (!bad)
307 : 0 : bad = WHITESPACE;
308 : :
309 [ + + ]: 18987212 : for (p = s; *p; p++)
310 [ + + ]: 18891596 : if (!strchr(bad, *p))
311 : 18784960 : c = p + 1;
312 : :
313 : 95616 : *c = 0;
314 : :
315 : 95616 : return s;
316 : : }
317 : :
318 : 8 : char *truncate_nl(char *s) {
319 [ - + ]: 8 : assert(s);
320 : :
321 : 8 : s[strcspn(s, NEWLINE)] = 0;
322 : 8 : return s;
323 : : }
324 : :
325 : 110550 : char ascii_tolower(char x) {
326 : :
327 [ + + + + ]: 110550 : if (x >= 'A' && x <= 'Z')
328 : 1632 : return x - 'A' + 'a';
329 : :
330 : 108918 : return x;
331 : : }
332 : :
333 : 0 : char ascii_toupper(char x) {
334 : :
335 [ # # # # ]: 0 : if (x >= 'a' && x <= 'z')
336 : 0 : return x - 'a' + 'A';
337 : :
338 : 0 : return x;
339 : : }
340 : :
341 : 72 : char *ascii_strlower(char *t) {
342 : : char *p;
343 : :
344 [ - + ]: 72 : assert(t);
345 : :
346 [ + + ]: 440 : for (p = t; *p; p++)
347 : 368 : *p = ascii_tolower(*p);
348 : :
349 : 72 : return t;
350 : : }
351 : :
352 : 0 : char *ascii_strupper(char *t) {
353 : : char *p;
354 : :
355 [ # # ]: 0 : assert(t);
356 : :
357 [ # # ]: 0 : for (p = t; *p; p++)
358 : 0 : *p = ascii_toupper(*p);
359 : :
360 : 0 : return t;
361 : : }
362 : :
363 : 8272 : char *ascii_strlower_n(char *t, size_t n) {
364 : : size_t i;
365 : :
366 [ + + ]: 8272 : if (n <= 0)
367 : 44 : return t;
368 : :
369 [ + + ]: 57040 : for (i = 0; i < n; i++)
370 : 48812 : t[i] = ascii_tolower(t[i]);
371 : :
372 : 8228 : return t;
373 : : }
374 : :
375 : 5635 : int ascii_strcasecmp_n(const char *a, const char *b, size_t n) {
376 : :
377 [ + + ]: 32825 : for (; n > 0; a++, b++, n--) {
378 : : int x, y;
379 : :
380 : 27655 : x = (int) (uint8_t) ascii_tolower(*a);
381 : 27655 : y = (int) (uint8_t) ascii_tolower(*b);
382 : :
383 [ + + ]: 27655 : if (x != y)
384 : 465 : return x - y;
385 : : }
386 : :
387 : 5170 : return 0;
388 : : }
389 : :
390 : 1151 : int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) {
391 : : int r;
392 : :
393 : 1151 : r = ascii_strcasecmp_n(a, b, MIN(n, m));
394 [ + + ]: 1151 : if (r != 0)
395 : 277 : return r;
396 : :
397 [ + + ]: 874 : return CMP(n, m);
398 : : }
399 : :
400 : 44 : bool chars_intersect(const char *a, const char *b) {
401 : : const char *p;
402 : :
403 : : /* Returns true if any of the chars in a are in b. */
404 [ + + ]: 224 : for (p = a; *p; p++)
405 [ + + ]: 204 : if (strchr(b, *p))
406 : 24 : return true;
407 : :
408 : 20 : return false;
409 : : }
410 : :
411 : 400 : bool string_has_cc(const char *p, const char *ok) {
412 : : const char *t;
413 : :
414 [ - + ]: 400 : assert(p);
415 : :
416 : : /*
417 : : * Check if a string contains control characters. If 'ok' is
418 : : * non-NULL it may be a string containing additional CCs to be
419 : : * considered OK.
420 : : */
421 : :
422 [ + + ]: 3508 : for (t = p; *t; t++) {
423 [ + + + + ]: 3164 : if (ok && strchr(ok, *t))
424 : 64 : continue;
425 : :
426 [ + + + + ]: 3100 : if (*t > 0 && *t < ' ')
427 : 32 : return true;
428 : :
429 [ + + ]: 3068 : if (*t == 127)
430 : 24 : return true;
431 : : }
432 : :
433 : 344 : return false;
434 : : }
435 : :
436 : 3196 : static int write_ellipsis(char *buf, bool unicode) {
437 [ + + + - ]: 3196 : if (unicode || is_locale_utf8()) {
438 : 3196 : buf[0] = 0xe2; /* tri-dot ellipsis: … */
439 : 3196 : buf[1] = 0x80;
440 : 3196 : buf[2] = 0xa6;
441 : : } else {
442 : 0 : buf[0] = '.';
443 : 0 : buf[1] = '.';
444 : 0 : buf[2] = '.';
445 : : }
446 : :
447 : 3196 : return 3;
448 : : }
449 : :
450 : 4108 : static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
451 : : size_t x, need_space, suffix_len;
452 : : char *t;
453 : :
454 [ - + ]: 4108 : assert(s);
455 [ - + ]: 4108 : assert(percent <= 100);
456 [ - + ]: 4108 : assert(new_length != (size_t) -1);
457 : :
458 [ + + ]: 4108 : if (old_length <= new_length)
459 : 3000 : return strndup(s, old_length);
460 : :
461 : : /* Special case short ellipsations */
462 [ - + + + ]: 1108 : switch (new_length) {
463 : :
464 : 0 : case 0:
465 : 0 : return strdup("");
466 : :
467 : 236 : case 1:
468 [ + - ]: 236 : if (is_locale_utf8())
469 : 236 : return strdup("…");
470 : : else
471 : 0 : return strdup(".");
472 : :
473 : 140 : case 2:
474 [ - + ]: 140 : if (!is_locale_utf8())
475 : 0 : return strdup("..");
476 : :
477 : 140 : break;
478 : :
479 : 732 : default:
480 : 732 : break;
481 : : }
482 : :
483 : : /* Calculate how much space the ellipsis will take up. If we are in UTF-8 mode we only need space for one
484 : : * character ("…"), otherwise for three characters ("..."). Note that in both cases we need 3 bytes of storage,
485 : : * either for the UTF-8 encoded character or for three ASCII characters. */
486 [ + - ]: 872 : need_space = is_locale_utf8() ? 1 : 3;
487 : :
488 : 872 : t = new(char, new_length+3);
489 [ - + ]: 872 : if (!t)
490 : 0 : return NULL;
491 : :
492 [ - + ]: 872 : assert(new_length >= need_space);
493 : :
494 : 872 : x = ((new_length - need_space) * percent + 50) / 100;
495 [ - + ]: 872 : assert(x <= new_length - need_space);
496 : :
497 : 872 : memcpy(t, s, x);
498 : 872 : write_ellipsis(t + x, false);
499 : 872 : suffix_len = new_length - x - need_space;
500 : 872 : memcpy(t + x + 3, s + old_length - suffix_len, suffix_len);
501 : 872 : *(t + x + 3 + suffix_len) = '\0';
502 : :
503 : 872 : return t;
504 : : }
505 : :
506 : 13692 : char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
507 : : size_t x, k, len, len2;
508 : : const char *i, *j;
509 : : char *e;
510 : : int r;
511 : :
512 : : /* Note that 'old_length' refers to bytes in the string, while 'new_length' refers to character cells taken up
513 : : * on screen. This distinction doesn't matter for ASCII strings, but it does matter for non-ASCII UTF-8
514 : : * strings.
515 : : *
516 : : * Ellipsation is done in a locale-dependent way:
517 : : * 1. If the string passed in is fully ASCII and the current locale is not UTF-8, three dots are used ("...")
518 : : * 2. Otherwise, a unicode ellipsis is used ("…")
519 : : *
520 : : * In other words: you'll get a unicode ellipsis as soon as either the string contains non-ASCII characters or
521 : : * the current locale is UTF-8.
522 : : */
523 : :
524 [ - + ]: 13692 : assert(s);
525 [ - + ]: 13692 : assert(percent <= 100);
526 : :
527 [ - + ]: 13692 : if (new_length == (size_t) -1)
528 : 0 : return strndup(s, old_length);
529 : :
530 [ + + ]: 13692 : if (new_length == 0)
531 : 656 : return strdup("");
532 : :
533 : : /* If no multibyte characters use ascii_ellipsize_mem for speed */
534 [ + + ]: 13036 : if (ascii_is_valid_n(s, old_length))
535 : 4108 : return ascii_ellipsize_mem(s, old_length, new_length, percent);
536 : :
537 : 8928 : x = ((new_length - 1) * percent) / 100;
538 [ - + ]: 8928 : assert(x <= new_length - 1);
539 : :
540 : 8928 : k = 0;
541 [ + + ]: 43756 : for (i = s; i < s + old_length; i = utf8_next_char(i)) {
542 : : char32_t c;
543 : : int w;
544 : :
545 : 39012 : r = utf8_encoded_to_unichar(i, &c);
546 [ - + ]: 39012 : if (r < 0)
547 : 0 : return NULL;
548 : :
549 [ + + ]: 39012 : w = unichar_iswide(c) ? 2 : 1;
550 [ + + ]: 39012 : if (k + w <= x)
551 : 34828 : k += w;
552 : : else
553 : 4184 : break;
554 : : }
555 : :
556 [ + + ]: 20256 : for (j = s + old_length; j > i; ) {
557 : : char32_t c;
558 : : int w;
559 : : const char *jj;
560 : :
561 : 13556 : jj = utf8_prev_char(j);
562 : 13556 : r = utf8_encoded_to_unichar(jj, &c);
563 [ - + ]: 13556 : if (r < 0)
564 : 0 : return NULL;
565 : :
566 [ + + ]: 13556 : w = unichar_iswide(c) ? 2 : 1;
567 [ + + ]: 13556 : if (k + w <= new_length) {
568 : 11328 : k += w;
569 : 11328 : j = jj;
570 : : } else
571 : 2228 : break;
572 : : }
573 [ - + ]: 8928 : assert(i <= j);
574 : :
575 : : /* we don't actually need to ellipsize */
576 [ + + ]: 8928 : if (i == j)
577 : 6700 : return memdup_suffix0(s, old_length);
578 : :
579 : : /* make space for ellipsis, if possible */
580 [ + + ]: 2228 : if (j < s + old_length)
581 : 2060 : j = utf8_next_char(j);
582 [ + + ]: 168 : else if (i > s)
583 : 68 : i = utf8_prev_char(i);
584 : :
585 : 2228 : len = i - s;
586 : 2228 : len2 = s + old_length - j;
587 : 2228 : e = new(char, len + 3 + len2 + 1);
588 [ - + ]: 2228 : if (!e)
589 : 0 : return NULL;
590 : :
591 : : /*
592 : : printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n",
593 : : old_length, new_length, x, len, len2, k);
594 : : */
595 : :
596 : 2228 : memcpy(e, s, len);
597 : 2228 : write_ellipsis(e + len, true);
598 : 2228 : memcpy(e + len + 3, j, len2);
599 : 2228 : *(e + len + 3 + len2) = '\0';
600 : :
601 : 2228 : return e;
602 : : }
603 : :
604 : 4775 : char *cellescape(char *buf, size_t len, const char *s) {
605 : : /* Escape and ellipsize s into buffer buf of size len. Only non-control ASCII
606 : : * characters are copied as they are, everything else is escaped. The result
607 : : * is different then if escaping and ellipsization was performed in two
608 : : * separate steps, because each sequence is either stored in full or skipped.
609 : : *
610 : : * This function should be used for logging about strings which expected to
611 : : * be plain ASCII in a safe way.
612 : : *
613 : : * An ellipsis will be used if s is too long. It was always placed at the
614 : : * very end.
615 : : */
616 : :
617 : 4775 : size_t i = 0, last_char_width[4] = {}, k = 0, j;
618 : :
619 [ - + ]: 4775 : assert(len > 0); /* at least a terminating NUL */
620 : :
621 : 47262 : for (;;) {
622 : : char four[4];
623 : : int w;
624 : :
625 [ + + ]: 52037 : if (*s == 0) /* terminating NUL detected? then we are done! */
626 : 4631 : goto done;
627 : :
628 : 47406 : w = cescape_char(*s, four);
629 [ + + ]: 47406 : if (i + w + 1 > len) /* This character doesn't fit into the buffer anymore? In that case let's
630 : : * ellipsize at the previous location */
631 : 144 : break;
632 : :
633 : : /* OK, there was space, let's add this escaped character to the buffer */
634 : 47262 : memcpy(buf + i, four, w);
635 : 47262 : i += w;
636 : :
637 : : /* And remember its width in the ring buffer */
638 : 47262 : last_char_width[k] = w;
639 : 47262 : k = (k + 1) % 4;
640 : :
641 : 47262 : s++;
642 : : }
643 : :
644 : : /* Ellipsation is necessary. This means we might need to truncate the string again to make space for 4
645 : : * characters ideally, but the buffer is shorter than that in the first place take what we can get */
646 [ + - ]: 324 : for (j = 0; j < ELEMENTSOF(last_char_width); j++) {
647 : :
648 [ + + ]: 324 : if (i + 4 <= len) /* nice, we reached our space goal */
649 : 96 : break;
650 : :
651 [ + + ]: 228 : k = k == 0 ? 3 : k - 1;
652 [ + + ]: 228 : if (last_char_width[k] == 0) /* bummer, we reached the beginning of the strings */
653 : 48 : break;
654 : :
655 [ - + ]: 180 : assert(i >= last_char_width[k]);
656 : 180 : i -= last_char_width[k];
657 : : }
658 : :
659 [ + + ]: 144 : if (i + 4 <= len) /* yay, enough space */
660 : 96 : i += write_ellipsis(buf + i, false);
661 [ + + ]: 48 : else if (i + 3 <= len) { /* only space for ".." */
662 : 16 : buf[i++] = '.';
663 : 16 : buf[i++] = '.';
664 [ + + ]: 32 : } else if (i + 2 <= len) /* only space for a single "." */
665 : 16 : buf[i++] = '.';
666 : : else
667 [ - + ]: 16 : assert(i + 1 <= len);
668 : :
669 : 16 : done:
670 : 4775 : buf[i] = '\0';
671 : 4775 : return buf;
672 : : }
673 : :
674 : 32 : char* strshorten(char *s, size_t l) {
675 [ - + ]: 32 : assert(s);
676 : :
677 [ + + ]: 32 : if (strnlen(s, l+1) > l)
678 : 16 : s[l] = 0;
679 : :
680 : 32 : return s;
681 : : }
682 : :
683 : 272 : char *strreplace(const char *text, const char *old_string, const char *new_string) {
684 : 272 : size_t l, old_len, new_len, allocated = 0;
685 : 272 : char *t, *ret = NULL;
686 : : const char *f;
687 : :
688 [ - + ]: 272 : assert(old_string);
689 [ - + ]: 272 : assert(new_string);
690 : :
691 [ + + ]: 272 : if (!text)
692 : 4 : return NULL;
693 : :
694 : 268 : old_len = strlen(old_string);
695 : 268 : new_len = strlen(new_string);
696 : :
697 : 268 : l = strlen(text);
698 [ - + ]: 268 : if (!GREEDY_REALLOC(ret, allocated, l+1))
699 : 0 : return NULL;
700 : :
701 : 268 : f = text;
702 : 268 : t = ret;
703 [ + + ]: 6496 : while (*f) {
704 : : size_t d, nl;
705 : :
706 [ + + ]: 6228 : if (!startswith(f, old_string)) {
707 : 6136 : *(t++) = *(f++);
708 : 6136 : continue;
709 : : }
710 : :
711 : 92 : d = t - ret;
712 : 92 : nl = l - old_len + new_len;
713 : :
714 [ - + ]: 92 : if (!GREEDY_REALLOC(ret, allocated, nl + 1))
715 : 0 : return mfree(ret);
716 : :
717 : 92 : l = nl;
718 : 92 : t = ret + d;
719 : :
720 : 92 : t = stpcpy(t, new_string);
721 : 92 : f += old_len;
722 : : }
723 : :
724 : 268 : *t = 0;
725 : 268 : return ret;
726 : : }
727 : :
728 : 40 : static void advance_offsets(
729 : : ssize_t diff,
730 : : size_t offsets[2], /* note: we can't use [static 2] here, since this may be NULL */
731 : : size_t shift[static 2],
732 : : size_t size) {
733 : :
734 [ + - ]: 40 : if (!offsets)
735 : 40 : return;
736 : :
737 [ # # ]: 0 : assert(shift);
738 : :
739 [ # # ]: 0 : if ((size_t) diff < offsets[0])
740 : 0 : shift[0] += size;
741 [ # # ]: 0 : if ((size_t) diff < offsets[1])
742 : 0 : shift[1] += size;
743 : : }
744 : :
745 : 20 : char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
746 : 20 : const char *i, *begin = NULL;
747 : : enum {
748 : : STATE_OTHER,
749 : : STATE_ESCAPE,
750 : : STATE_CSI,
751 : : STATE_CSO,
752 : 20 : } state = STATE_OTHER;
753 : 20 : char *obuf = NULL;
754 : 20 : size_t osz = 0, isz, shift[2] = {};
755 : : FILE *f;
756 : :
757 [ - + ]: 20 : assert(ibuf);
758 [ - + ]: 20 : assert(*ibuf);
759 : :
760 : : /* This does three things:
761 : : *
762 : : * 1. Replaces TABs by 8 spaces
763 : : * 2. Strips ANSI color sequences (a subset of CSI), i.e. ESC '[' … 'm' sequences
764 : : * 3. Strips ANSI operating system sequences (CSO), i.e. ESC ']' … BEL sequences
765 : : *
766 : : * Everything else will be left as it is. In particular other ANSI sequences are left as they are, as
767 : : * are any other special characters. Truncated ANSI sequences are left-as is too. This call is
768 : : * supposed to suppress the most basic formatting noise, but nothing else.
769 : : *
770 : : * Why care for CSO sequences? Well, to undo what terminal_urlify() and friends generate. */
771 : :
772 [ - + ]: 20 : isz = _isz ? *_isz : strlen(*ibuf);
773 : :
774 : : /* Note we turn off internal locking on f for performance reasons. It's safe to do so since we
775 : : * created f here and it doesn't leave our scope. */
776 : 20 : f = open_memstream_unlocked(&obuf, &osz);
777 [ - + ]: 20 : if (!f)
778 : 0 : return NULL;
779 : :
780 [ + + ]: 680 : for (i = *ibuf; i < *ibuf + isz + 1; i++) {
781 : :
782 [ + + + - : 660 : switch (state) {
- ]
783 : :
784 : 444 : case STATE_OTHER:
785 [ + + ]: 444 : if (i >= *ibuf + isz) /* EOT */
786 : 20 : break;
787 [ + + ]: 424 : else if (*i == '\x1B')
788 : 52 : state = STATE_ESCAPE;
789 [ + + ]: 372 : else if (*i == '\t') {
790 : 20 : fputs(" ", f);
791 : 20 : advance_offsets(i - *ibuf, highlight, shift, 7);
792 : : } else
793 : 352 : fputc(*i, f);
794 : :
795 : 424 : break;
796 : :
797 : 52 : case STATE_ESCAPE:
798 [ - + ]: 52 : if (i >= *ibuf + isz) { /* EOT */
799 : 0 : fputc('\x1B', f);
800 : 0 : advance_offsets(i - *ibuf, highlight, shift, 1);
801 : 0 : break;
802 [ + - ]: 52 : } else if (*i == '[') { /* ANSI CSI */
803 : 52 : state = STATE_CSI;
804 : 52 : begin = i + 1;
805 [ # # ]: 0 : } else if (*i == ']') { /* ANSI CSO */
806 : 0 : state = STATE_CSO;
807 : 0 : begin = i + 1;
808 : : } else {
809 : 0 : fputc('\x1B', f);
810 : 0 : fputc(*i, f);
811 : 0 : advance_offsets(i - *ibuf, highlight, shift, 1);
812 : 0 : state = STATE_OTHER;
813 : : }
814 : :
815 : 52 : break;
816 : :
817 : 164 : case STATE_CSI:
818 : :
819 [ + - ]: 164 : if (i >= *ibuf + isz || /* EOT … */
820 [ + + ]: 164 : !strchr("01234567890;m", *i)) { /* … or invalid chars in sequence */
821 : 20 : fputc('\x1B', f);
822 : 20 : fputc('[', f);
823 : 20 : advance_offsets(i - *ibuf, highlight, shift, 2);
824 : 20 : state = STATE_OTHER;
825 : 20 : i = begin-1;
826 [ + + ]: 144 : } else if (*i == 'm')
827 : 32 : state = STATE_OTHER;
828 : :
829 : 164 : break;
830 : :
831 : 0 : case STATE_CSO:
832 : :
833 [ # # ]: 0 : if (i >= *ibuf + isz || /* EOT … */
834 [ # # # # : 0 : (*i != '\a' && (uint8_t) *i < 32U) || (uint8_t) *i > 126U) { /* … or invalid chars in sequence */
# # ]
835 : 0 : fputc('\x1B', f);
836 : 0 : fputc(']', f);
837 : 0 : advance_offsets(i - *ibuf, highlight, shift, 2);
838 : 0 : state = STATE_OTHER;
839 : 0 : i = begin-1;
840 [ # # ]: 0 : } else if (*i == '\a')
841 : 0 : state = STATE_OTHER;
842 : :
843 : 0 : break;
844 : : }
845 : 660 : }
846 : :
847 [ - + ]: 20 : if (fflush_and_check(f) < 0) {
848 : 0 : fclose(f);
849 : 0 : return mfree(obuf);
850 : : }
851 : :
852 : 20 : fclose(f);
853 : :
854 : 20 : free_and_replace(*ibuf, obuf);
855 : :
856 [ - + ]: 20 : if (_isz)
857 : 0 : *_isz = osz;
858 : :
859 [ - + ]: 20 : if (highlight) {
860 : 0 : highlight[0] += shift[0];
861 : 0 : highlight[1] += shift[1];
862 : : }
863 : :
864 : 20 : return *ibuf;
865 : : }
866 : :
867 : 319188 : char *strextend_with_separator(char **x, const char *separator, ...) {
868 : : bool need_separator;
869 : : size_t f, l, l_separator;
870 : : char *r, *p;
871 : : va_list ap;
872 : :
873 [ - + ]: 319188 : assert(x);
874 : :
875 : 319188 : l = f = strlen_ptr(*x);
876 : :
877 : 319188 : need_separator = !isempty(*x);
878 : 319188 : l_separator = strlen_ptr(separator);
879 : :
880 : 319188 : va_start(ap, separator);
881 : 319440 : for (;;) {
882 : : const char *t;
883 : : size_t n;
884 : :
885 : 638628 : t = va_arg(ap, const char *);
886 [ + + ]: 638628 : if (!t)
887 : 319188 : break;
888 : :
889 : 319440 : n = strlen(t);
890 : :
891 [ + + ]: 319440 : if (need_separator)
892 : 315464 : n += l_separator;
893 : :
894 [ - + ]: 319440 : if (n > ((size_t) -1) - l) {
895 : 0 : va_end(ap);
896 : 0 : return NULL;
897 : : }
898 : :
899 : 319440 : l += n;
900 : 319440 : need_separator = true;
901 : : }
902 : 319188 : va_end(ap);
903 : :
904 : 319188 : need_separator = !isempty(*x);
905 : :
906 : 319188 : r = realloc(*x, l+1);
907 [ - + ]: 319188 : if (!r)
908 : 0 : return NULL;
909 : :
910 : 319188 : p = r + f;
911 : :
912 : 319188 : va_start(ap, separator);
913 : 319440 : for (;;) {
914 : : const char *t;
915 : :
916 : 638628 : t = va_arg(ap, const char *);
917 [ + + ]: 638628 : if (!t)
918 : 319188 : break;
919 : :
920 [ + + + + ]: 319440 : if (need_separator && separator)
921 : 576 : p = stpcpy(p, separator);
922 : :
923 : 319440 : p = stpcpy(p, t);
924 : :
925 : 319440 : need_separator = true;
926 : : }
927 : 319188 : va_end(ap);
928 : :
929 [ - + ]: 319188 : assert(p == r + l);
930 : :
931 : 319188 : *p = 0;
932 : 319188 : *x = r;
933 : :
934 : 319188 : return r + l;
935 : : }
936 : :
937 : 516 : char *strrep(const char *s, unsigned n) {
938 : : size_t l;
939 : : char *r, *p;
940 : : unsigned i;
941 : :
942 [ - + ]: 516 : assert(s);
943 : :
944 : 516 : l = strlen(s);
945 : 516 : p = r = malloc(l * n + 1);
946 [ - + ]: 516 : if (!r)
947 : 0 : return NULL;
948 : :
949 [ + + ]: 2480 : for (i = 0; i < n; i++)
950 : 1964 : p = stpcpy(p, s);
951 : :
952 : 516 : *p = 0;
953 : 516 : return r;
954 : : }
955 : :
956 : 192 : int split_pair(const char *s, const char *sep, char **l, char **r) {
957 : : char *x, *a, *b;
958 : :
959 [ - + ]: 192 : assert(s);
960 [ - + ]: 192 : assert(sep);
961 [ - + ]: 192 : assert(l);
962 [ - + ]: 192 : assert(r);
963 : :
964 [ + + ]: 192 : if (isempty(sep))
965 : 8 : return -EINVAL;
966 : :
967 : 184 : x = strstr(s, sep);
968 [ + + ]: 184 : if (!x)
969 : 4 : return -EINVAL;
970 : :
971 : 180 : a = strndup(s, x - s);
972 [ - + ]: 180 : if (!a)
973 : 0 : return -ENOMEM;
974 : :
975 : 180 : b = strdup(x + strlen(sep));
976 [ - + ]: 180 : if (!b) {
977 : 0 : free(a);
978 : 0 : return -ENOMEM;
979 : : }
980 : :
981 : 180 : *l = a;
982 : 180 : *r = b;
983 : :
984 : 180 : return 0;
985 : : }
986 : :
987 : 22008 : int free_and_strdup(char **p, const char *s) {
988 : : char *t;
989 : :
990 [ - + ]: 22008 : assert(p);
991 : :
992 : : /* Replaces a string pointer with a strdup()ed new string,
993 : : * possibly freeing the old one. */
994 : :
995 [ + + ]: 22008 : if (streq_ptr(*p, s))
996 : 392 : return 0;
997 : :
998 [ + + ]: 21616 : if (s) {
999 : 21600 : t = strdup(s);
1000 [ - + ]: 21600 : if (!t)
1001 : 0 : return -ENOMEM;
1002 : : } else
1003 : 16 : t = NULL;
1004 : :
1005 : 21616 : free(*p);
1006 : 21616 : *p = t;
1007 : :
1008 : 21616 : return 1;
1009 : : }
1010 : :
1011 : 964 : int free_and_strndup(char **p, const char *s, size_t l) {
1012 : : char *t;
1013 : :
1014 [ - + ]: 964 : assert(p);
1015 [ + + - + ]: 964 : assert(s || l == 0);
1016 : :
1017 : : /* Replaces a string pointer with a strndup()ed new string,
1018 : : * freeing the old one. */
1019 : :
1020 [ + + - + ]: 964 : if (!*p && !s)
1021 : 0 : return 0;
1022 : :
1023 [ + + + + : 964 : if (*p && s && strneq(*p, s, l) && (l > strlen(*p) || (*p)[l] == '\0'))
+ + + + +
+ ]
1024 : 348 : return 0;
1025 : :
1026 [ + + ]: 616 : if (s) {
1027 : 612 : t = strndup(s, l);
1028 [ - + ]: 612 : if (!t)
1029 : 0 : return -ENOMEM;
1030 : : } else
1031 : 4 : t = NULL;
1032 : :
1033 : 616 : free_and_replace(*p, t);
1034 : 616 : return 1;
1035 : : }
1036 : :
1037 : 5972 : bool string_is_safe(const char *p) {
1038 : : const char *t;
1039 : :
1040 [ - + ]: 5972 : if (!p)
1041 : 0 : return false;
1042 : :
1043 [ + + ]: 64308 : for (t = p; *t; t++) {
1044 [ + - + + ]: 58392 : if (*t > 0 && *t < ' ') /* no control characters */
1045 : 36 : return false;
1046 : :
1047 [ + + ]: 58356 : if (strchr(QUOTES "\\\x7f", *t))
1048 : 20 : return false;
1049 : : }
1050 : :
1051 : 5916 : return true;
1052 : : }
|