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