1 /* $XTermId: xstrings.c,v 1.78 2020/10/12 18:50:28 tom Exp $ */
2
3 /*
4 * Copyright 2000-2019,2020 by Thomas E. Dickey
5 *
6 * All Rights Reserved
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 * Except as contained in this notice, the name(s) of the above copyright
28 * holders shall not be used in advertising or otherwise to promote the
29 * sale, use or other dealings in this Software without prior written
30 * authorization.
31 */
32
33 #include <xterm.h>
34
35 #include <sys/types.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <ctype.h>
39
40 #include <xstrings.h>
41
42 static void
alloc_pw(struct passwd * target,struct passwd * source)43 alloc_pw(struct passwd *target, struct passwd *source)
44 {
45 *target = *source;
46 /* we care only about these strings */
47 target->pw_dir = x_strdup(source->pw_dir);
48 target->pw_name = x_strdup(source->pw_name);
49 target->pw_shell = x_strdup(source->pw_shell);
50 }
51
52 static void
free_pw(struct passwd * source)53 free_pw(struct passwd *source)
54 {
55 free(source->pw_dir);
56 free(source->pw_name);
57 free(source->pw_shell);
58 }
59
60 void
x_appendargv(char ** target,char ** source)61 x_appendargv(char **target, char **source)
62 {
63 if (target && source) {
64 target += x_countargv(target);
65 while ((*target++ = *source++) != 0) ;
66 }
67 }
68
69 char *
x_basename(char * name)70 x_basename(char *name)
71 {
72 char *cp;
73
74 cp = strrchr(name, '/');
75 return (cp ? cp + 1 : name);
76 }
77
78 unsigned
x_countargv(char ** argv)79 x_countargv(char **argv)
80 {
81 unsigned result = 0;
82 if (argv) {
83 while (*argv++) {
84 ++result;
85 }
86 }
87 return result;
88 }
89
90 /*
91 * Decode a hexadecimal string, returning the decoded string.
92 * On return, 'next' points to the first character not part of the input.
93 * The caller must free the result.
94 */
95 char *
x_decode_hex(const char * source,const char ** next)96 x_decode_hex(const char *source, const char **next)
97 {
98 char *result = 0;
99 int pass;
100 size_t j, k;
101
102 for (pass = 0; pass < 2; ++pass) {
103 for (j = k = 0; isxdigit(CharOf(source[j])); ++j) {
104 if ((pass != 0) && (j & 1) != 0) {
105 result[k++] = (char) ((CharOf(x_hex2int(source[j - 1])) << 4)
106 | CharOf(x_hex2int(source[j])));
107 }
108 }
109 *next = (source + j);
110 if ((j & 1) == 0) {
111 if (pass) {
112 result[k] = '\0';
113 } else {
114 result = malloc(++j);
115 if (result == 0)
116 break; /* not enough memory */
117 }
118 } else {
119 break; /* must have an even number of digits */
120 }
121 }
122 return result;
123 }
124
125 /*
126 * Encode a string into hexadecimal, returning the encoded string.
127 * The caller must free the result.
128 */
129 char *
x_encode_hex(const char * source)130 x_encode_hex(const char *source)
131 {
132 size_t need = (strlen(source) * 2) + 1;
133 char *result = malloc(need);
134
135 if (result != 0) {
136 unsigned j, k;
137 result[0] = '\0';
138 for (j = k = 0; source[j] != '\0'; ++j) {
139 sprintf(result + k, "%02X", CharOf(source[j]));
140 k += 2;
141 }
142 }
143 return result;
144 }
145
146 char *
x_getenv(const char * name)147 x_getenv(const char *name)
148 {
149 char *result;
150 result = x_strdup(x_nonempty(getenv(name)));
151 TRACE2(("getenv(%s) %s\n", name, result));
152 return result;
153 }
154
155 static char *
login_alias(char * login_name,uid_t uid,struct passwd * in_out)156 login_alias(char *login_name, uid_t uid, struct passwd *in_out)
157 {
158 /*
159 * If the logon-name differs from the value we get by looking in the
160 * password file, check if it does correspond to the same uid. If so,
161 * allow that as an alias for the uid.
162 */
163 if (!IsEmpty(login_name)
164 && strcmp(login_name, in_out->pw_name)) {
165 struct passwd pw2;
166 Boolean ok2;
167
168 if ((ok2 = x_getpwnam(login_name, &pw2))) {
169 uid_t uid2 = pw2.pw_uid;
170 struct passwd pw3;
171 Boolean ok3;
172
173 if ((ok3 = x_getpwuid(uid, &pw3))
174 && ((uid_t) pw3.pw_uid == uid2)) {
175 /* use the other passwd-data including shell */
176 alloc_pw(in_out, &pw2);
177 } else {
178 FreeAndNull(login_name);
179 }
180 if (ok2)
181 free_pw(&pw2);
182 if (ok3)
183 free_pw(&pw3);
184 }
185 }
186 return login_name;
187 }
188
189 /*
190 * Call this with in_out pointing to data filled in by x_getpwnam() or by
191 * x_getpwnam(). It finds the user's logon name, if possible. As a side
192 * effect, it updates in_out to fill in possibly more-relevant data, i.e.,
193 * in case there is more than one alias for the same uid.
194 */
195 char *
x_getlogin(uid_t uid,struct passwd * in_out)196 x_getlogin(uid_t uid, struct passwd *in_out)
197 {
198 char *login_name;
199
200 login_name = login_alias(x_getenv("LOGNAME"), uid, in_out);
201 if (IsEmpty(login_name)) {
202 free(login_name);
203 login_name = login_alias(x_getenv("USER"), uid, in_out);
204 }
205 #ifdef HAVE_GETLOGIN
206 /*
207 * Of course getlogin() will fail if we're started from a window-manager,
208 * since there's no controlling terminal to fuss with. For that reason, we
209 * tried first to get something useful from the user's $LOGNAME or $USER
210 * environment variables.
211 */
212 if (IsEmpty(login_name)) {
213 TRACE2(("...try getlogin\n"));
214 free(login_name);
215 login_name = login_alias(x_strdup(getlogin()), uid, in_out);
216 }
217 #endif
218
219 if (IsEmpty(login_name)) {
220 free(login_name);
221 login_name = x_strdup(in_out->pw_name);
222 }
223
224 TRACE2(("x_getloginid ->%s\n", NonNull(login_name)));
225 return login_name;
226 }
227
228 /*
229 * Simpler than getpwnam_r, retrieves the passwd result by name and stores the
230 * result via the given pointer. On failure, wipes the data to prevent use.
231 */
232 Boolean
x_getpwnam(const char * name,struct passwd * result)233 x_getpwnam(const char *name, struct passwd *result)
234 {
235 struct passwd *ptr = getpwnam(name);
236 Boolean code;
237
238 if (ptr != 0 && OkPasswd(ptr)) {
239 code = True;
240 alloc_pw(result, ptr);
241 } else {
242 code = False;
243 memset(result, 0, sizeof(*result));
244 }
245 return code;
246 }
247
248 /*
249 * Simpler than getpwuid_r, retrieves the passwd result by uid and stores the
250 * result via the given pointer. On failure, wipes the data to prevent use.
251 */
252 Boolean
x_getpwuid(uid_t uid,struct passwd * result)253 x_getpwuid(uid_t uid, struct passwd *result)
254 {
255 struct passwd *ptr = getpwuid((uid_t) uid);
256 Boolean code;
257
258 if (ptr != 0 && OkPasswd(ptr)) {
259 code = True;
260 alloc_pw(result, ptr);
261 } else {
262 code = False;
263 memset(result, 0, sizeof(*result));
264 }
265 TRACE2(("x_getpwuid(%d) %d\n", (int) uid, (int) code));
266 return code;
267 }
268
269 /*
270 * Decode a single hex "nibble", returning the nibble as 0-15, or -1 on error.
271 */
272 int
x_hex2int(int c)273 x_hex2int(int c)
274 {
275 if (c >= '0' && c <= '9')
276 return c - '0';
277 if (c >= 'a' && c <= 'f')
278 return c - 'a' + 10;
279 if (c >= 'A' && c <= 'F')
280 return c - 'A' + 10;
281 return -1;
282 }
283
284 /*
285 * Check if the given string is nonnull/nonempty. If so, return a pointer
286 * to the beginning of its content, otherwise return null.
287 */
288 String
x_nonempty(String s)289 x_nonempty(String s)
290 {
291 if (s != 0) {
292 if (*s == '\0') {
293 s = 0;
294 } else {
295 s = x_skip_blanks(s);
296 if (*s == '\0')
297 s = 0;
298 }
299 }
300 return s;
301 }
302
303 String
x_skip_blanks(String s)304 x_skip_blanks(String s)
305 {
306 while (IsSpace(CharOf(*s)))
307 ++s;
308 return s;
309 }
310
311 String
x_skip_nonblanks(String s)312 x_skip_nonblanks(String s)
313 {
314 while (*s != '\0' && !IsSpace(CharOf(*s)))
315 ++s;
316 return s;
317 }
318
319 static const char *
skip_blanks(const char * s)320 skip_blanks(const char *s)
321 {
322 while (IsSpace(CharOf(*s)))
323 ++s;
324 return s;
325 }
326
327 /*
328 * Split a command-string into an argv[]-style array.
329 */
330 char **
x_splitargs(const char * command)331 x_splitargs(const char *command)
332 {
333 char **result = 0;
334
335 if (command != 0) {
336 const char *first = skip_blanks(command);
337 char *blob = x_strdup(first);
338
339 if (blob != 0) {
340 int pass;
341
342 for (pass = 0; pass < 2; ++pass) {
343 int state;
344 size_t count;
345 size_t n;
346
347 for (n = count = 0, state = 0; first[n] != '\0'; ++n) {
348
349 switch (state) {
350 case 0:
351 if (!IsSpace(CharOf(first[n]))) {
352 state = 1;
353 if (pass)
354 result[count] = blob + n;
355 ++count;
356 } else {
357 blob[n] = '\0';
358 }
359 break;
360 case 1:
361 if (IsSpace(CharOf(first[n]))) {
362 blob[n] = '\0';
363 state = 0;
364 }
365 break;
366 }
367 }
368 if (!pass) {
369 result = TypeCallocN(char *, count + 1);
370 if (!result) {
371 free(blob);
372 break;
373 }
374 }
375 }
376 }
377 } else {
378 result = TypeCalloc(char *);
379 }
380 return result;
381 }
382
383 /*
384 * Free storage allocated by x_splitargs().
385 */
386 void
x_freeargs(char ** argv)387 x_freeargs(char **argv)
388 {
389 if (argv != 0) {
390 free(*argv);
391 free(argv);
392 }
393 }
394
395 int
x_strcasecmp(const char * s1,const char * s2)396 x_strcasecmp(const char *s1, const char *s2)
397 {
398 size_t len = strlen(s1);
399
400 if (len != strlen(s2))
401 return 1;
402
403 return x_strncasecmp(s1, s2, (unsigned) len);
404 }
405
406 int
x_strncasecmp(const char * s1,const char * s2,unsigned n)407 x_strncasecmp(const char *s1, const char *s2, unsigned n)
408 {
409 while (n-- != 0) {
410 char c1 = x_toupper(*s1);
411 char c2 = x_toupper(*s2);
412 if (c1 != c2)
413 return 1;
414 if (c1 == 0)
415 break;
416 s1++;
417 s2++;
418 }
419
420 return 0;
421 }
422
423 /*
424 * Allocates a copy of a string
425 */
426 char *
x_strdup(const char * s)427 x_strdup(const char *s)
428 {
429 char *result = 0;
430
431 if (s != 0) {
432 char *t = malloc(strlen(s) + 5);
433 if (t != 0) {
434 strcpy(t, s);
435 }
436 result = t;
437 }
438 return result;
439 }
440
441 /*
442 * Returns a pointer to the first occurrence of s2 in s1,
443 * or NULL if there are none.
444 */
445 char *
x_strindex(char * s1,const char * s2)446 x_strindex(char *s1, const char *s2)
447 {
448 char *s3;
449 size_t s2len = strlen(s2);
450
451 while ((s3 = (strchr) (s1, *s2)) != NULL) {
452 if (strncmp(s3, s2, s2len) == 0)
453 return (s3);
454 s1 = ++s3;
455 }
456 return (NULL);
457 }
458
459 /*
460 * Trims leading/trailing spaces from a copy of the string.
461 */
462 char *
x_strtrim(const char * source)463 x_strtrim(const char *source)
464 {
465 char *result;
466
467 if (IsEmpty(source)) {
468 result = x_strdup("");
469 } else {
470 char *t = x_strdup(source);
471 if (t != 0) {
472 char *s = t;
473 char *d = s;
474 while (IsSpace(CharOf(*s)))
475 ++s;
476 while ((*d++ = *s++) != '\0') {
477 ;
478 }
479 if (*t != '\0') {
480 s = t + strlen(t);
481 while (s != t && IsSpace(CharOf(s[-1]))) {
482 *--s = '\0';
483 }
484 }
485 }
486 result = t;
487 }
488 return result;
489 }
490
491 /*
492 * Trims trailing whitespace from a copy of the string.
493 */
494 char *
x_strrtrim(const char * source)495 x_strrtrim(const char *source)
496 {
497 char *result;
498
499 if (IsEmpty(source)) {
500 result = x_strdup("");
501 } else {
502 char *t = x_strdup(source);
503 if (t != 0) {
504 if (*t != '\0') {
505 char *s = t + strlen(t);
506 while (s != t && IsSpace(CharOf(s[-1]))) {
507 *--s = '\0';
508 }
509 }
510 }
511 result = t;
512 }
513 return result;
514 }
515
516 /*
517 * Avoid using system locale for upper/lowercase conversion, since there are
518 * a few locales where toupper(tolower(c)) != c.
519 */
520 char
x_toupper(int ch)521 x_toupper(int ch)
522 {
523 static char table[256];
524 char result = table[CharOf(ch)];
525
526 if (result == '\0') {
527 unsigned n;
528 static const char s[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
529
530 for (n = 0; n < sizeof(table); ++n) {
531 table[n] = (char) n;
532 }
533 for (n = 0; s[n] != '\0'; ++n) {
534 table[CharOf(s[n])] = s[n % 26];
535 }
536 result = table[CharOf(ch)];
537 }
538
539 return result;
540 }
541
542 /*
543 * Match strings ignoring case and allowing glob-like '*' and '?'
544 */
545 int
x_wildstrcmp(const char * pattern,const char * actual)546 x_wildstrcmp(const char *pattern, const char *actual)
547 {
548 int result = 0;
549
550 while (*pattern && *actual) {
551 char c1 = x_toupper(*pattern);
552 char c2 = x_toupper(*actual);
553
554 if (c1 == '*') {
555 Boolean found = False;
556 pattern++;
557 while (*actual != '\0') {
558 if (!x_wildstrcmp(pattern, actual++)) {
559 found = True;
560 break;
561 }
562 }
563 if (!found) {
564 result = 1;
565 break;
566 }
567 } else if (c1 == '?') {
568 ++pattern;
569 ++actual;
570 } else if ((result = (c1 != c2)) == 0) {
571 ++pattern;
572 ++actual;
573 } else {
574 break;
575 }
576 }
577 return result;
578 }
579