1/* $XTermId: xstrings.c,v 1.81 2024/12/01 20:27:00 tom Exp $ */
2
3/*
4 * Copyright 2000-2023,2024 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
42static void
43alloc_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
52static void
53free_pw(struct passwd *source)
54{
55    free(source->pw_dir);
56    free(source->pw_name);
57    free(source->pw_shell);
58}
59
60void
61x_appendargv(char **target, char **source)
62{
63    if (target && source) {
64	target += x_countargv(target);
65	while ((*target++ = *source++) != NULL) ;
66    }
67}
68
69char *
70x_basename(char *name)
71{
72    char *cp;
73
74    cp = strrchr(name, '/');
75    return (cp ? cp + 1 : name);
76}
77
78unsigned
79x_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 */
95char *
96x_decode_hex(const char *source, const char **next)
97{
98    char *result = NULL;
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 == NULL)
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 */
129char *
130x_encode_hex(const char *source)
131{
132    size_t need = (strlen(source) * 2) + 1;
133    char *result = malloc(need);
134
135    if (result != NULL) {
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
146char *
147x_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
155static char *
156login_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
167	if (x_getpwnam(login_name, &pw2)) {
168	    uid_t uid2 = pw2.pw_uid;
169	    struct passwd pw3;
170	    Boolean ok3;
171
172	    if ((ok3 = x_getpwuid(uid, &pw3))
173		&& ((uid_t) pw3.pw_uid == uid2)) {
174		/* use the other passwd-data including shell */
175		alloc_pw(in_out, &pw2);
176	    } else {
177		FreeAndNull(login_name);
178	    }
179	    free_pw(&pw2);
180	    if (ok3)
181		free_pw(&pw3);
182	}
183    }
184    return login_name;
185}
186
187/*
188 * Call this with in_out pointing to data filled in by x_getpwnam() or by
189 * x_getpwnam().  It finds the user's logon name, if possible.  As a side
190 * effect, it updates in_out to fill in possibly more-relevant data, i.e.,
191 * in case there is more than one alias for the same uid.
192 */
193char *
194x_getlogin(uid_t uid, struct passwd *in_out)
195{
196    char *login_name;
197
198    login_name = login_alias(x_getenv("LOGNAME"), uid, in_out);
199    if (IsEmpty(login_name)) {
200	free(login_name);
201	login_name = login_alias(x_getenv("USER"), uid, in_out);
202    }
203#ifdef HAVE_GETLOGIN
204    /*
205     * Of course getlogin() will fail if we're started from a window-manager,
206     * since there's no controlling terminal to fuss with.  For that reason, we
207     * tried first to get something useful from the user's $LOGNAME or $USER
208     * environment variables.
209     */
210    if (IsEmpty(login_name)) {
211	TRACE2(("...try getlogin\n"));
212	free(login_name);
213	login_name = login_alias(x_strdup(getlogin()), uid, in_out);
214    }
215#endif
216
217    if (IsEmpty(login_name)) {
218	free(login_name);
219	login_name = x_strdup(in_out->pw_name);
220    }
221
222    TRACE2(("x_getloginid ->%s\n", NonNull(login_name)));
223    return login_name;
224}
225
226/*
227 * Simpler than getpwnam_r, retrieves the passwd result by name and stores the
228 * result via the given pointer.  On failure, wipes the data to prevent use.
229 */
230Boolean
231x_getpwnam(const char *name, struct passwd *result)
232{
233    struct passwd *ptr = getpwnam(name);
234    Boolean code;
235
236    if (ptr != NULL && OkPasswd(ptr)) {
237	code = True;
238	alloc_pw(result, ptr);
239    } else {
240	code = False;
241	memset(result, 0, sizeof(*result));
242    }
243    return code;
244}
245
246/*
247 * Simpler than getpwuid_r, retrieves the passwd result by uid and stores the
248 * result via the given pointer.  On failure, wipes the data to prevent use.
249 */
250Boolean
251x_getpwuid(uid_t uid, struct passwd *result)
252{
253    struct passwd *ptr = getpwuid((uid_t) uid);
254    Boolean code;
255
256    if (ptr != NULL && OkPasswd(ptr)) {
257	code = True;
258	alloc_pw(result, ptr);
259    } else {
260	code = False;
261	memset(result, 0, sizeof(*result));
262    }
263    TRACE2(("x_getpwuid(%d) %d\n", (int) uid, (int) code));
264    return code;
265}
266
267/*
268 * Decode a single hex "nibble", returning the nibble as 0-15, or -1 on error.
269 */
270int
271x_hex2int(int c)
272{
273    if (c >= '0' && c <= '9')
274	return c - '0';
275    if (c >= 'a' && c <= 'f')
276	return c - 'a' + 10;
277    if (c >= 'A' && c <= 'F')
278	return c - 'A' + 10;
279    return -1;
280}
281
282/*
283 * Check if the given string is nonnull/nonempty.  If so, return a pointer
284 * to the beginning of its content, otherwise return null.
285 */
286String
287x_nonempty(String s)
288{
289    if (s != NULL) {
290	if (*s == '\0') {
291	    s = NULL;
292	} else {
293	    s = x_skip_blanks(s);
294	    if (*s == '\0')
295		s = NULL;
296	}
297    }
298    return s;
299}
300
301String
302x_skip_blanks(String s)
303{
304    while (IsSpace(CharOf(*s)))
305	++s;
306    return s;
307}
308
309String
310x_skip_nonblanks(String s)
311{
312    while (*s != '\0' && !IsSpace(CharOf(*s)))
313	++s;
314    return s;
315}
316
317static const char *
318skip_blanks(const char *s)
319{
320    while (IsSpace(CharOf(*s)))
321	++s;
322    return s;
323}
324
325/*
326 * Split a command-string into an argv[]-style array.
327 */
328char **
329x_splitargs(const char *command)
330{
331    char **result = NULL;
332
333    if (command != NULL) {
334	const char *first = skip_blanks(command);
335	char *blob = x_strdup(first);
336
337	if (blob != NULL) {
338	    int pass;
339
340	    for (pass = 0; pass < 2; ++pass) {
341		int state;
342		size_t count;
343		size_t n;
344
345		for (n = count = 0, state = 0; first[n] != '\0'; ++n) {
346
347		    switch (state) {
348		    case 0:
349			if (!IsSpace(CharOf(first[n]))) {
350			    state = 1;
351			    if (pass)
352				result[count] = blob + n;
353			    ++count;
354			} else {
355			    blob[n] = '\0';
356			}
357			break;
358		    case 1:
359			if (IsSpace(CharOf(first[n]))) {
360			    blob[n] = '\0';
361			    state = 0;
362			}
363			break;
364		    }
365		}
366		if (!pass) {
367		    result = TypeCallocN(char *, count + 1);
368		    if (!result) {
369			free(blob);
370			break;
371		    }
372		}
373	    }
374	}
375    } else {
376	result = TypeCalloc(char *);
377    }
378    return result;
379}
380
381/*
382 * Free storage allocated by x_splitargs().
383 */
384void
385x_freeargs(char **argv)
386{
387    if (argv != NULL) {
388	free(*argv);
389	free(argv);
390    }
391}
392
393int
394x_strcasecmp(const char *s1, const char *s2)
395{
396    size_t len1 = (s1 != NULL) ? strlen(s1) : 0;
397    size_t len2 = (s2 != NULL) ? strlen(s2) : 0;
398
399    return ((len1 != len2)
400	    ? 1
401	    : x_strncasecmp(s1, s2, (unsigned) len1));
402}
403
404int
405x_strncasecmp(const char *s1, const char *s2, unsigned n)
406{
407    int result = 0;
408
409    if (s1 != NULL && s2 != NULL) {
410	while (n-- != 0) {
411	    char c1 = x_toupper(*s1);
412	    char c2 = x_toupper(*s2);
413	    if (c1 != c2) {
414		result = 1;
415		break;
416	    } else if (c1 == 0) {
417		break;
418	    }
419	    s1++;
420	    s2++;
421	}
422    } else if (s1 == NULL && s2 != NULL) {
423	result = 1;
424    } else if (s1 != NULL && s2 == NULL) {
425	result = 1;
426    }
427
428    return result;
429}
430
431/*
432 * Allocates a copy of a string
433 */
434char *
435x_strdup(const char *s)
436{
437    char *result = NULL;
438
439    if (s != NULL) {
440	char *t = malloc(strlen(s) + 5);
441	if (t != NULL) {
442	    strcpy(t, s);
443	}
444	result = t;
445    }
446    return result;
447}
448
449/*
450 * Returns a pointer to the first occurrence of s2 in s1,
451 * or NULL if there are none.
452 */
453char *
454x_strindex(char *s1, const char *s2)
455{
456    char *s3;
457    size_t s2len = strlen(s2);
458
459    while ((s3 = (strchr) (s1, *s2)) != NULL) {
460	if (strncmp(s3, s2, s2len) == 0)
461	    return (s3);
462	s1 = ++s3;
463    }
464    return (NULL);
465}
466
467/*
468 * Trims leading/trailing spaces from a copy of the string.
469 */
470char *
471x_strtrim(const char *source)
472{
473    char *result;
474
475    if (IsEmpty(source)) {
476	result = x_strdup("");
477    } else {
478	char *t = x_strdup(source);
479	if (t != NULL) {
480	    char *s = t;
481	    char *d = s;
482	    while (IsSpace(CharOf(*s)))
483		++s;
484	    while ((*d++ = *s++) != '\0') {
485		;
486	    }
487	    if (*t != '\0') {
488		s = t + strlen(t);
489		while (s != t && IsSpace(CharOf(s[-1]))) {
490		    *--s = '\0';
491		}
492	    }
493	}
494	result = t;
495    }
496    return result;
497}
498
499/*
500 * Trims trailing whitespace from a copy of the string.
501 */
502char *
503x_strrtrim(const char *source)
504{
505    char *result;
506
507    if (IsEmpty(source)) {
508	result = x_strdup("");
509    } else {
510	char *t = x_strdup(source);
511	if (t != NULL) {
512	    if (*t != '\0') {
513		char *s = t + strlen(t);
514		while (s != t && IsSpace(CharOf(s[-1]))) {
515		    *--s = '\0';
516		}
517	    }
518	}
519	result = t;
520    }
521    return result;
522}
523
524/*
525 * Avoid using system locale for upper/lowercase conversion, since there are
526 * a few locales where toupper(tolower(c)) != c.
527 */
528char
529x_toupper(int ch)
530{
531    static char table[256];
532    char result = table[CharOf(ch)];
533
534    if (result == '\0') {
535	unsigned n;
536	static const char s[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
537
538	for (n = 0; n < sizeof(table); ++n) {
539	    table[n] = (char) n;
540	}
541	for (n = 0; s[n] != '\0'; ++n) {
542	    table[CharOf(s[n])] = s[n % 26];
543	}
544	result = table[CharOf(ch)];
545    }
546
547    return result;
548}
549
550/*
551 * Match strings ignoring case and allowing glob-like '*' and '?'
552 */
553int
554x_wildstrcmp(const char *pattern, const char *actual)
555{
556    int result = 0;
557
558    while (*pattern && *actual) {
559	char c1 = x_toupper(*pattern);
560	char c2 = x_toupper(*actual);
561
562	if (c1 == '*') {
563	    Boolean found = False;
564	    pattern++;
565	    while (*actual != '\0') {
566		if (!x_wildstrcmp(pattern, actual++)) {
567		    found = True;
568		    break;
569		}
570	    }
571	    if (!found) {
572		result = 1;
573		break;
574	    }
575	} else if (c1 == '?') {
576	    ++pattern;
577	    ++actual;
578	} else if ((result = (c1 != c2)) == 0) {
579	    ++pattern;
580	    ++actual;
581	} else {
582	    break;
583	}
584    }
585    return result;
586}
587