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