1 /* $FreeBSD$ */ 2 /* 3 * Copyright (C) 1984-2024 Mark Nudelman 4 * 5 * You may distribute under the terms of either the GNU General Public 6 * License or the Less License, as specified in the README file. 7 * 8 * For more information, see the README file. 9 */ 10 11 12 /* 13 * Entry point, initialization, miscellaneous routines. 14 */ 15 16 #include "less.h" 17 #if MSDOS_COMPILER==WIN32C 18 #define WIN32_LEAN_AND_MEAN 19 #include <windows.h> 20 21 #if defined(MINGW) || defined(_MSC_VER) 22 #include <locale.h> 23 #include <shellapi.h> 24 #endif 25 26 public unsigned less_acp = CP_ACP; 27 #endif 28 29 #include "option.h" 30 31 public char * every_first_cmd = NULL; 32 public lbool new_file; 33 public int is_tty; 34 public IFILE curr_ifile = NULL_IFILE; 35 public IFILE old_ifile = NULL_IFILE; 36 public struct scrpos initial_scrpos; 37 public POSITION start_attnpos = NULL_POSITION; 38 public POSITION end_attnpos = NULL_POSITION; 39 public int wscroll; 40 public constant char *progname; 41 public int quitting; 42 public int dohelp; 43 public char * init_header = NULL; 44 static int secure_allow_features; 45 46 #if LOGFILE 47 public int logfile = -1; 48 public lbool force_logfile = FALSE; 49 public char * namelogfile = NULL; 50 #endif 51 52 #if EDITOR 53 public constant char * editor; 54 public constant char * editproto; 55 #endif 56 57 #if TAGS 58 extern char * tags; 59 extern char * tagoption; 60 extern int jump_sline; 61 #endif 62 63 #ifdef WIN32 64 static wchar_t consoleTitle[256]; 65 #endif 66 67 public int one_screen; 68 extern int less_is_more; 69 extern int missing_cap; 70 extern int know_dumb; 71 extern int quit_if_one_screen; 72 extern int no_init; 73 extern int errmsgs; 74 extern int redraw_on_quit; 75 extern int term_init_done; 76 extern int first_time; 77 78 #if MSDOS_COMPILER==WIN32C && (defined(MINGW) || defined(_MSC_VER)) 79 /* malloc'ed 0-terminated utf8 of 0-terminated wide ws, or null on errors */ 80 static char *utf8_from_wide(constant wchar_t *ws) 81 { 82 char *u8 = NULL; 83 int n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL); 84 if (n > 0) 85 { 86 u8 = ecalloc(n, sizeof(char)); 87 WideCharToMultiByte(CP_UTF8, 0, ws, -1, u8, n, NULL, NULL); 88 } 89 return u8; 90 } 91 92 /* 93 * similar to using UTF8 manifest to make the ANSI APIs UTF8, but dynamically 94 * with setlocale. unlike the manifest, argv and environ are already ACP, so 95 * make them UTF8. Additionally, this affects only the libc/crt API, and so 96 * e.g. fopen filename becomes UTF-8, but CreateFileA filename remains CP_ACP. 97 * CP_ACP remains the original codepage - use the dynamic less_acp instead. 98 * effective on win 10 1803 or later when compiled with ucrt, else no-op. 99 */ 100 static void try_utf8_locale(int *pargc, constant char ***pargv) 101 { 102 char *locale_orig = strdup(setlocale(LC_ALL, NULL)); 103 wchar_t **wargv = NULL, *wenv, *wp; 104 constant char **u8argv; 105 char *u8e; 106 int i, n; 107 108 if (!setlocale(LC_ALL, ".UTF8")) 109 goto cleanup; /* not win10 1803+ or not ucrt */ 110 111 /* 112 * wargv is before glob expansion. some ucrt builds may expand globs 113 * before main is entered, so n may be smaller than the original argc. 114 * that's ok, because later code at main expands globs anyway. 115 */ 116 wargv = CommandLineToArgvW(GetCommandLineW(), &n); 117 if (!wargv) 118 goto bad_args; 119 120 u8argv = (constant char **) ecalloc(n + 1, sizeof(char *)); 121 for (i = 0; i < n; ++i) 122 { 123 if (!(u8argv[i] = utf8_from_wide(wargv[i]))) 124 goto bad_args; 125 } 126 u8argv[n] = 0; 127 128 less_acp = CP_UTF8; 129 *pargc = n; 130 *pargv = u8argv; /* leaked on exit */ 131 132 /* convert wide env to utf8 where we can, but don't abort on errors */ 133 if ((wenv = GetEnvironmentStringsW())) 134 { 135 for (wp = wenv; *wp; wp += wcslen(wp) + 1) 136 { 137 if ((u8e = utf8_from_wide(wp))) 138 _putenv(u8e); 139 free(u8e); /* windows putenv makes a copy */ 140 } 141 FreeEnvironmentStringsW(wenv); 142 } 143 144 goto cleanup; 145 146 bad_args: 147 error("WARNING: cannot use unicode arguments", NULL_PARG); 148 setlocale(LC_ALL, locale_orig); 149 150 cleanup: 151 free(locale_orig); 152 LocalFree(wargv); 153 } 154 #endif 155 156 static int security_feature_error(constant char *type, size_t len, constant char *name) 157 { 158 PARG parg; 159 size_t msglen = len + strlen(type) + 64; 160 char *msg = ecalloc(msglen, sizeof(char)); 161 SNPRINTF3(msg, msglen, "LESSSECURE_ALLOW: %s feature name \"%.*s\"", type, (int) len, name); 162 parg.p_string = msg; 163 error("%s", &parg); 164 free(msg); 165 return 0; 166 } 167 168 /* 169 * Return the SF_xxx value of a secure feature given the name of the feature. 170 */ 171 static int security_feature(constant char *name, size_t len) 172 { 173 struct secure_feature { constant char *name; int sf_value; }; 174 static struct secure_feature features[] = { 175 { "edit", SF_EDIT }, 176 { "examine", SF_EXAMINE }, 177 { "glob", SF_GLOB }, 178 { "history", SF_HISTORY }, 179 { "lesskey", SF_LESSKEY }, 180 { "lessopen", SF_LESSOPEN }, 181 { "logfile", SF_LOGFILE }, 182 { "osc8", SF_OSC8_OPEN }, 183 { "pipe", SF_PIPE }, 184 { "shell", SF_SHELL }, 185 { "stop", SF_STOP }, 186 { "tags", SF_TAGS }, 187 }; 188 int i; 189 int match = -1; 190 191 for (i = 0; i < countof(features); i++) 192 { 193 if (strncmp(features[i].name, name, len) == 0) 194 { 195 if (match >= 0) /* name is ambiguous */ 196 return security_feature_error("ambiguous", len, name); 197 match = i; 198 } 199 } 200 if (match < 0) 201 return security_feature_error("invalid", len, name); 202 return features[match].sf_value; 203 } 204 205 /* 206 * Set the secure_allow_features bitmask, which controls 207 * whether certain secure features are allowed. 208 */ 209 static void init_secure(void) 210 { 211 #if SECURE 212 secure_allow_features = 0; 213 #else 214 constant char *str = lgetenv("LESSSECURE"); 215 if (isnullenv(str)) 216 secure_allow_features = ~0; /* allow everything */ 217 else 218 secure_allow_features = 0; /* allow nothing */ 219 220 str = lgetenv("LESSSECURE_ALLOW"); 221 if (!isnullenv(str)) 222 { 223 for (;;) 224 { 225 constant char *estr; 226 while (*str == ' ' || *str == ',') ++str; /* skip leading spaces/commas */ 227 if (*str == '\0') break; 228 estr = strchr(str, ','); 229 if (estr == NULL) estr = str + strlen(str); 230 while (estr > str && estr[-1] == ' ') --estr; /* trim trailing spaces */ 231 secure_allow_features |= security_feature(str, ptr_diff(estr, str)); 232 str = estr; 233 } 234 } 235 #endif 236 } 237 238 /* 239 * Entry point. 240 */ 241 int main(int argc, constant char *argv[]) 242 { 243 IFILE ifile; 244 constant char *s; 245 246 #if MSDOS_COMPILER==WIN32C && (defined(MINGW) || defined(_MSC_VER)) 247 if (GetACP() != CP_UTF8) /* not using a UTF-8 manifest */ 248 try_utf8_locale(&argc, &argv); 249 #endif 250 251 #ifdef __EMX__ 252 _response(&argc, &argv); 253 _wildcard(&argc, &argv); 254 #endif 255 256 progname = *argv++; 257 argc--; 258 init_secure(); 259 260 #ifdef WIN32 261 if (getenv("HOME") == NULL) 262 { 263 /* 264 * If there is no HOME environment variable, 265 * try the concatenation of HOMEDRIVE + HOMEPATH. 266 */ 267 char *drive = getenv("HOMEDRIVE"); 268 char *path = getenv("HOMEPATH"); 269 if (drive != NULL && path != NULL) 270 { 271 char *env = (char *) ecalloc(strlen(drive) + 272 strlen(path) + 6, sizeof(char)); 273 strcpy(env, "HOME="); 274 strcat(env, drive); 275 strcat(env, path); 276 putenv(env); 277 } 278 } 279 /* on failure, consoleTitle is already a valid empty string */ 280 GetConsoleTitleW(consoleTitle, countof(consoleTitle)); 281 #endif /* WIN32 */ 282 283 /* 284 * Process command line arguments and LESS environment arguments. 285 * Command line arguments override environment arguments. 286 */ 287 is_tty = isatty(1); 288 init_mark(); 289 init_cmds(); 290 init_poll(); 291 init_charset(); 292 init_line(); 293 init_cmdhist(); 294 init_option(); 295 init_search(); 296 297 /* 298 * If the name of the executable program is "more", 299 * act like LESS_IS_MORE is set. 300 */ 301 if (strcmp(last_component(progname), "more") == 0) { 302 less_is_more = 1; 303 scan_option("-fG"); 304 } 305 306 init_prompt(); 307 308 init_unsupport(); 309 s = lgetenv(less_is_more ? "MORE" : "LESS"); 310 if (s != NULL) 311 scan_option(s); 312 313 #define isoptstring(s) less_is_more ? (((s)[0] == '-') && (s)[1] != '\0') : \ 314 (((s)[0] == '-' || (s)[0] == '+') && (s)[1] != '\0') 315 while (argc > 0 && (isoptstring(*argv) || isoptpending())) 316 { 317 s = *argv++; 318 argc--; 319 if (strcmp(s, "--") == 0) 320 break; 321 scan_option(s); 322 } 323 #undef isoptstring 324 325 if (isoptpending()) 326 { 327 /* 328 * Last command line option was a flag requiring a 329 * following string, but there was no following string. 330 */ 331 nopendopt(); 332 quit(QUIT_OK); 333 } 334 335 if (less_is_more) 336 no_init = TRUE; 337 338 get_term(); 339 expand_cmd_tables(); 340 341 #if EDITOR 342 editor = lgetenv("VISUAL"); 343 if (editor == NULL || *editor == '\0') 344 { 345 editor = lgetenv("EDITOR"); 346 if (isnullenv(editor)) 347 editor = EDIT_PGM; 348 } 349 editproto = lgetenv("LESSEDIT"); 350 if (isnullenv(editproto)) 351 editproto = "%E ?lm+%lm. %g"; 352 #endif 353 354 /* 355 * Call get_ifile with all the command line filenames 356 * to "register" them with the ifile system. 357 */ 358 ifile = NULL_IFILE; 359 if (dohelp) 360 ifile = get_ifile(FAKE_HELPFILE, ifile); 361 while (argc-- > 0) 362 { 363 #if (MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC) 364 /* 365 * Because the "shell" doesn't expand filename patterns, 366 * treat each argument as a filename pattern rather than 367 * a single filename. 368 * Expand the pattern and iterate over the expanded list. 369 */ 370 struct textlist tlist; 371 constant char *filename; 372 char *gfilename; 373 char *qfilename; 374 375 gfilename = lglob(*argv++); 376 init_textlist(&tlist, gfilename); 377 filename = NULL; 378 while ((filename = forw_textlist(&tlist, filename)) != NULL) 379 { 380 qfilename = shell_unquote(filename); 381 (void) get_ifile(qfilename, ifile); 382 free(qfilename); 383 ifile = prev_ifile(NULL_IFILE); 384 } 385 free(gfilename); 386 #else 387 (void) get_ifile(*argv++, ifile); 388 ifile = prev_ifile(NULL_IFILE); 389 #endif 390 } 391 /* 392 * Set up terminal, etc. 393 */ 394 if (!is_tty) 395 { 396 /* 397 * Output is not a tty. 398 * Just copy the input file(s) to output. 399 */ 400 set_output(1); /* write to stdout */ 401 SET_BINARY(1); 402 if (edit_first() == 0) 403 { 404 do { 405 cat_file(); 406 } while (edit_next(1) == 0); 407 } 408 quit(QUIT_OK); 409 } 410 411 if (missing_cap && !know_dumb && !less_is_more) 412 error("WARNING: terminal is not fully functional", NULL_PARG); 413 open_getchr(); 414 raw_mode(1); 415 init_signals(1); 416 417 /* 418 * Select the first file to examine. 419 */ 420 #if TAGS 421 if (tagoption != NULL || strcmp(tags, "-") == 0) 422 { 423 /* 424 * A -t option was given. 425 * Verify that no filenames were also given. 426 * Edit the file selected by the "tags" search, 427 * and search for the proper line in the file. 428 */ 429 if (nifile() > 0) 430 { 431 error("No filenames allowed with -t option", NULL_PARG); 432 quit(QUIT_ERROR); 433 } 434 findtag(tagoption); 435 if (edit_tagfile()) /* Edit file which contains the tag */ 436 quit(QUIT_ERROR); 437 /* 438 * Search for the line which contains the tag. 439 * Set up initial_scrpos so we display that line. 440 */ 441 initial_scrpos.pos = tagsearch(); 442 if (initial_scrpos.pos == NULL_POSITION) 443 quit(QUIT_ERROR); 444 initial_scrpos.ln = jump_sline; 445 } else 446 #endif 447 { 448 if (edit_first()) 449 quit(QUIT_ERROR); 450 /* 451 * See if file fits on one screen to decide whether 452 * to send terminal init. But don't need this 453 * if -X (no_init) overrides this (see init()). 454 */ 455 if (quit_if_one_screen) 456 { 457 if (nifile() > 1) /* If more than one file, -F cannot be used */ 458 quit_if_one_screen = FALSE; 459 else if (!no_init) 460 one_screen = get_one_screen(); 461 } 462 } 463 if (init_header != NULL) 464 { 465 opt_header(TOGGLE, init_header); 466 free(init_header); 467 init_header = NULL; 468 } 469 470 if (errmsgs > 0) 471 { 472 /* 473 * We displayed some messages on error output 474 * (file descriptor 2; see flush()). 475 * Before erasing the screen contents, wait for a keystroke. 476 */ 477 less_printf("Press RETURN to continue ", NULL_PARG); 478 get_return(); 479 putchr('\n'); 480 } 481 set_output(1); 482 init(); 483 commands(); 484 quit(QUIT_OK); 485 /*NOTREACHED*/ 486 return (0); 487 } 488 489 /* 490 * Copy a string to a "safe" place 491 * (that is, to a buffer allocated by calloc). 492 */ 493 public char * saven(constant char *s, size_t n) 494 { 495 char *p = (char *) ecalloc(n+1, sizeof(char)); 496 strncpy(p, s, n); 497 p[n] = '\0'; 498 return (p); 499 } 500 501 public char * save(constant char *s) 502 { 503 return saven(s, strlen(s)); 504 } 505 506 public void out_of_memory(void) 507 { 508 error("Cannot allocate memory", NULL_PARG); 509 quit(QUIT_ERROR); 510 } 511 512 /* 513 * Allocate memory. 514 * Like calloc(), but never returns an error (NULL). 515 */ 516 public void * ecalloc(size_t count, size_t size) 517 { 518 void * p; 519 520 p = (void *) calloc(count, size); 521 if (p == NULL) 522 out_of_memory(); 523 return p; 524 } 525 526 /* 527 * Skip leading spaces in a string. 528 */ 529 public char * skipsp(char *s) 530 { 531 while (*s == ' ' || *s == '\t') 532 s++; 533 return (s); 534 } 535 536 /* {{ There must be a better way. }} */ 537 public constant char * skipspc(constant char *s) 538 { 539 while (*s == ' ' || *s == '\t') 540 s++; 541 return (s); 542 } 543 544 /* 545 * See how many characters of two strings are identical. 546 * If uppercase is true, the first string must begin with an uppercase 547 * character; the remainder of the first string may be either case. 548 */ 549 public size_t sprefix(constant char *ps, constant char *s, int uppercase) 550 { 551 char c; 552 char sc; 553 size_t len = 0; 554 555 for ( ; *s != '\0'; s++, ps++) 556 { 557 c = *ps; 558 if (uppercase) 559 { 560 if (len == 0 && ASCII_IS_LOWER(c)) 561 return (0); 562 if (ASCII_IS_UPPER(c)) 563 c = ASCII_TO_LOWER(c); 564 } 565 sc = *s; 566 if (len > 0 && ASCII_IS_UPPER(sc)) 567 sc = ASCII_TO_LOWER(sc); 568 if (c != sc) 569 break; 570 len++; 571 } 572 return (len); 573 } 574 575 /* 576 * Exit the program. 577 */ 578 public void quit(int status) 579 { 580 static int save_status; 581 582 /* 583 * Put cursor at bottom left corner, clear the line, 584 * reset the terminal modes, and exit. 585 */ 586 if (status < 0) 587 status = save_status; 588 else 589 save_status = status; 590 quitting = 1; 591 check_altpipe_error(); 592 if (interactive()) 593 clear_bot(); 594 deinit(); 595 flush(); 596 if (redraw_on_quit && term_init_done) 597 { 598 /* 599 * The last file text displayed might have been on an 600 * alternate screen, which now (since deinit) cannot be seen. 601 * redraw_on_quit tells us to redraw it on the main screen. 602 */ 603 first_time = 1; /* Don't print "skipping" or tildes */ 604 repaint(); 605 flush(); 606 } 607 edit((char*)NULL); 608 save_cmdhist(); 609 raw_mode(0); 610 #if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC 611 /* 612 * If we don't close 2, we get some garbage from 613 * 2's buffer when it flushes automatically. 614 * I cannot track this one down RB 615 * The same bug shows up if we use ^C^C to abort. 616 */ 617 close(2); 618 #endif 619 #ifdef WIN32 620 SetConsoleTitleW(consoleTitle); 621 #endif 622 close_getchr(); 623 exit(status); 624 } 625 626 /* 627 * Are all the features in the features mask allowed by security? 628 */ 629 public int secure_allow(int features) 630 { 631 return ((secure_allow_features & features) == features); 632 } 633