Home | History | Annotate | Line # | Download | only in dist
      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 
     42 static void
     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
     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
     61 x_appendargv(char **target, char **source)
     62 {
     63     if (target && source) {
     64 	target += x_countargv(target);
     65 	while ((*target++ = *source++) != NULL) ;
     66     }
     67 }
     68 
     69 char *
     70 x_basename(char *name)
     71 {
     72     char *cp;
     73 
     74     cp = strrchr(name, '/');
     75     return (cp ? cp + 1 : name);
     76 }
     77 
     78 unsigned
     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 *
     96 x_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  */
    129 char *
    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 != 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 
    146 char *
    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 *
    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 
    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  */
    193 char *
    194 x_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  */
    230 Boolean
    231 x_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  */
    250 Boolean
    251 x_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  */
    270 int
    271 x_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  */
    286 String
    287 x_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 
    301 String
    302 x_skip_blanks(String s)
    303 {
    304     while (IsSpace(CharOf(*s)))
    305 	++s;
    306     return s;
    307 }
    308 
    309 String
    310 x_skip_nonblanks(String s)
    311 {
    312     while (*s != '\0' && !IsSpace(CharOf(*s)))
    313 	++s;
    314     return s;
    315 }
    316 
    317 static const char *
    318 skip_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  */
    328 char **
    329 x_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  */
    384 void
    385 x_freeargs(char **argv)
    386 {
    387     if (argv != NULL) {
    388 	free(*argv);
    389 	free(argv);
    390     }
    391 }
    392 
    393 int
    394 x_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 
    404 int
    405 x_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  */
    434 char *
    435 x_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  */
    453 char *
    454 x_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  */
    470 char *
    471 x_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  */
    502 char *
    503 x_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  */
    528 char
    529 x_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  */
    553 int
    554 x_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