1 /*
2  * $LynxId: LYLocal.c,v 1.134 2021/07/30 08:23:29 tom Exp $
3  *
4  *  Routines to manipulate the local filesystem.
5  *  Written by: Rick Mallett, Carleton University
6  *  Report problems to rmallett@ccs.carleton.ca
7  *  Modified 18-Dec-95 David Trueman (david@cs.dal.ca):
8  *	Added OK_PERMIT compilation option.
9  *	Support replacement of compiled-in f)ull menu configuration via
10  *	  DIRED_MENU definitions in lynx.cfg, so that more than one menu
11  *	  can be driven by the same executable.
12  *  Modified Oct-96 Klaus Weide (kweide@tezcat.com):
13  *	Changed to use the library's HTList_* functions and macros for
14  *	  managing the list of tagged file URLs.
15  *	Keep track of proper level of URL escaping, so that unusual filenames
16  *	  which contain #% etc. are handled properly (some HTUnEscapeSome()'s
17  *	  left in to be conservative, and to document where superfluous
18  *	  unescaping took place before).
19  *	Dynamic memory instead of fixed length buffers in a few cases.
20  *	Other minor changes to make things work as intended.
21  *  Modified Jun-97 Klaus Weide (kweide@tezcat.com) & FM:
22  *	Modified the code handling DIRED_MENU to do more careful
23  *	  checking of the selected file.  In addition to "TAG", "FILE", and
24  *	  "DIR", DIRED_MENU definitions in lynx.cfg now also recognize LINK as
25  *	  a type.  DIRED_MENU definitions with a type field of "LINK" are only
26  *	  used if the current selection is a symbolic link ("FILE" and "DIR"
27  *	  definitions are not used in that case).  The default menu
28  *	  definitions have been updated to reflect this change, and to avoid
29  *	  the showing of menu items whose action would always fail - KW
30  *	Cast all code into the Lynx programming style. - FM
31  */
32 
33 #include <HTUtils.h>
34 #include <HTAAProt.h>
35 #include <HTFile.h>
36 #include <HTAlert.h>
37 #include <HTParse.h>
38 #include <LYCurses.h>
39 #include <LYGlobalDefs.h>
40 #include <LYUtils.h>
41 #include <LYStrings.h>
42 #include <LYCharUtils.h>
43 #include <LYStructs.h>
44 #include <LYHistory.h>
45 #include <LYUpload.h>
46 #include <LYLocal.h>
47 #include <LYClean.h>
48 #include <www_wait.h>
49 
50 #ifdef SUPPORT_CHDIR
51 #include <LYMainLoop.h>
52 #endif
53 
54 #include <LYLeaks.h>
55 
56 #undef USE_COMPRESS
57 
58 #ifdef __DJGPP__
59 #define EXT_TAR_GZ ".tgz"
60 #define EXT_TAR_Z  ".taz"
61 #define EXT_Z      ".z"
62 #else
63 #define EXT_TAR_GZ ".tar.gz"
64 #define EXT_TAR_Z  ".tar.Z"
65 #define EXT_Z      ".Z"
66 #endif
67 
68 #ifndef DIRED_MAXBUF
69 #define DIRED_MAXBUF 512
70 #endif
71 
72 #ifdef DIRED_SUPPORT
73 
74 #ifdef OK_INSTALL
75 #ifdef FNAMES_8_3
76 #define INSTALLDIRS_FILE "instdirs.htm"
77 #else
78 #define INSTALLDIRS_FILE ".installdirs.html"
79 #endif /* FNAMES_8_3 */
80 #endif /* OK_INSTALL */
81 
82 static int get_filename(const char *prompt,
83 			bstring **buf);
84 
85 #ifdef OK_PERMIT
86 static int permit_location(char *destpath,
87 			   char *srcpath,
88 			   char **newpath);
89 #endif /* OK_PERMIT */
90 /* *INDENT-OFF* */
91 static char *render_item ( const char *	s,
92 	const char *	path,
93 	const char *	dir,
94 	char *		buf,
95 	size_t		bufsize,
96 	int		url_syntax);
97 
98 struct dired_menu {
99     int cond;
100 #define DE_TAG     1
101 #define DE_DIR     2
102 #define DE_FILE    3
103 #define DE_SYMLINK 4
104     char *sfx;
105     const char *c_sfx;
106     char *link;
107     const char *c_link;
108     char *rest;
109     const char *c_rest;
110     char *href;
111     const char *c_href;
112     struct dired_menu *next;
113 };
114 
115 #define GetDiredSuffix(p) ((p)->sfx  ? (p)->sfx  : (p)->c_sfx)
116 #define GetDiredLink(p)   ((p)->link ? (p)->link : (p)->c_link)
117 #define GetDiredRest(p)   ((p)->rest ? (p)->rest : (p)->c_rest)
118 #define GetDiredHref(p)   ((p)->href ? (p)->href : (p)->c_href)
119 
120 #undef DATA
121 #define DATA(cond, sfx, link, rest, href) { \
122 	cond, \
123 	NULL, sfx, \
124 	NULL, link, \
125 	NULL, rest, \
126 	NULL, href, \
127 	NULL }
128 
129 static struct dired_menu *menu_head = NULL;
130 static struct dired_menu defmenu[] = {
131 
132 /*
133  * The following initializations determine the contents of the f)ull menu
134  * selection when in dired mode.  If any menu entries are defined in the
135  * configuration file via DIRED_MENU lines, then these default entries are
136  * discarded entirely.
137  */
138 #ifdef SUPPORT_CHDIR
139 DATA( 0,              "", "Change directory",
140                       "", "LYNXDIRED://CHDIR"),
141 #endif
142 DATA( 0,              "", "New File",
143 "(in current directory)", "LYNXDIRED://NEW_FILE%d"),
144 
145 DATA( 0,              "", "New Directory",
146 "(in current directory)", "LYNXDIRED://NEW_FOLDER%d"),
147 
148 #ifdef OK_INSTALL
149 DATA( DE_FILE,        "", "Install",
150 "selected file to new location", "LYNXDIRED://INSTALL_SRC%p"),
151 /* The following (installing a directory) doesn't work for me, at least
152    with the "install" from GNU fileutils 4.0.  I leave it in anyway, in
153    case one compiles with INSTALL_PATH / INSTALL_ARGS defined to some
154    other command for which it works (like a script, or maybe "cp -a"). - kw
155 */
156 DATA( DE_DIR,         "", "Install",
157 "selected directory to new location", "LYNXDIRED://INSTALL_SRC%p"),
158 #endif /* OK_INSTALL */
159 
160 DATA( DE_FILE,        "", "Modify File Name",
161 "(of current selection)", "LYNXDIRED://MODIFY_NAME%p"),
162 DATA( DE_DIR,         "", "Modify Directory Name",
163 "(of current selection)", "LYNXDIRED://MODIFY_NAME%p"),
164 #ifdef S_IFLNK
165 DATA( DE_SYMLINK,     "", "Modify Name",
166 "(of selected symbolic link)", "LYNXDIRED://MODIFY_NAME%p"),
167 #endif  /* S_IFLNK */
168 
169 #ifdef OK_PERMIT
170 DATA( DE_FILE,        "", "Modify File Permissions",
171 "(of current selection)", "LYNXDIRED://PERMIT_SRC%p"),
172 DATA( DE_DIR,         "", "Modify Directory Permissions",
173 "(of current selection)", "LYNXDIRED://PERMIT_SRC%p"),
174 #endif /* OK_PERMIT */
175 
176 DATA( DE_FILE,        "", "Change Location",
177 "(of selected file)"    , "LYNXDIRED://MODIFY_LOCATION%p"),
178 DATA( DE_DIR,         "", "Change Location",
179 "(of selected directory)", "LYNXDIRED://MODIFY_LOCATION%p"),
180 #ifdef S_IFLNK
181 DATA( DE_SYMLINK,     "", "Change Location",
182 "(of selected symbolic link)", "LYNXDIRED://MODIFY_LOCATION%p"),
183 #endif /* S_IFLNK */
184 
185 DATA( DE_FILE,        "", "Remove File",
186    "(current selection)", "LYNXDIRED://REMOVE_SINGLE%p"),
187 DATA( DE_DIR,         "", "Remove Directory",
188    "(current selection)", "LYNXDIRED://REMOVE_SINGLE%p"),
189 #ifdef S_IFLNK
190 DATA( DE_SYMLINK,     "", "Remove Symbolic Link",
191    "(current selection)", "LYNXDIRED://REMOVE_SINGLE%p"),
192 #endif /* S_IFLNK */
193 
194 #if defined(OK_UUDECODE) && !defined(ARCHIVE_ONLY)
195 DATA( DE_FILE,        "", "UUDecode",
196    "(current selection)", "LYNXDIRED://UUDECODE%p"),
197 #endif /* OK_UUDECODE && !ARCHIVE_ONLY */
198 
199 #if defined(OK_TAR) && !defined(ARCHIVE_ONLY)
200 DATA( DE_FILE,        EXT_TAR_Z, "Expand",
201    "(current selection)", "LYNXDIRED://UNTAR_Z%p"),
202 #endif /* OK_TAR && !ARCHIVE_ONLY */
203 
204 #if defined(OK_TAR) && defined(OK_GZIP) && !defined(ARCHIVE_ONLY)
205 DATA( DE_FILE,        ".tar.gz", "Expand",
206    "(current selection)", "LYNXDIRED://UNTAR_GZ%p"),
207 
208 DATA( DE_FILE,        ".tgz", "Expand",
209    "(current selection)", "LYNXDIRED://UNTAR_GZ%p"),
210 #endif /* OK_TAR && OK_GZIP && !ARCHIVE_ONLY */
211 
212 #ifndef ARCHIVE_ONLY
213 DATA( DE_FILE,        EXT_Z, "Uncompress",
214    "(current selection)", "LYNXDIRED://DECOMPRESS%p"),
215 #endif /* ARCHIVE_ONLY */
216 
217 #if defined(OK_GZIP) && !defined(ARCHIVE_ONLY)
218 DATA( DE_FILE,        ".gz", "Uncompress",
219    "(current selection)", "LYNXDIRED://UNGZIP%p"),
220 #endif /* OK_GZIP && !ARCHIVE_ONLY */
221 
222 #if defined(OK_ZIP) && !defined(ARCHIVE_ONLY)
223 DATA( DE_FILE,        ".zip", "Uncompress",
224    "(current selection)", "LYNXDIRED://UNZIP%p"),
225 #endif /* OK_ZIP && !ARCHIVE_ONLY */
226 
227 #if defined(OK_TAR) && !defined(ARCHIVE_ONLY)
228 DATA( DE_FILE,        ".tar", "UnTar",
229    "(current selection)", "LYNXDIRED://UNTAR%p"),
230 #endif /* OK_TAR && !ARCHIVE_ONLY */
231 
232 #ifdef OK_TAR
233 DATA( DE_DIR,         "", "Tar",
234    "(current selection)", "LYNXDIRED://TAR%p"),
235 #endif /* OK_TAR */
236 
237 #if defined(OK_TAR) && defined(OK_GZIP)
238 DATA( DE_DIR,         "", "Tar and compress",
239       "(using GNU gzip)", "LYNXDIRED://TAR_GZ%p"),
240 #endif /* OK_TAR && OK_GZIP */
241 
242 #if defined(OK_TAR) && defined(USE_COMPRESS)
243 DATA( DE_DIR,         "", "Tar and compress",
244       "(using compress)", "LYNXDIRED://TAR_Z%p"),
245 #endif /* OK_TAR && USE_COMPRESS */
246 
247 #ifdef OK_ZIP
248 DATA( DE_DIR,         "", "Package and compress",
249            "(using zip)", "LYNXDIRED://ZIP%p"),
250 #endif /* OK_ZIP */
251 
252 DATA( DE_FILE,        "", "Compress",
253  "(using Unix compress)", "LYNXDIRED://COMPRESS%p"),
254 
255 #ifdef OK_GZIP
256 DATA( DE_FILE,        "", "Compress",
257           "(using gzip)", "LYNXDIRED://GZIP%p"),
258 #endif /* OK_GZIP */
259 
260 #ifdef OK_ZIP
261 DATA( DE_FILE,        "", "Compress",
262            "(using zip)", "LYNXDIRED://ZIP%p"),
263 #endif /* OK_ZIP */
264 
265 DATA( DE_TAG,         "", "Move all tagged items to another location.",
266                       "", "LYNXDIRED://MOVE_TAGGED%d"),
267 
268 #ifdef OK_INSTALL
269 DATA( DE_TAG,         "", "Install tagged files into another directory.",
270                       "", "LYNXDIRED://INSTALL_SRC%00"),
271 #endif
272 
273 DATA( DE_TAG,         "", "Remove all tagged files and directories.",
274                       "", "LYNXDIRED://REMOVE_TAGGED"),
275 
276 DATA( DE_TAG,         "", "Untag all tagged files and directories.",
277                       "", "LYNXDIRED://CLEAR_TAGGED"),
278 
279 DATA( 0,              NULL, NULL,
280                       NULL, NULL),
281 };
282 #undef DATA
283 /* *INDENT-ON* */
284 
cannot_stat(const char * name)285 static BOOLEAN cannot_stat(const char *name)
286 {
287     char *tmpbuf = 0;
288 
289     HTSprintf0(&tmpbuf, gettext("Unable to get status of '%s'."), name);
290     HTAlert(tmpbuf);
291     FREE(tmpbuf);
292     return FALSE;
293 }
294 
295 #define OK_STAT(name, sb) (stat(name, sb) == 0)
296 
ok_stat(const char * name,struct stat * sb)297 static BOOLEAN ok_stat(const char *name, struct stat *sb)
298 {
299     BOOLEAN rc = TRUE;
300 
301     CTRACE((tfp, "testing ok_stat(%s)\n", name));
302     if (!OK_STAT(name, sb)) {
303 #ifdef DOSPATH
304 	size_t len = strlen(name);
305 
306 	/*
307 	 * If a path ends with '\' or ':', we can guess that it may be
308 	 * a directory name.  Adding a '.' (after a '\') will produce a
309 	 * pathname that stat() will accept as a directory name.
310 	 */
311 	if (len != 0 && (name[len - 1] == '\\' || name[len - 1] == ':')) {
312 	    char *temp = malloc(len + 3);
313 
314 	    if (temp != 0) {
315 		strcpy(temp, name);
316 		if (temp[len - 1] == '\\') {
317 		    strcpy(temp + len, ".");
318 		} else {
319 		    strcpy(temp + len, "\\.");
320 		}
321 		rc = OK_STAT(temp, sb);
322 		free(temp);
323 	    } else {
324 		rc = FALSE;
325 	    }
326 	} else
327 #endif
328 	    rc = FALSE;
329     }
330 
331     if (rc == FALSE)
332 	rc = cannot_stat(name);
333 
334     return rc;
335 }
336 
337 #ifdef HAVE_LSTAT
ok_lstat(char * name,struct stat * sb)338 static BOOLEAN ok_lstat(char *name, struct stat *sb)
339 {
340     CTRACE((tfp, "testing ok_lstat(%s)\n", name));
341     if (lstat(name, sb) < 0) {
342 	return cannot_stat(name);
343     }
344     return TRUE;
345 }
346 #else
347 #define ok_lstat(name,sb) ok_stat(name,sb)
348 #endif
349 
ok_file_or_dir(struct stat * sb)350 static BOOLEAN ok_file_or_dir(struct stat *sb)
351 {
352     if (!S_ISDIR(sb->st_mode)
353 	&& !S_ISREG(sb->st_mode)) {
354 	HTAlert(gettext("The selected item is not a file or a directory!  Request ignored."));
355 	return FALSE;
356     }
357     return TRUE;
358 }
359 
360 #ifdef OK_INSTALL		/* currently only used in local_install */
ok_localname(char * dst,const char * src)361 static BOOLEAN ok_localname(char *dst, const char *src)
362 {
363     struct stat dir_info;
364 
365     if (!ok_stat(src, &dir_info)
366 	|| !ok_file_or_dir(&dir_info)) {
367 	return FALSE;
368     }
369     if (strlen(src) >= DIRED_MAXBUF) {
370 	CTRACE((tfp, "filename too long in ok_localname!\n"));
371 	return FALSE;
372     }
373     strcpy(dst, src);
374     return TRUE;
375 }
376 #endif /* OK_INSTALL */
377 
378 #define MAX_ARGC 10
379 
make_argv(const char * command,...)380 static char **make_argv(const char *command, ...)
381 {
382     static char *result[MAX_ARGC];
383     int argc = 0;
384     char *value;
385     va_list ap;
386 
387     va_start(ap, command);
388     result[0] = 0;
389     StrAllocCopy(result[argc++], command);
390     do {
391 	result[argc] = 0;
392 	value = (char *) va_arg(ap, char *);
393 
394 	if (value != 0)
395 	    StrAllocCopy(result[argc], value);
396     } while (result[argc++] != 0);
397     va_end(ap);
398 
399     return result;
400 }
401 
free_argv(char ** argv)402 static void free_argv(char **argv)
403 {
404     int argc;
405 
406     for (argc = 0; argv[argc] != 0; ++argc) {
407 	free(argv[argc]);
408     }
409 }
410 
411 /*
412  * Execute DIRED command, return -1 or 0 on failure, 1 success.
413  */
LYExecv(const char * path,char ** argv,char * msg)414 static int LYExecv(const char *path,
415 		   char **argv,
416 		   char *msg)
417 {
418     int rc = 0;
419 
420 #if defined(VMS)
421     CTRACE((tfp, "LYExecv:  Called inappropriately! (path=%s)\n", path));
422 #else
423     int n;
424     char *tmpbuf = 0;
425 
426 #if defined(__DJGPP__) || defined(_WINDOWS)
427     (void) msg;
428     stop_curses();
429     HTSprintf0(&tmpbuf, "%s", path);
430     for (n = 1; argv[n] != 0; n++)
431 	HTSprintf(&tmpbuf, " %s", argv[n]);
432     HTSprintf(&tmpbuf, "\n");
433     rc = LYSystem(tmpbuf) ? 0 : 1;
434 #else
435     int pid;
436 
437 #ifdef HAVE_TYPE_UNIONWAIT
438     union wait wstatus;
439 
440 #else
441     int wstatus;
442 #endif
443 
444     if (TRACE) {
445 	CTRACE((tfp, "LYExecv path='%s'\n", path));
446 	for (n = 0; argv[n] != 0; n++)
447 	    CTRACE((tfp, "argv[%d] = '%s'\n", n, argv[n]));
448     }
449 
450     rc = 1;			/* It will work */
451     stop_curses();
452     pid = fork();		/* fork and execute command */
453 
454     switch (pid) {
455     case -1:
456 	HTSprintf0(&tmpbuf, gettext("Unable to %s due to system error!"), msg);
457 	rc = 0;
458 	break;			/* don't fall thru! - KW */
459 
460     case 0:			/* child */
461 #ifdef USE_EXECVP
462 	execvp(path, argv);	/* this uses our $PATH */
463 #else
464 	execv(path, argv);
465 #endif
466 	exit(EXIT_FAILURE);	/* execv failed, give wait() something to look at */
467 	/*NOTREACHED */
468 
469     default:			/* parent */
470 #ifndef HAVE_WAITPID
471 	while (wait(&wstatus) != pid) ;		/* do nothing */
472 #else
473 	while (-1 == waitpid(pid, &wstatus, 0)) {	/* wait for child */
474 #ifdef EINTR
475 	    if (errno == EINTR)
476 		continue;
477 #endif /* EINTR */
478 #ifdef ERESTARTSYS
479 	    if (errno == ERESTARTSYS)
480 		continue;
481 #endif /* ERESTARTSYS */
482 	    break;
483 	}
484 #endif /* !HAVE_WAITPID */
485 	if ((WIFEXITED(wstatus)
486 	     && (WEXITSTATUS(wstatus) != 0))
487 	    || (WIFSIGNALED(wstatus)
488 		&& (WTERMSIG(wstatus) > 0))) {	/* error return */
489 	    HTSprintf0(&tmpbuf,
490 		       gettext("Probable failure to %s due to system error!"),
491 		       msg);
492 	    rc = 0;
493 	}
494     }
495 #endif /* __DJGPP__ */
496 
497     if (rc == 0) {
498 	/*
499 	 * Screen may have message from the failed execv'd command.  Give user
500 	 * time to look at it before screen refresh.
501 	 */
502 	LYSleepAlert();
503     }
504     start_curses();
505     if (tmpbuf != 0) {
506 	if (rc == 0)
507 	    HTAlert(tmpbuf);
508 	FREE(tmpbuf);
509     }
510 #endif /* VMS || _WINDOWS */
511     CTRACE((tfp, "LYexecv ->%d\n", rc));
512     return (rc);
513 }
514 
make_directory(char * path)515 static int make_directory(char *path)
516 {
517     int code;
518     const char *program;
519 
520     if ((program = HTGetProgramPath(ppMKDIR)) != NULL) {
521 	char **args;
522 	char *msg = 0;
523 
524 	HTSprintf0(&msg, "make directory %s", path);
525 	args = make_argv("mkdir",
526 			 path,
527 			 NULL);
528 	code = (LYExecv(program, args, msg) <= 0) ? -1 : 1;
529 	FREE(msg);
530 	free_argv(args);
531     } else {
532 #ifdef _WINDOWS
533 	code = mkdir(path) ? -1 : 1;
534 #else
535 	code = mkdir(path, 0777) ? -1 : 1;
536 #endif
537 	CTRACE((tfp, "builtin mkdir ->%d\n\t%s\n", code, path));
538     }
539     return (code);
540 }
541 
remove_file(char * path)542 static int remove_file(char *path)
543 {
544     int code;
545     const char *program;
546 
547     if ((program = HTGetProgramPath(ppRM)) != NULL) {
548 	char **args;
549 	char *tmpbuf = NULL;
550 
551 	args = make_argv("rm",
552 			 "-f",
553 			 path,
554 			 NULL);
555 	HTSprintf0(&tmpbuf, gettext("remove %s"), path);
556 	code = LYExecv(program, args, tmpbuf);
557 	FREE(tmpbuf);
558 	free_argv(args);
559     } else {
560 	code = remove(path) ? -1 : 1;
561 	CTRACE((tfp, "builtin remove ->%d\n\t%s\n", code, path));
562     }
563     return (code);
564 }
565 
remove_directory(char * path)566 static int remove_directory(char *path)
567 {
568     int code;
569     const char *program;
570 
571     if ((program = HTGetProgramPath(ppRMDIR)) != NULL) {
572 	char **args;
573 	char *tmpbuf = NULL;
574 
575 	args = make_argv("rmdir",
576 			 path,
577 			 NULL);
578 	HTSprintf0(&tmpbuf, gettext("remove %s"), path);
579 	code = LYExecv(program, args, tmpbuf);
580 	FREE(tmpbuf);
581 	free_argv(args);
582     } else {
583 	code = rmdir(path) ? -1 : 1;
584 	CTRACE((tfp, "builtin rmdir ->%d\n\t%s\n", code, path));
585     }
586     return (code);
587 }
588 
touch_file(char * path)589 static int touch_file(char *path)
590 {
591     int code;
592     const char *program;
593 
594     if ((program = HTGetProgramPath(ppTOUCH)) != NULL) {
595 	char **args;
596 	char *msg = NULL;
597 
598 	HTSprintf0(&msg, gettext("touch %s"), path);
599 	args = make_argv("touch",
600 			 path,
601 			 NULL);
602 	code = (LYExecv(program, args, msg) <= 0) ? -1 : 1;
603 	FREE(msg);
604 	free_argv(args);
605     } else {
606 	FILE *fp;
607 
608 	if ((fp = fopen(path, BIN_W)) != 0) {
609 	    fclose(fp);
610 	    code = 1;
611 	} else {
612 	    code = -1;
613 	}
614 	CTRACE((tfp, "builtin touch ->%d\n\t%s\n", code, path));
615     }
616     return (code);
617 }
618 
move_file(char * source,char * target)619 static int move_file(char *source, char *target)
620 {
621     int code;
622     const char *program;
623 
624     if ((program = HTGetProgramPath(ppMV)) != NULL) {
625 	char *msg = 0;
626 	char **args;
627 
628 	HTSprintf0(&msg, gettext("move %s to %s"), source, target);
629 	args = make_argv("mv",
630 			 source,
631 			 target,
632 			 NULL);
633 	code = (LYExecv(program, args, msg) <= 0) ? -1 : 1;
634 	FREE(msg);
635 	free_argv(args);
636     } else {
637 	struct stat sb;
638 	char *actual = 0;
639 
640 	/* the caller sets up a target directory; we need a file path */
641 	if (stat(target, &sb) == 0
642 	    && S_ISDIR(sb.st_mode)) {
643 	    HTSprintf0(&actual, "%s/%s", target, LYPathLeaf(source));
644 	    CTRACE((tfp, "move_file source=%s, target=%s\n", source, target));
645 	    target = actual;
646 	}
647 	code = rename(source, target);
648 	CTRACE((tfp, "builtin move ->%d\n\tsource=%s\n\ttarget=%s\n",
649 		code, source, target));
650 	if (code != 0) {	/* it failed */
651 	    if ((code = LYCopyFile(source, target)) >= 0) {
652 		code = remove(source);
653 		CTRACE((tfp, "...remove source after copying ->%d\n", code));
654 	    }
655 	}
656 	if (code == 0)
657 	    code = 1;
658 	if (actual != target) {
659 	    FREE(actual);
660 	}
661     }
662     return code;
663 }
664 
not_already_exists(char * name)665 static BOOLEAN not_already_exists(char *name)
666 {
667     struct stat dir_info;
668 
669     if (!OK_STAT(name, &dir_info)) {
670 	if (errno != ENOENT) {
671 	    cannot_stat(name);
672 	} else {
673 	    return TRUE;
674 	}
675     } else if (S_ISDIR(dir_info.st_mode)) {
676 	HTAlert(gettext("There is already a directory with that name!  Request ignored."));
677     } else if (S_ISREG(dir_info.st_mode)) {
678 	HTAlert(gettext("There is already a file with that name!  Request ignored."));
679     } else {
680 	HTAlert(gettext("The specified name is already in use!  Request ignored."));
681     }
682     return FALSE;
683 }
684 
dir_has_same_owner(struct stat * dst_info,struct stat * src_info)685 static BOOLEAN dir_has_same_owner(struct stat *dst_info,
686 				  struct stat *src_info)
687 {
688     if (S_ISDIR(dst_info->st_mode)) {
689 	if (dst_info->st_uid == src_info->st_uid) {
690 	    return TRUE;
691 	} else {
692 	    HTAlert(gettext("Destination has different owner!  Request denied."));
693 	}
694     } else {
695 	HTAlert(gettext("Destination is not a valid directory!  Request denied."));
696     }
697     return FALSE;
698 }
699 
700 /*
701  * Make sure the source and target are not the same location.
702  */
same_location(struct stat * dst_info,struct stat * src_info)703 static BOOLEAN same_location(struct stat *dst_info,
704 			     struct stat *src_info)
705 {
706     BOOLEAN result = FALSE;
707 
708 #ifdef UNIX
709     if (src_info->st_dev == dst_info->st_dev &&
710 	src_info->st_ino == dst_info->st_ino) {
711 	HTAlert(gettext("Source and destination are the same location!  Request ignored!"));
712 	result = TRUE;
713     }
714 #endif
715     return result;
716 }
717 
718 /*
719  * Remove all tagged files and directories.
720  */
remove_tagged(void)721 static int remove_tagged(void)
722 {
723     int ans;
724     BOOL will_clear = TRUE;
725     char *cp;
726     char *tmpbuf = NULL;
727     char *testpath = NULL;
728     struct stat dir_info;
729     int count;
730     HTList *tag;
731 
732     if (HTList_isEmpty(tagged))	/* should never happen */
733 	return 0;
734 
735     ans = HTConfirm(gettext("Remove all tagged files and directories?"));
736 
737     count = 0;
738     tag = tagged;
739     while (ans == YES && (cp = (char *) HTList_nextObject(tag)) != NULL) {
740 	if (is_url(cp) == FILE_URL_TYPE) {	/* unnecessary check */
741 	    testpath = HTfullURL_toFile(cp);
742 	    LYTrimPathSep(testpath);
743 	    will_clear = TRUE;
744 
745 	    /*
746 	     * Check the current status of the path to be deleted.
747 	     */
748 	    if (!ok_stat(testpath, &dir_info)) {
749 		will_clear = FALSE;
750 		break;
751 	    } else {
752 		if (remove_file(testpath) <= 0) {
753 		    if (count == 0)
754 			count = -1;
755 		    will_clear = FALSE;
756 		    break;
757 		}
758 		++count;
759 		FREE(testpath);
760 	    }
761 	}
762     }
763     FREE(testpath);
764     FREE(tmpbuf);
765     if (will_clear)
766 	clear_tags();
767     return count;
768 }
769 
parse_directory(char * path)770 static char *parse_directory(char *path)
771 {
772     char *result;
773 
774     if (path) {
775 	path = strip_trailing_slash(path);
776 	path = HTParse(".", path, PARSE_PATH + PARSE_PUNCTUATION);
777 	result = HTURLPath_toFile(path, TRUE, FALSE);
778 	FREE(path);
779     } else {			/* Last resort, should never happen. */
780 	result = HTURLPath_toFile(".", TRUE, FALSE);
781     }
782     return result;
783 }
784 
785 /*
786  * Move all tagged files and directories to a new location.
787  *
788  * The 'testpath' parameter is the current location, used for resolving
789  * relative target specifications.
790  */
modify_tagged(char * testpath)791 static int modify_tagged(char *testpath)
792 {
793     char *cp;
794     bstring *given_target = NULL;
795     char *dst_path = NULL;
796     char *src_path = NULL;
797     char *old_path = NULL;
798     struct stat src_info;
799     struct stat dst_info;
800     int count = 0;
801     HTList *tag;
802 
803     CTRACE((tfp, "modify_tagged(%s)\n", testpath));
804 
805     if (HTList_isEmpty(tagged))	/* should never happen */
806 	return 0;
807 
808     _statusline(gettext("Enter new location for tagged items: "));
809 
810     BStrCopy0(given_target, "");
811     (void) LYgetBString(&given_target, FALSE, 0, NORECALL);
812     if (!isBEmpty(given_target)) {
813 	/*
814 	 * Replace ~/ references to the home directory.
815 	 */
816 	if (LYIsTilde(given_target->str[0]) && LYIsPathSep(given_target->str[1])) {
817 	    char *cp1 = NULL;
818 
819 	    StrAllocCopy(cp1, Home_Dir());
820 	    StrAllocCat(cp1, (given_target->str + 1));
821 	    BStrCopy0(given_target, cp1);
822 	    FREE(cp1);
823 	}
824 
825 	/*
826 	 * If path is relative, prefix it with current location.
827 	 */
828 	if (!LYIsPathSep(given_target->str[0])) {
829 	    dst_path = HTLocalName(testpath);
830 	    LYAddPathSep(&dst_path);
831 	    StrAllocCat(dst_path, given_target->str);
832 	} else {
833 	    dst_path = HTLocalName(given_target->str);
834 	}
835 
836 	if (!ok_stat(dst_path, &dst_info)) {
837 	    FREE(dst_path);
838 	    BStrFree(given_target);
839 	    return 0;
840 	}
841 
842 	/*
843 	 * Determine the ownership of the current location, using the directory
844 	 * containing the file or subdir from each of the tagged files.
845 	 */
846 	for (tag = tagged; (cp = (char *) HTList_nextObject(tag)) != NULL;) {
847 	    src_path = parse_directory(cp);
848 
849 	    if (isEmpty(old_path) || strcmp(old_path, src_path)) {
850 		if (!ok_stat(src_path, &src_info)
851 		    || same_location(&dst_info, &src_info)
852 		    || !dir_has_same_owner(&dst_info, &src_info)) {
853 		    FREE(src_path);
854 		    BStrFree(given_target);
855 		    return 0;
856 		}
857 	    }
858 	    StrAllocCopy(old_path, src_path);
859 	    FREE(src_path);
860 	}
861 
862 	/*
863 	 * Move all tagged items to the target location.
864 	 */
865 	for (tag = tagged; (cp = (char *) HTList_nextObject(tag)) != NULL;) {
866 	    src_path = HTfullURL_toFile(cp);
867 
868 	    if (move_file(src_path, dst_path) < 0) {
869 		if (count == 0)
870 		    count = -1;
871 		break;
872 	    }
873 	    FREE(src_path);
874 	    ++count;
875 	}
876 	clear_tags();
877 	FREE(src_path);
878 	FREE(dst_path);
879     }
880     BStrFree(given_target);
881     return count;
882 }
883 
884 /*
885  * Modify the name of the specified item.
886  */
modify_name(char * testpath)887 static int modify_name(char *testpath)
888 {
889     const char *cp;
890     bstring *tmpbuf = NULL;
891     char *newpath = NULL;
892     struct stat dir_info;
893     int code = 0;
894 
895     /*
896      * Determine the status of the selected item.
897      */
898     testpath = strip_trailing_slash(testpath);
899 
900     if (ok_stat(testpath, &dir_info)) {
901 
902 	/*
903 	 * Change the name of the file or directory.
904 	 */
905 	if (S_ISDIR(dir_info.st_mode)) {
906 	    cp = gettext("Enter new name for directory: ");
907 	} else if (S_ISREG(dir_info.st_mode)) {
908 	    cp = gettext("Enter new name for file: ");
909 	} else {
910 	    return ok_file_or_dir(&dir_info);
911 	}
912 
913 	BStrCopy0(tmpbuf, LYPathLeaf(testpath));
914 	if (get_filename(cp, &tmpbuf)) {
915 
916 	    /*
917 	     * Do not allow the user to also change the location at this time.
918 	     */
919 	    if (LYLastPathSep(tmpbuf->str) != 0) {
920 		HTAlert(gettext("Illegal character (path-separator) found! Request ignored."));
921 	    } else if (strlen(tmpbuf->str)) {
922 		if ((cp = LYLastPathSep(testpath)) != NULL) {
923 		    HTSprintf0(&newpath, "%.*s%s",
924 			       (int) (cp - testpath + 1),
925 			       testpath, tmpbuf->str);
926 		} else {
927 		    StrAllocCopy(newpath, tmpbuf->str);
928 		}
929 
930 		/*
931 		 * Make sure the destination does not already exist.
932 		 */
933 		if (not_already_exists(newpath)) {
934 		    code = move_file(testpath, newpath);
935 		}
936 		FREE(newpath);
937 	    }
938 	}
939     }
940     BStrFree(tmpbuf);
941     return code;
942 }
943 
944 /*
945  * Change the location of a file or directory.
946  */
modify_location(char * testpath)947 static int modify_location(char *testpath)
948 {
949     const char *cp;
950     char *sp;
951     bstring *tmpbuf = NULL;
952     char *newpath = NULL;
953     char *savepath = NULL;
954     struct stat old_info;
955     struct stat dir_info;
956     int code = 0;
957 
958     /*
959      * Determine the status of the selected item.
960      */
961     testpath = strip_trailing_slash(testpath);
962     if (!ok_stat(testpath, &dir_info)) {
963 	return 0;
964     }
965 
966     /*
967      * Change the location of the file or directory.
968      */
969     if (S_ISDIR(dir_info.st_mode)) {
970 	cp = gettext("Enter new location for directory: ");
971     } else if (S_ISREG(dir_info.st_mode)) {
972 	cp = gettext("Enter new location for file: ");
973     } else {
974 	return ok_file_or_dir(&dir_info);
975     }
976 
977     BStrCopy0(tmpbuf, testpath);
978     *LYPathLeaf(tmpbuf->str) = '\0';
979     if (get_filename(cp, &tmpbuf)) {
980 	if (strlen(tmpbuf->str)) {
981 	    StrAllocCopy(savepath, testpath);
982 	    StrAllocCopy(newpath, testpath);
983 
984 	    /*
985 	     * Allow ~/ references to the home directory.
986 	     */
987 	    if (LYIsTilde(tmpbuf->str[0])
988 		&& (tmpbuf->str[1] == '\0' || LYIsPathSep(tmpbuf->str[1]))) {
989 		StrAllocCopy(newpath, Home_Dir());
990 		StrAllocCat(newpath, (tmpbuf->str + 1));
991 		BStrCopy0(tmpbuf, newpath);
992 	    }
993 	    if (LYisAbsPath(tmpbuf->str)) {
994 		StrAllocCopy(newpath, tmpbuf->str);
995 	    } else if ((sp = LYLastPathSep(newpath)) != NULL) {
996 		*++sp = '\0';
997 		StrAllocCat(newpath, tmpbuf->str);
998 	    } else {
999 		HTAlert(gettext("Unexpected failure - unable to find trailing path separator"));
1000 		FREE(newpath);
1001 		FREE(savepath);
1002 		BStrFree(tmpbuf);
1003 		return 0;
1004 	    }
1005 
1006 	    /*
1007 	     * Make sure the source and target have the same owner (uid).
1008 	     */
1009 	    old_info = dir_info;
1010 	    if (!ok_stat(newpath, &dir_info)) {
1011 		code = 0;
1012 	    } else if (same_location(&old_info, &dir_info)) {
1013 		code = 0;
1014 	    } else if (dir_has_same_owner(&dir_info, &old_info)) {
1015 		code = move_file(savepath, newpath);
1016 	    }
1017 	    FREE(newpath);
1018 	    FREE(savepath);
1019 	}
1020     }
1021     BStrFree(tmpbuf);
1022     return code;
1023 }
1024 
1025 /*
1026  * Modify name or location of a file or directory on localhost.
1027  */
local_modify(DocInfo * doc,char ** newpath)1028 int local_modify(DocInfo *doc, char **newpath)
1029 {
1030     int ans;
1031     char *cp;
1032     bstring *testpath = NULL;
1033     int count;
1034     int code = 0;
1035 
1036     if (!HTList_isEmpty(tagged)) {
1037 	cp = HTpartURL_toFile(doc->address);
1038 
1039 	count = modify_tagged(cp);
1040 	FREE(cp);
1041 
1042 	if (doc->link > (nlinks - count - 1))
1043 	    doc->link = (nlinks - count - 1);
1044 	doc->link = ((doc->link < 0)
1045 		     ? 0
1046 		     : doc->link);
1047 
1048 	return count;
1049     } else if (doc->link < 0 || doc->link > nlinks) {
1050 	/*
1051 	 * Added protection.
1052 	 */
1053 	return 0;
1054     }
1055 
1056     /*
1057      * Do not allow simultaneous change of name and location as in Unix.  This
1058      * reduces functionality but reduces difficulty for the novice.
1059      */
1060 #ifdef OK_PERMIT
1061     _statusline(gettext("Modify name, location, or permission (n, l, or p): "));
1062 #else
1063     _statusline(gettext("Modify name or location (n or l): "));
1064 #endif /* OK_PERMIT */
1065     ans = LYgetch_single();
1066 
1067     if (StrChr("NLP", ans) != NULL) {
1068 	cp = HTfullURL_toFile(links[doc->link].lname);
1069 	if (strlen(cp) >= DIRED_MAXBUF) {
1070 	    FREE(cp);
1071 	    return 0;
1072 	}
1073 	BStrCopy0(testpath, cp);
1074 	FREE(cp);
1075 
1076 	if (ans == 'N') {
1077 	    code = modify_name(testpath->str);
1078 	} else if (ans == 'L') {
1079 	    if (modify_location(testpath->str)) {
1080 		if (doc->link == (nlinks - 1))
1081 		    --doc->link;
1082 		code = 1;
1083 	    }
1084 #ifdef OK_PERMIT
1085 	} else if (ans == 'P') {
1086 	    code = permit_location(NULL, testpath->str, newpath);
1087 #endif /* OK_PERMIT */
1088 	} else {
1089 	    /*
1090 	     * Code for changing ownership needed here.
1091 	     */
1092 	    HTAlert(gettext("This feature not yet implemented!"));
1093 	}
1094     }
1095     BStrFree(testpath);
1096     return code;
1097 }
1098 
1099 #define BadChars() ((!no_dotfiles && show_dotfiles) \
1100 		    ? "~/" \
1101 		    : ".~/")
1102 
1103 /*
1104  * Create a new empty file in the current directory.
1105  */
create_file(char * current_location)1106 static int create_file(char *current_location)
1107 {
1108     int code = FALSE;
1109     bstring *tmpbuf = NULL;
1110     char *testpath = NULL;
1111 
1112     BStrCopy0(tmpbuf, "");
1113     if (get_filename(gettext("Enter name of file to create: "), &tmpbuf)) {
1114 
1115 	if (strstr(tmpbuf->str, "//") != NULL) {
1116 	    HTAlert(gettext("Illegal redirection \"//\" found! Request ignored."));
1117 	} else if (strlen(tmpbuf->str) &&
1118 		   StrChr(BadChars(), tmpbuf->str[0]) == NULL) {
1119 	    StrAllocCopy(testpath, current_location);
1120 	    LYAddPathSep(&testpath);
1121 
1122 	    /*
1123 	     * Append the target filename to the current location.
1124 	     */
1125 	    StrAllocCat(testpath, tmpbuf->str);
1126 
1127 	    /*
1128 	     * Make sure the target does not already exist
1129 	     */
1130 	    if (not_already_exists(testpath)) {
1131 		code = touch_file(testpath);
1132 	    }
1133 	    FREE(testpath);
1134 	}
1135     }
1136     BStrFree(tmpbuf);
1137     return code;
1138 }
1139 
1140 /*
1141  * Create a new directory in the current directory.
1142  */
create_directory(char * current_location)1143 static int create_directory(char *current_location)
1144 {
1145     int code = FALSE;
1146     bstring *tmpbuf = NULL;
1147     char *testpath = NULL;
1148 
1149     BStrCopy0(tmpbuf, "");
1150     if (get_filename(gettext("Enter name for new directory: "), &tmpbuf)) {
1151 
1152 	if (strstr(tmpbuf->str, "//") != NULL) {
1153 	    HTAlert(gettext("Illegal redirection \"//\" found! Request ignored."));
1154 	} else if (strlen(tmpbuf->str) &&
1155 		   StrChr(BadChars(), tmpbuf->str[0]) == NULL) {
1156 	    StrAllocCopy(testpath, current_location);
1157 	    LYAddPathSep(&testpath);
1158 
1159 	    StrAllocCat(testpath, tmpbuf->str);
1160 
1161 	    /*
1162 	     * Make sure the target does not already exist.
1163 	     */
1164 	    if (not_already_exists(testpath)) {
1165 		code = make_directory(testpath);
1166 	    }
1167 	    FREE(testpath);
1168 	}
1169     }
1170     BStrFree(tmpbuf);
1171     return code;
1172 }
1173 
1174 /*
1175  * Create a file or a directory at the current location.
1176  */
local_create(DocInfo * doc)1177 int local_create(DocInfo *doc)
1178 {
1179     int ans;
1180     char *cp;
1181     char testpath[DIRED_MAXBUF];
1182 
1183     cp = HTfullURL_toFile(doc->address);
1184     if (strlen(cp) >= DIRED_MAXBUF) {
1185 	FREE(cp);
1186 	return 0;
1187     }
1188     strcpy(testpath, cp);
1189     FREE(cp);
1190 
1191     _statusline(gettext("Create file or directory (f or d): "));
1192     ans = LYgetch_single();
1193 
1194     if (ans == 'F') {
1195 	return (create_file(testpath));
1196     } else if (ans == 'D') {
1197 	return (create_directory(testpath));
1198     } else {
1199 	return 0;
1200     }
1201 }
1202 
1203 /*
1204  * Remove a single file or directory.
1205  */
remove_single(char * testpath)1206 static int remove_single(char *testpath)
1207 {
1208     int code = 0;
1209     char *cp;
1210     char *tmpbuf = 0;
1211     struct stat dir_info;
1212     BOOL is_directory = FALSE;
1213 
1214     if (!ok_lstat(testpath, &dir_info)) {
1215 	return 0;
1216     }
1217 
1218     /*
1219      * Locate the filename portion of the path.
1220      */
1221     if ((cp = LYLastPathSep(testpath)) != NULL) {
1222 	++cp;
1223     } else {
1224 	cp = testpath;
1225     }
1226     if (S_ISDIR(dir_info.st_mode)) {
1227 	/*
1228 	 * This strlen stuff will probably screw up intl translations.  Course,
1229 	 * it's probably broken for screen sizes other 80, too -jes
1230 	 */
1231 	if (strlen(cp) < 37) {
1232 	    HTSprintf0(&tmpbuf,
1233 		       gettext("Remove directory '%s'?"), cp);
1234 	} else {
1235 	    HTSprintf0(&tmpbuf,
1236 		       gettext("Remove directory?"));
1237 	}
1238 	is_directory = TRUE;
1239     } else if (S_ISREG(dir_info.st_mode)) {
1240 	if (strlen(cp) < 60) {
1241 	    HTSprintf0(&tmpbuf, gettext("Remove file '%s'?"), cp);
1242 	} else {
1243 	    HTSprintf0(&tmpbuf, gettext("Remove file?"));
1244 	}
1245 #ifdef S_IFLNK
1246     } else if (S_ISLNK(dir_info.st_mode)) {
1247 	if (strlen(cp) < 50) {
1248 	    HTSprintf0(&tmpbuf, gettext("Remove symbolic link '%s'?"), cp);
1249 	} else {
1250 	    HTSprintf0(&tmpbuf, gettext("Remove symbolic link?"));
1251 	}
1252 #endif
1253     } else {
1254 	cannot_stat(testpath);
1255 	FREE(tmpbuf);
1256 	return 0;
1257     }
1258 
1259     if (HTConfirm(tmpbuf) == YES) {
1260 	code = is_directory
1261 	    ? remove_directory(testpath)
1262 	    : remove_file(testpath);
1263     }
1264     FREE(tmpbuf);
1265     return code;
1266 }
1267 
1268 /*
1269  * Remove a file or a directory.
1270  */
local_remove(DocInfo * doc)1271 int local_remove(DocInfo *doc)
1272 {
1273     char *cp, *tp;
1274     char testpath[DIRED_MAXBUF];
1275     int count, i;
1276 
1277     if (!HTList_isEmpty(tagged)) {
1278 	count = remove_tagged();
1279 	if (doc->link > (nlinks - count - 1))
1280 	    doc->link = (nlinks - count - 1);
1281 	doc->link = ((doc->link < 0)
1282 		     ? 0
1283 		     : doc->link);
1284 	return count;
1285     } else if (doc->link < 0 || doc->link > nlinks) {
1286 	return 0;
1287     }
1288     cp = links[doc->link].lname;
1289     if (is_url(cp) == FILE_URL_TYPE) {
1290 	tp = HTfullURL_toFile(cp);
1291 	if (strlen(tp) >= DIRED_MAXBUF) {
1292 	    FREE(tp);
1293 	    return 0;
1294 	}
1295 	strcpy(testpath, tp);
1296 	FREE(tp);
1297 
1298 	if ((i = (int) strlen(testpath)) && testpath[i - 1] == '/')
1299 	    testpath[(i - 1)] = '\0';
1300 
1301 	if (remove_single(testpath)) {
1302 	    if (doc->link == (nlinks - 1))
1303 		--doc->link;
1304 	    return 1;
1305 	}
1306     }
1307     return 0;
1308 }
1309 
1310 #ifdef OK_PERMIT
1311 
1312 static bstring *LYValidPermitFile = NULL;
1313 
permit_bits(char * string_mode)1314 static long permit_bits(char *string_mode)
1315 {
1316     if (!strcmp(string_mode, "IRUSR"))
1317 	return S_IRUSR;
1318     if (!strcmp(string_mode, "IWUSR"))
1319 	return S_IWUSR;
1320     if (!strcmp(string_mode, "IXUSR"))
1321 	return S_IXUSR;
1322     if (!strcmp(string_mode, "IRGRP"))
1323 	return S_IRGRP;
1324     if (!strcmp(string_mode, "IWGRP"))
1325 	return S_IWGRP;
1326     if (!strcmp(string_mode, "IXGRP"))
1327 	return S_IXGRP;
1328     if (!strcmp(string_mode, "IROTH"))
1329 	return S_IROTH;
1330     if (!strcmp(string_mode, "IWOTH"))
1331 	return S_IWOTH;
1332     if (!strcmp(string_mode, "IXOTH"))
1333 	return S_IXOTH;
1334     /* Don't include setuid and friends; use shell access for that. */
1335     return 0;
1336 }
1337 
1338 /*
1339  * Handle DIRED permissions.
1340  */
permit_location(char * destpath,char * srcpath,char ** newpath)1341 static int permit_location(char *destpath,
1342 			   char *srcpath,
1343 			   char **newpath)
1344 {
1345     int code = 0;
1346 
1347 #ifndef UNIX
1348     HTAlert(gettext("Sorry, don't know how to permit non-UNIX files yet."));
1349 #else
1350     static char tempfile[LY_MAXPATH] = "\0";
1351     char *cp;
1352     char tmpdst[LY_MAXPATH];
1353     struct stat dir_info;
1354     const char *program;
1355 
1356     if (srcpath) {
1357 	/*
1358 	 * Create form.
1359 	 */
1360 	FILE *fp0;
1361 	char *user_filename;
1362 	const char *group_name;
1363 
1364 	srcpath = strip_trailing_slash(srcpath);
1365 
1366 	/*
1367 	 * A couple of sanity tests.
1368 	 */
1369 	if (!ok_lstat(srcpath, &dir_info)
1370 	    || !ok_file_or_dir(&dir_info))
1371 	    return code;
1372 
1373 	user_filename = LYPathLeaf(srcpath);
1374 
1375 	(void) LYRemoveTemp(tempfile);
1376 	if ((fp0 = LYOpenTemp(tempfile, HTML_SUFFIX, "w")) == NULL) {
1377 	    HTAlert(gettext("Unable to open permit options file"));
1378 	    return (code);
1379 	}
1380 
1381 	/*
1382 	 * Make the tempfile a URL.
1383 	 */
1384 	LYLocalFileToURL(newpath, tempfile);
1385 	LYRegisterUIPage(*newpath, UIP_PERMIT_OPTIONS);
1386 
1387 	group_name = HTAA_GidToName((int) dir_info.st_gid);
1388 	BStrCopy0(LYValidPermitFile, srcpath);
1389 
1390 	fprintf(fp0, "<Html><Head>\n<Title>%s</Title>\n</Head>\n<Body>\n",
1391 		PERMIT_OPTIONS_TITLE);
1392 	fprintf(fp0, "<H1>%s%s</H1>\n", PERMISSIONS_SEGMENT, user_filename);
1393 	{
1394 	    /*
1395 	     * Prevent filenames which include '#' or '?' from messing it up.
1396 	     */
1397 	    char *srcpath_url = HTEscape(srcpath, URL_PATH);
1398 
1399 	    fprintf(fp0, "<Form Action=\"%s//PERMIT_LOCATION%s\">\n",
1400 		    STR_LYNXDIRED, srcpath_url);
1401 	    FREE(srcpath_url);
1402 	}
1403 
1404 	fprintf(fp0, "<Ol><Li>%s<Br><Br>\n",
1405 		gettext("Specify permissions below:"));
1406 	fprintf(fp0, "%s:<Br>\n", gettext("Owner:"));
1407 	fprintf(fp0,
1408 		"<Input Type=\"checkbox\" Name=\"mode\" Value=\"IRUSR\" %s> Read<Br>\n",
1409 		(dir_info.st_mode & S_IRUSR) ? "checked" : "");
1410 	fprintf(fp0,
1411 		"<Input Type=\"checkbox\" Name=\"mode\" Value=\"IWUSR\" %s> Write<Br>\n",
1412 		(dir_info.st_mode & S_IWUSR) ? "checked" : "");
1413 	/*
1414 	 * If restricted, only change eXecute permissions on directories.
1415 	 */
1416 	if (!no_change_exec_perms || S_ISDIR(dir_info.st_mode))
1417 	    fprintf(fp0,
1418 		    "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IXUSR\" %s> %s<Br>\n",
1419 		    (dir_info.st_mode & S_IXUSR) ? "checked" : "",
1420 		    S_ISDIR(dir_info.st_mode) ? "Search" : "Execute");
1421 
1422 	fprintf(fp0, "%s %s:<Br>\n", gettext("Group"), group_name);
1423 	fprintf(fp0,
1424 		"<Input Type=\"checkbox\" Name=\"mode\" Value=\"IRGRP\" %s> Read<Br>\n",
1425 		(dir_info.st_mode & S_IRGRP) ? "checked" : "");
1426 	fprintf(fp0,
1427 		"<Input Type=\"checkbox\" Name=\"mode\" Value=\"IWGRP\" %s> Write<Br>\n",
1428 		(dir_info.st_mode & S_IWGRP) ? "checked" : "");
1429 	/*
1430 	 * If restricted, only change eXecute permissions on directories.
1431 	 */
1432 	if (!no_change_exec_perms || S_ISDIR(dir_info.st_mode))
1433 	    fprintf(fp0,
1434 		    "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IXGRP\" %s> %s<Br>\n",
1435 		    (dir_info.st_mode & S_IXGRP) ? "checked" : "",
1436 		    S_ISDIR(dir_info.st_mode) ? "Search" : "Execute");
1437 
1438 	fprintf(fp0, "%s<Br>\n", gettext("Others:"));
1439 	fprintf(fp0,
1440 		"<Input Type=\"checkbox\" Name=\"mode\" Value=\"IROTH\" %s> Read<Br>\n",
1441 		(dir_info.st_mode & S_IROTH) ? "checked" : "");
1442 	fprintf(fp0,
1443 		"<Input Type=\"checkbox\" Name=\"mode\" Value=\"IWOTH\" %s> Write<Br>\n",
1444 		(dir_info.st_mode & S_IWOTH) ? "checked" : "");
1445 	/*
1446 	 * If restricted, only change eXecute permissions on directories.
1447 	 */
1448 	if (!no_change_exec_perms || S_ISDIR(dir_info.st_mode))
1449 	    fprintf(fp0,
1450 		    "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IXOTH\" %s> %s<Br>\n",
1451 		    (dir_info.st_mode & S_IXOTH) ? "checked" : "",
1452 		    S_ISDIR(dir_info.st_mode) ? "Search" : "Execute");
1453 
1454 	fprintf(fp0,
1455 		"<Br>\n<Li><Input Type=\"submit\" Value=\"Submit\">  %s %s %s.\n</Ol>\n</Form>\n",
1456 		gettext("form to permit"),
1457 		S_ISDIR(dir_info.st_mode) ? "directory" : "file",
1458 		user_filename);
1459 	fprintf(fp0, "</Body></Html>");
1460 	LYCloseTempFP(fp0);
1461 
1462 	LYforce_no_cache = TRUE;
1463 	code = PERMIT_FORM_RESULT;	/* Special flag for LYMainLoop */
1464 
1465     } else {			/* The form being activated. */
1466 	mode_t new_mode = 0;
1467 
1468 	/*
1469 	 * Make sure we have a valid set-permission file comparison string
1470 	 * loaded via a previous call with srcpath != NULL.  - KW
1471 	 */
1472 	if (isBEmpty(LYValidPermitFile)) {
1473 	    if (LYCursesON)
1474 		HTAlert(INVALID_PERMIT_URL);
1475 	    else
1476 		fprintf(stderr, "%s\n", INVALID_PERMIT_URL);
1477 	    CTRACE((tfp, "permit_location: called for <%s>.\n",
1478 		    (destpath ?
1479 		     destpath : "NULL URL pointer")));
1480 	    return code;
1481 	}
1482 	cp = destpath;
1483 	while (*cp != '\0' && *cp != '?') {	/* Find filename */
1484 	    cp++;
1485 	}
1486 	if (*cp == '\0') {
1487 	    return (code);	/* Nothing to permit. */
1488 	}
1489 	*cp++ = '\0';		/* Null terminate file name and
1490 				   start working on the masks. */
1491 
1492 	/* Will now operate only on filename part. */
1493 	if ((destpath = HTURLPath_toFile(destpath, TRUE, FALSE)) == 0)
1494 	    return (code);
1495 	if (strlen(destpath) >= LY_MAXPATH) {
1496 	    FREE(destpath);
1497 	    return (code);
1498 	}
1499 	strcpy(tmpdst, destpath);
1500 	FREE(destpath);
1501 	destpath = tmpdst;
1502 
1503 	/*
1504 	 * Make sure that the file string is the one from the last displayed
1505 	 * File Permissions menu.  - KW
1506 	 */
1507 	if (strcmp(destpath, LYValidPermitFile->str)) {
1508 	    if (LYCursesON)
1509 		HTAlert(INVALID_PERMIT_URL);
1510 	    else
1511 		fprintf(stderr, "%s\n", INVALID_PERMIT_URL);
1512 	    CTRACE((tfp, "permit_location: called for file '%s'.\n",
1513 		    destpath));
1514 	    return code;
1515 	}
1516 
1517 	/*
1518 	 * A couple of sanity tests.
1519 	 */
1520 	destpath = strip_trailing_slash(destpath);
1521 	if (!ok_stat(destpath, &dir_info)
1522 	    || !ok_file_or_dir(&dir_info)) {
1523 	    return code;
1524 	}
1525 
1526 	/*
1527 	 * Cycle over permission strings.
1528 	 */
1529 	while (*cp != '\0') {
1530 	    char *cr = cp;
1531 
1532 	    while (*cr != '\0' && *cr != '&') {		/* GET data split by '&'. */
1533 		cr++;
1534 	    }
1535 	    if (*cr != '\0') {
1536 		*cr++ = '\0';
1537 	    }
1538 	    if (StrNCmp(cp, "mode=", 5) == 0) {		/* Magic string. */
1539 		long mask = permit_bits(cp + 5);
1540 
1541 		if (mask != 0) {
1542 		    /*
1543 		     * If restricted, only change eXecute permissions on
1544 		     * directories.
1545 		     */
1546 		    if (!no_change_exec_perms
1547 			|| StrChr(cp + 5, 'X') == NULL
1548 			|| S_ISDIR(dir_info.st_mode)) {
1549 			new_mode |= (mode_t) mask;
1550 		    }
1551 		} else {
1552 		    HTAlert(gettext("Invalid mode format."));
1553 		    return code;
1554 		}
1555 	    } else {
1556 		HTAlert(gettext("Invalid syntax format."));
1557 		return code;
1558 	    }
1559 
1560 	    cp = cr;
1561 	}
1562 
1563 	/*
1564 	 * Call chmod().
1565 	 */
1566 	code = 1;
1567 	if ((program = HTGetProgramPath(ppCHMOD)) != NULL) {
1568 	    char **args;
1569 	    char amode[10];
1570 	    char *tmpbuf = NULL;
1571 
1572 	    HTSprintf0(&tmpbuf, "chmod %.4o %s", (unsigned) new_mode, destpath);
1573 	    sprintf(amode, "%.4o", (unsigned) new_mode);
1574 	    args = make_argv("chmod",
1575 			     amode,
1576 			     destpath,
1577 			     NULL);
1578 	    if (LYExecv(program, args, tmpbuf) <= 0) {
1579 		code = -1;
1580 	    }
1581 	    FREE(tmpbuf);
1582 	    free_argv(args);
1583 	} else {
1584 	    if (chmod(destpath, new_mode) < 0) {
1585 		code = -1;
1586 	    }
1587 	    CTRACE((tfp, "builtin chmod %.4o ->%d\n\t%s\n",
1588 		    (unsigned) new_mode, code, destpath));
1589 	}
1590 	if (code == 1)
1591 	    LYforce_no_cache = TRUE;	/* Force update of dired listing. */
1592     }
1593 #endif /* !UNIX */
1594     return code;
1595 }
1596 #endif /* OK_PERMIT */
1597 
1598 /*
1599  * Display or remove a tag from a given link.
1600  */
tagflag(int flag,int cur)1601 void tagflag(int flag,
1602 	     int cur)
1603 {
1604     if (nlinks > 0) {
1605 	LYmove(links[cur].ly, 2);
1606 	lynx_stop_reverse();
1607 	if (flag == TRUE) {
1608 	    LYaddch('+');
1609 	} else {
1610 	    LYaddch(' ');
1611 	}
1612 
1613 #if defined(FANCY_CURSES) || defined(USE_SLANG)
1614 	if (!LYShowCursor)
1615 	    LYHideCursor();	/* get cursor out of the way */
1616 	else
1617 #endif /* FANCY CURSES || USE_SLANG */
1618 	    /*
1619 	     * Never hide the cursor if there's no FANCY CURSES.
1620 	     */
1621 	    LYmove(links[cur].ly, links[cur].lx);
1622 
1623 	LYrefresh();
1624     }
1625 }
1626 
1627 /*
1628  * Handle DIRED tags.
1629  */
showtags(HTList * t)1630 void showtags(HTList *t)
1631 {
1632     int i;
1633     HTList *s;
1634     char *name;
1635 
1636     for (i = 0; i < nlinks; i++) {
1637 	s = t;
1638 	while ((name = (char *) HTList_nextObject(s)) != NULL) {
1639 	    if (!strcmp(links[i].lname, name)) {
1640 		tagflag(TRUE, i);
1641 		break;
1642 	    }
1643 	}
1644     }
1645 }
1646 
DirectoryOf(char * pathname)1647 static char *DirectoryOf(char *pathname)
1648 {
1649     char *result = 0;
1650     char *leaf;
1651 
1652     StrAllocCopy(result, pathname);
1653     leaf = LYPathLeaf(result);
1654 
1655     if (leaf != result) {
1656 	const char *result1 = 0;
1657 
1658 	*leaf = '\0';
1659 	if (!LYisRootPath(result))
1660 	    LYTrimPathSep(result);
1661 	result1 = wwwName(result);
1662 	StrAllocCopy(result, result1);
1663     }
1664     return result;
1665 }
1666 
1667 #ifdef __DJGPP__
1668 /*
1669  * Convert filenames to acceptable 8+3 names when necessary.  Make a copy of
1670  * the parameter if we must modify it.
1671  */
LYonedot(char * line)1672 static char *LYonedot(char *line)
1673 {
1674     char *dot;
1675     static char line1[LY_MAXPATH];
1676 
1677     if (pathconf(line, _PC_NAME_MAX) <= 12) {
1678 	LYStrNCpy(line1, line, sizeof(line1) - 1);
1679 	for (;;) {
1680 	    if ((dot = strrchr(line1, '.')) == 0
1681 		|| LYLastPathSep(dot) != 0) {
1682 		break;
1683 	    } else if (strlen(dot) == 1) {
1684 		*dot = 0;
1685 	    } else {
1686 		*dot = '_';
1687 	    }
1688 	}
1689 	return (line1);
1690     }
1691     return (line);
1692 }
1693 #else
1694 #define LYonedot(path) path
1695 #endif /*  __DJGPP__ */
1696 
match_op(const char * prefix,char * data)1697 static char *match_op(const char *prefix,
1698 		      char *data)
1699 {
1700     size_t len = strlen(prefix);
1701 
1702     if (!StrNCmp("LYNXDIRED://", data, 12)
1703 	&& !strncasecomp(prefix, data + 12, (int) len)) {
1704 	len += 12;
1705 #if defined(USE_DOS_DRIVES)
1706 	if (data[len] == '/') {	/* this is normal */
1707 	    len++;
1708 	}
1709 #endif
1710 	return data + len;
1711     }
1712     return 0;
1713 }
1714 
1715 /*
1716  * Construct the appropriate system command taking care to escape all path
1717  * references to avoid spoofing the shell.
1718  */
build_command(char * line,char * dirName,char * arg)1719 static char *build_command(char *line,
1720 			   char *dirName,
1721 			   char *arg)
1722 {
1723     char *buffer = NULL;
1724     const char *program;
1725     const char *tar_path = HTGetProgramPath(ppTAR);
1726 
1727     if ((arg = match_op("DECOMPRESS", line)) != 0) {
1728 #define FMT_UNCOMPRESS "%s %s"
1729 	if ((program = HTGetProgramPath(ppUNCOMPRESS)) != NULL) {
1730 	    HTAddParam(&buffer, FMT_UNCOMPRESS, 1, program);
1731 	    HTAddParam(&buffer, FMT_UNCOMPRESS, 2, arg);
1732 	    HTEndParam(&buffer, FMT_UNCOMPRESS, 2);
1733 	}
1734 	return buffer;
1735     }
1736 #if defined(OK_UUDECODE) && !defined(ARCHIVE_ONLY)
1737     if ((arg = match_op("UUDECODE", line)) != 0) {
1738 #define FMT_UUDECODE "%s %s"
1739 	if ((program = HTGetProgramPath(ppUUDECODE)) != NULL) {
1740 	    HTAddParam(&buffer, FMT_UUDECODE, 1, program);
1741 	    HTAddParam(&buffer, FMT_UUDECODE, 2, arg);
1742 	    HTEndParam(&buffer, FMT_UUDECODE, 2);
1743 	    HTAlert(gettext("Warning!  UUDecoded file will exist in the directory you started Lynx."));
1744 	}
1745 	return buffer;
1746     }
1747 #endif /* OK_UUDECODE && !ARCHIVE_ONLY */
1748 
1749 #ifdef OK_TAR
1750     if (tar_path != NULL) {
1751 # ifndef ARCHIVE_ONLY
1752 #  ifdef OK_GZIP
1753 	if ((arg = match_op("UNTAR_GZ", line)) != 0) {
1754 #define FMT_UNTAR_GZ "cd %s; %s -qdc %s |  %s %s %s"
1755 	    if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
1756 		dirName = DirectoryOf(arg);
1757 		HTAddParam(&buffer, FMT_UNTAR_GZ, 1, dirName);
1758 		HTAddParam(&buffer, FMT_UNTAR_GZ, 2, program);
1759 		HTAddParam(&buffer, FMT_UNTAR_GZ, 3, arg);
1760 		HTAddParam(&buffer, FMT_UNTAR_GZ, 4, tar_path);
1761 		HTAddToCmd(&buffer, FMT_UNTAR_GZ, 5, TAR_DOWN_OPTIONS);
1762 		HTAddToCmd(&buffer, FMT_UNTAR_GZ, 6, TAR_PIPE_OPTIONS);
1763 		HTEndParam(&buffer, FMT_UNTAR_GZ, 6);
1764 	    }
1765 	    return buffer;
1766 	}
1767 #  endif /* OK_GZIP */
1768 	if ((arg = match_op("UNTAR_Z", line)) != 0) {
1769 #define FMT_UNTAR_Z "cd %s; %s %s |  %s %s %s"
1770 	    if ((program = HTGetProgramPath(ppZCAT)) != NULL) {
1771 		dirName = DirectoryOf(arg);
1772 		HTAddParam(&buffer, FMT_UNTAR_Z, 1, dirName);
1773 		HTAddParam(&buffer, FMT_UNTAR_Z, 2, program);
1774 		HTAddParam(&buffer, FMT_UNTAR_Z, 3, arg);
1775 		HTAddParam(&buffer, FMT_UNTAR_Z, 4, tar_path);
1776 		HTAddToCmd(&buffer, FMT_UNTAR_Z, 5, TAR_DOWN_OPTIONS);
1777 		HTAddToCmd(&buffer, FMT_UNTAR_Z, 6, TAR_PIPE_OPTIONS);
1778 		HTEndParam(&buffer, FMT_UNTAR_Z, 6);
1779 	    }
1780 	    return buffer;
1781 	}
1782 	if ((arg = match_op("UNTAR", line)) != 0) {
1783 #define FMT_UNTAR "cd %s; %s %s %s"
1784 	    dirName = DirectoryOf(arg);
1785 	    HTAddParam(&buffer, FMT_UNTAR, 1, dirName);
1786 	    HTAddParam(&buffer, FMT_UNTAR, 2, tar_path);
1787 	    HTAddToCmd(&buffer, FMT_UNTAR, 3, TAR_DOWN_OPTIONS);
1788 	    HTAddParam(&buffer, FMT_UNTAR, 4, arg);
1789 	    HTEndParam(&buffer, FMT_UNTAR, 4);
1790 	    return buffer;
1791 	}
1792 # endif	/* !ARCHIVE_ONLY */
1793 
1794 # ifdef OK_GZIP
1795 	if ((arg = match_op("TAR_GZ", line)) != 0) {
1796 #define FMT_TAR_GZ "cd %s; %s %s %s %s | %s -qc >%s%s"
1797 	    if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
1798 		dirName = DirectoryOf(arg);
1799 		HTAddParam(&buffer, FMT_TAR_GZ, 1, dirName);
1800 		HTAddParam(&buffer, FMT_TAR_GZ, 2, tar_path);
1801 		HTAddToCmd(&buffer, FMT_TAR_GZ, 3, TAR_UP_OPTIONS);
1802 		HTAddToCmd(&buffer, FMT_TAR_GZ, 4, TAR_PIPE_OPTIONS);
1803 		HTAddParam(&buffer, FMT_TAR_GZ, 5, LYPathLeaf(arg));
1804 		HTAddParam(&buffer, FMT_TAR_GZ, 6, program);
1805 		HTAddParam(&buffer, FMT_TAR_GZ, 7, LYonedot(LYPathLeaf(arg)));
1806 		HTAddParam(&buffer, FMT_TAR_GZ, 8, EXT_TAR_GZ);
1807 		HTEndParam(&buffer, FMT_TAR_GZ, 8);
1808 	    }
1809 	    return buffer;
1810 	}
1811 # endif	/* OK_GZIP */
1812 
1813 	if ((arg = match_op("TAR_Z", line)) != 0) {
1814 #define FMT_TAR_Z "cd %s; %s %s %s %s | %s >%s%s"
1815 	    if ((program = HTGetProgramPath(ppCOMPRESS)) != NULL) {
1816 		dirName = DirectoryOf(arg);
1817 		HTAddParam(&buffer, FMT_TAR_Z, 1, dirName);
1818 		HTAddParam(&buffer, FMT_TAR_Z, 2, tar_path);
1819 		HTAddToCmd(&buffer, FMT_TAR_Z, 3, TAR_UP_OPTIONS);
1820 		HTAddToCmd(&buffer, FMT_TAR_Z, 4, TAR_PIPE_OPTIONS);
1821 		HTAddParam(&buffer, FMT_TAR_Z, 5, LYPathLeaf(arg));
1822 		HTAddParam(&buffer, FMT_TAR_Z, 6, program);
1823 		HTAddParam(&buffer, FMT_TAR_Z, 7, LYonedot(LYPathLeaf(arg)));
1824 		HTAddParam(&buffer, FMT_TAR_Z, 8, EXT_TAR_Z);
1825 		HTEndParam(&buffer, FMT_TAR_Z, 8);
1826 	    }
1827 	    return buffer;
1828 	}
1829 
1830 	if ((arg = match_op("TAR", line)) != 0) {
1831 #define FMT_TAR "cd %s; %s %s %s %s.tar %s"
1832 	    dirName = DirectoryOf(arg);
1833 	    HTAddParam(&buffer, FMT_TAR, 1, dirName);
1834 	    HTAddParam(&buffer, FMT_TAR, 2, tar_path);
1835 	    HTAddToCmd(&buffer, FMT_TAR, 3, TAR_UP_OPTIONS);
1836 	    HTAddToCmd(&buffer, FMT_TAR, 4, TAR_FILE_OPTIONS);
1837 	    HTAddParam(&buffer, FMT_TAR, 5, LYonedot(LYPathLeaf(arg)));
1838 	    HTAddParam(&buffer, FMT_TAR, 6, LYPathLeaf(arg));
1839 	    HTEndParam(&buffer, FMT_TAR, 6);
1840 	    return buffer;
1841 	}
1842     }
1843 #endif /* OK_TAR */
1844 
1845 #ifdef OK_GZIP
1846     if ((arg = match_op("GZIP", line)) != 0) {
1847 #define FMT_GZIP "%s -q %s"
1848 	if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
1849 	    HTAddParam(&buffer, FMT_GZIP, 1, program);
1850 	    HTAddParam(&buffer, FMT_GZIP, 2, arg);
1851 	    HTEndParam(&buffer, FMT_GZIP, 2);
1852 	}
1853 	return buffer;
1854     }
1855 #ifndef ARCHIVE_ONLY
1856     if ((arg = match_op("UNGZIP", line)) != 0) {
1857 #define FMT_UNGZIP "%s -d %s"
1858 	if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
1859 	    HTAddParam(&buffer, FMT_UNGZIP, 1, program);
1860 	    HTAddParam(&buffer, FMT_UNGZIP, 2, arg);
1861 	    HTEndParam(&buffer, FMT_UNGZIP, 2);
1862 	}
1863 	return buffer;
1864     }
1865 #endif /* !ARCHIVE_ONLY */
1866 #endif /* OK_GZIP */
1867 
1868 #ifdef OK_ZIP
1869     if ((arg = match_op("ZIP", line)) != 0) {
1870 #define FMT_ZIP "cd %s; %s -rq %s.zip %s"
1871 	if ((program = HTGetProgramPath(ppZIP)) != NULL) {
1872 	    dirName = DirectoryOf(arg);
1873 	    HTAddParam(&buffer, FMT_ZIP, 1, dirName);
1874 	    HTAddParam(&buffer, FMT_ZIP, 2, program);
1875 	    HTAddParam(&buffer, FMT_ZIP, 3, LYonedot(LYPathLeaf(arg)));
1876 	    HTAddParam(&buffer, FMT_ZIP, 4, LYPathLeaf(arg));
1877 	    HTEndParam(&buffer, FMT_ZIP, 4);
1878 	}
1879 	return buffer;
1880     }
1881 #if !defined(ARCHIVE_ONLY)
1882     if ((arg = match_op("UNZIP", line)) != 0) {
1883 #define FMT_UNZIP "cd %s; %s -q %s"
1884 	if ((program = HTGetProgramPath(ppUNZIP)) != NULL) {
1885 	    dirName = DirectoryOf(arg);
1886 	    HTAddParam(&buffer, FMT_UNZIP, 1, dirName);
1887 	    HTAddParam(&buffer, FMT_UNZIP, 2, program);
1888 	    HTAddParam(&buffer, FMT_UNZIP, 3, arg);
1889 	    HTEndParam(&buffer, FMT_UNZIP, 3);
1890 	}
1891 	return buffer;
1892     }
1893 # endif	/* !ARCHIVE_ONLY */
1894 #endif /* OK_ZIP */
1895 
1896     if ((arg = match_op("COMPRESS", line)) != 0) {
1897 #define FMT_COMPRESS "%s %s"
1898 	if ((program = HTGetProgramPath(ppCOMPRESS)) != NULL) {
1899 	    HTAddParam(&buffer, FMT_COMPRESS, 1, program);
1900 	    HTAddParam(&buffer, FMT_COMPRESS, 2, arg);
1901 	    HTEndParam(&buffer, FMT_COMPRESS, 2);
1902 	}
1903 	return buffer;
1904     }
1905 
1906     return NULL;
1907 }
1908 
1909 /*
1910  * Perform file management operations for LYNXDIRED URL's.  Attempt to be
1911  * consistent.  These are (pseudo) URLs - i.e., they should be in URL syntax:
1912  * some bytes will be URL-escaped with '%'.  This is necessary because these
1913  * (pseudo) URLs will go through some of the same kinds of interpretations and
1914  * mutilations as real ones:  HTParse, stripping off #fragments etc.  (Some
1915  * access schemes currently have special rules about not escaping parsing '#'
1916  * "the URL way" built into HTParse, but that doesn't look like a clean way.)
1917  */
local_dired(DocInfo * doc)1918 int local_dired(DocInfo *doc)
1919 {
1920     char *line_url;		/* will point to doc's address, which is a URL */
1921     char *line = NULL;		/* same as line_url, but HTUnEscaped, will be alloced */
1922     char *arg = NULL;		/* ...will point into line[] */
1923     char *tp = NULL;
1924     char *tmpbuf = NULL;
1925     char *buffer = NULL;
1926     char *dirName = NULL;
1927     BOOL do_pop_doc = TRUE;
1928 
1929     line_url = doc->address;
1930     CTRACE((tfp, "local_dired: called for <%s>.\n",
1931 	    (line_url
1932 	     ? line_url
1933 	     : gettext("NULL URL pointer"))));
1934     HTUnEscapeSome(line_url, "/");	/* don't mess too much with *doc */
1935 
1936     StrAllocCopy(line, line_url);
1937     HTUnEscape(line);		/* _file_ (not URL) syntax, for those functions
1938 				   that need it.  Don't forget to FREE it. */
1939     if (match_op("CHDIR", line) != 0) {
1940 #ifdef SUPPORT_CHDIR
1941 	handle_LYK_CHDIR();
1942 	do_pop_doc = FALSE;
1943 #endif
1944 	arg = 0;		/* do something to avoid cc's complaints */
1945     } else if ((arg = match_op("NEW_FILE", line)) != 0) {
1946 	if (create_file(arg) > 0)
1947 	    LYforce_no_cache = TRUE;
1948     } else if ((arg = match_op("NEW_FOLDER", line)) != 0) {
1949 	if (create_directory(arg) > 0)
1950 	    LYforce_no_cache = TRUE;
1951 #ifdef OK_INSTALL
1952     } else if ((arg = match_op("INSTALL_SRC", line)) != 0) {
1953 	local_install(NULL, arg, &tp);
1954 	if (tp) {
1955 	    FREE(doc->address);
1956 	    doc->address = tp;
1957 	}
1958 	FREE(line);
1959 	return 0;
1960     } else if ((arg = match_op("INSTALL_DEST", line)) != 0) {
1961 	local_install(arg, NULL, &tp);
1962 	LYpop(doc);
1963 #endif /* OK_INSTALL */
1964     } else if ((arg = match_op("MODIFY_NAME", line)) != 0) {
1965 	if (modify_name(arg) > 0)
1966 	    LYforce_no_cache = TRUE;
1967     } else if ((arg = match_op("MODIFY_LOCATION", line)) != 0) {
1968 	if (modify_location(arg) > 0)
1969 	    LYforce_no_cache = TRUE;
1970     } else if ((arg = match_op("MOVE_TAGGED", line_url)) != 0) {
1971 	if (modify_tagged(arg) > 0)
1972 	    LYforce_no_cache = TRUE;
1973 #ifdef OK_PERMIT
1974     } else if ((arg = match_op("PERMIT_SRC", line)) != 0) {
1975 	permit_location(NULL, arg, &tp);
1976 	if (tp) {
1977 	    /*
1978 	     * One of the checks may have failed.
1979 	     */
1980 	    FREE(doc->address);
1981 	    doc->address = tp;
1982 	}
1983 	FREE(line);
1984 	return 0;
1985     } else if ((arg = match_op("PERMIT_LOCATION", line_url)) != 0) {
1986 	permit_location(arg, NULL, &tp);
1987 #endif /* OK_PERMIT */
1988     } else if ((arg = match_op("REMOVE_SINGLE", line)) != 0) {
1989 	if (remove_single(arg) > 0)
1990 	    LYforce_no_cache = TRUE;
1991     } else if (match_op("REMOVE_TAGGED", line) != 0) {
1992 	if (remove_tagged())
1993 	    LYforce_no_cache = TRUE;
1994     } else if (match_op("CLEAR_TAGGED", line) != 0) {
1995 	clear_tags();
1996     } else if ((arg = match_op("UPLOAD", line)) != 0) {
1997 	/*
1998 	 * They're written by LYUpload_options() HTUnEscaped; don't want to
1999 	 * change that for now...  so pass through without more unescaping.
2000 	 * Directory names containing '#' will probably fail.
2001 	 */
2002 	if (LYUpload(line_url))
2003 	    LYforce_no_cache = TRUE;
2004     } else {
2005 	LYTrimPathSep(line);
2006 	if (LYLastPathSep(line) == NULL) {
2007 	    FREE(line);
2008 	    return 0;
2009 	}
2010 
2011 	buffer = build_command(line, dirName, arg);
2012 
2013 	if (buffer != 0) {
2014 	    if ((int) strlen(buffer) < LYcolLimit - 14) {
2015 		HTSprintf0(&tmpbuf, gettext("Executing %s "), buffer);
2016 	    } else {
2017 		HTSprintf0(&tmpbuf,
2018 			   gettext("Executing system command. This might take a while."));
2019 	    }
2020 	    _statusline(tmpbuf);
2021 	    stop_curses();
2022 	    printf("%s\r\n", tmpbuf);
2023 	    LYSystem(buffer);
2024 #ifdef VMS
2025 	    HadVMSInterrupt = FALSE;
2026 #endif /* VMS */
2027 	    start_curses();
2028 	    LYforce_no_cache = TRUE;
2029 	}
2030     }
2031 
2032     FREE(dirName);
2033     FREE(tmpbuf);
2034     FREE(buffer);
2035     FREE(line);
2036     FREE(tp);
2037     if (do_pop_doc)
2038 	LYpop(doc);
2039     return 0;
2040 }
2041 
2042 /*
2043  * Provide a menu of file management options.
2044  */
dired_options(DocInfo * doc,char ** newfile)2045 int dired_options(DocInfo *doc, char **newfile)
2046 {
2047     static char tempfile[LY_MAXPATH];
2048     const char *my_suffix;
2049     char *path = NULL;
2050     char *dir;
2051     lynx_list_item_type *nxt;
2052     struct stat dir_info;
2053     FILE *fp0;
2054     char *dir_url;
2055     char *path_url;
2056     BOOLEAN nothing_tagged;
2057     int count;
2058     struct dired_menu *mp;
2059     char buf[2048];
2060 
2061     if ((fp0 = InternalPageFP(tempfile, FALSE)) == 0)
2062 	return (0);
2063 
2064     /*
2065      * Make the tempfile a URL.
2066      */
2067     LYLocalFileToURL(newfile, tempfile);
2068     LYRegisterUIPage(*newfile, UIP_DIRED_MENU);
2069 
2070     if (doc->link > -1 && doc->link < (nlinks + 1)) {
2071 	path = HTfullURL_toFile(links[doc->link].lname);
2072 	LYTrimPathSep(path);
2073 
2074 	if (!ok_lstat(path, &dir_info)) {
2075 	    LYCloseTempFP(fp0);
2076 	    FREE(path);
2077 	    return 0;
2078 	}
2079 
2080     } else {
2081 	StrAllocCopy(path, "");
2082 	memset(&dir_info, 0, sizeof(dir_info));
2083     }
2084 
2085     dir = HTfullURL_toFile(doc->address);
2086     LYTrimPathSep(dir);
2087 
2088     nothing_tagged = (BOOL) (HTList_isEmpty(tagged));
2089 
2090     BeginInternalPage(fp0, DIRED_MENU_TITLE, DIRED_MENU_HELP);
2091 
2092     fprintf(fp0, "<em>%s</em> %s<br>\n", gettext("Current directory:"), dir);
2093 
2094     if (nothing_tagged) {
2095 	fprintf(fp0, "<em>%s</em> ", gettext("Current selection:"));
2096 	if (strlen(path)) {
2097 	    fprintf(fp0, "%s<p>\n", path);
2098 	} else {
2099 	    fprintf(fp0, "%s.<p>\n", gettext("Nothing currently selected."));
2100 	}
2101     } else {
2102 	/*
2103 	 * Write out number of tagged items, and names of first few of them
2104 	 * relative to current (in the DIRED sense) directory.
2105 	 */
2106 	int n = HTList_count(tagged);
2107 	char *cp1 = NULL;
2108 	char *cd = NULL;
2109 	int i, m;
2110 
2111 #define NUM_TAGS_TO_WRITE 10
2112 	fprintf(fp0, "<em>%s</em> %d %s",
2113 		gettext("Current selection:"),
2114 		n, ((n == 1)
2115 		    ? gettext("tagged item:")
2116 		    : gettext("tagged items:")));
2117 	StrAllocCopy(cd, doc->address);
2118 	HTUnEscapeSome(cd, "/");
2119 	LYAddHtmlSep(&cd);
2120 	m = (n < NUM_TAGS_TO_WRITE) ? n : NUM_TAGS_TO_WRITE;
2121 	for (i = 1; i <= m; i++) {
2122 	    cp1 = HTRelative((char *) HTList_objectAt(tagged, i - 1),
2123 			     (*cd ? cd : "file://localhost"));
2124 	    HTUnEscape(cp1);
2125 	    LYEntify(&cp1, TRUE);	/* _should_ do this everywhere... */
2126 	    fprintf(fp0, "%s<br>\n&nbsp;&nbsp;&nbsp;%s",
2127 		    (i == 1 ? "" : " ,"), cp1);
2128 	    FREE(cp1);
2129 	}
2130 	if (n > m) {
2131 	    fprintf(fp0, " , ...");
2132 	}
2133 	fprintf(fp0, "<p>\n");
2134 	FREE(cd);
2135     }
2136 
2137     /*
2138      * If menu_head is NULL then use defaults and link them together now.
2139      */
2140     if (menu_head == NULL) {
2141 	for (mp = defmenu; GetDiredHref(mp) != NULL; mp++)
2142 	    mp->next = (mp + 1);
2143 	(--mp)->next = NULL;
2144 	menu_head = defmenu;
2145     }
2146 
2147     for (mp = menu_head; mp != NULL; mp = mp->next) {
2148 	if (mp->cond != DE_TAG && !nothing_tagged)
2149 	    continue;
2150 	if (mp->cond == DE_TAG && nothing_tagged)
2151 	    continue;
2152 	if (mp->cond == DE_DIR &&
2153 	    (!*path || !S_ISDIR(dir_info.st_mode)))
2154 	    continue;
2155 	if (mp->cond == DE_FILE &&
2156 	    (!*path || !S_ISREG(dir_info.st_mode)))
2157 	    continue;
2158 #ifdef S_IFLNK
2159 	if (mp->cond == DE_SYMLINK &&
2160 	    (!*path || !S_ISLNK(dir_info.st_mode)))
2161 	    continue;
2162 #endif
2163 	my_suffix = GetDiredSuffix(mp);
2164 	if (non_empty(my_suffix) &&
2165 	    (strlen(path) < strlen(my_suffix) ||
2166 	     strcmp(my_suffix, &path[(strlen(path) - strlen(my_suffix))]) != 0))
2167 	    continue;
2168 	dir_url = HTEscape(dir, URL_PATH);
2169 	path_url = HTEscape(path, URL_PATH);
2170 	fprintf(fp0, "<a href=\"%s",
2171 		render_item(GetDiredHref(mp),
2172 			    path_url, dir_url, buf, sizeof(buf), YES));
2173 	fprintf(fp0, "\">%s</a> ",
2174 		render_item(GetDiredLink(mp),
2175 			    path, dir, buf, sizeof(buf), NO));
2176 	fprintf(fp0, "%s<br>\n",
2177 		render_item(GetDiredRest(mp),
2178 			    path, dir, buf, sizeof(buf), NO));
2179 	FREE(dir_url);
2180 	FREE(path_url);
2181     }
2182     FREE(path);
2183 
2184     if (uploaders != NULL) {
2185 	fprintf(fp0, "<p>Upload to current directory:<p>\n");
2186 	for (count = 0, nxt = uploaders;
2187 	     nxt != NULL;
2188 	     nxt = nxt->next, count++) {
2189 	    fprintf(fp0,
2190 		    "<a href=\"LYNXDIRED://UPLOAD=%d/TO=%s\"> %s </a><br>\n",
2191 		    count, dir, nxt->name);
2192 	}
2193     }
2194     FREE(dir);
2195 
2196     EndInternalPage(fp0);
2197     LYCloseTempFP(fp0);
2198 
2199     LYforce_no_cache = TRUE;
2200 
2201     return (0);
2202 }
2203 
2204 /*
2205  * Check DIRED filename, return true on success
2206  */
get_filename(const char * prompt,bstring ** bufp)2207 static int get_filename(const char *prompt,
2208 			bstring **bufp)
2209 {
2210     char *cp;
2211 
2212     _statusline(prompt);
2213 
2214     (void) LYgetBString(bufp, FALSE, 0, NORECALL);
2215     if (strstr((*bufp)->str, "../") != NULL) {
2216 	HTAlert(gettext("Illegal filename; request ignored."));
2217 	return FALSE;
2218     } else if (no_dotfiles || !show_dotfiles) {
2219 	cp = LYLastPathSep((*bufp)->str);	/* find last slash */
2220 	if (cp)
2221 	    cp += 1;
2222 	else
2223 	    cp = (*bufp)->str;
2224 	if (*cp == '.') {
2225 	    HTAlert(gettext("Illegal filename; request ignored."));
2226 	    return FALSE;
2227 	}
2228     }
2229     return !isBEmpty((*bufp));
2230 }
2231 
2232 #ifdef OK_INSTALL
2233 
2234 #define LYEXECV_MAX_ARGC 15
2235 /* these are quasi-constant once they have been allocated: */
2236 static char **install_argp = NULL;	/* args for execv install */
2237 static char *install_path = NULL;	/* auxiliary */
2238 
2239 #ifdef LY_FIND_LEAKS
clear_install_path(void)2240 static void clear_install_path(void)
2241 {
2242     FREE(install_argp);
2243     FREE(install_path);
2244 }
2245 #endif /* LY_FIND_LEAKS */
2246 
2247 /*
2248  * Fill in args array for execv (or execvp etc.) call, after first allocating
2249  * it if necessary.  No fancy parsing, cmd_args is just split at spaces.  Leave
2250  * room for reserve additional args to be added by caller.
2251  *
2252  * On success *argvp points to new args vector, *pathp is auxiliary.  On
2253  * success returns index of next argument, else -1.  This is generic enough
2254  * that it could be used for other calls than install, except the atexit call.
2255  * Go through this trouble for install because INSTALL_ARGS may be significant,
2256  * and someone may configure it with more than one significant flags.  - kw
2257  */
fill_argv_for_execv(char *** argvp,char ** pathp,char * cmd_path,const char * cmd_args,int reserve)2258 static int fill_argv_for_execv(char ***argvp,
2259 			       char **pathp,
2260 			       char *cmd_path,
2261 			       const char *cmd_args,
2262 			       int reserve)
2263 {
2264     int n = 0;
2265 
2266     char **args;
2267     char *cp;
2268 
2269     if (*argvp == NULL) {
2270 	*argvp = typecallocn(char *, LYEXECV_MAX_ARGC + 1);
2271 
2272 	if (!*argvp)
2273 	    return (-1);
2274 #ifdef LY_FIND_LEAKS
2275 	atexit(clear_install_path);
2276 #endif
2277     }
2278     args = *argvp;
2279     args[n++] = cmd_path;
2280     if (cmd_args) {
2281 	StrAllocCopy(*pathp, cmd_args);
2282 	cp = strtok(*pathp, " ");
2283 	if (cp) {
2284 	    while (cp && (n < LYEXECV_MAX_ARGC - reserve)) {
2285 		args[n++] = cp;
2286 		cp = strtok(NULL, " ");
2287 	    }
2288 	    if (cp && (n >= LYEXECV_MAX_ARGC - reserve)) {
2289 		CTRACE((tfp, "Too many args for '%s' in '%s'!\n",
2290 			NONNULL(cmd_path), cmd_args));
2291 		return (-1);
2292 	    }
2293 	} else {
2294 	    args[n++] = *pathp;
2295 	}
2296     }
2297     args[n] = (char *) 0;
2298     return (n);
2299 }
2300 
2301 /*
2302  * Install the specified file or directory.
2303  */
local_install(char * destpath,char * srcpath,char ** newpath)2304 BOOLEAN local_install(char *destpath,
2305 		      char *srcpath,
2306 		      char **newpath)
2307 {
2308     char *tmpbuf = NULL;
2309     static char savepath[DIRED_MAXBUF];		/* This will be the link that
2310 
2311 						   is to be installed. */
2312     struct stat dir_info;
2313     char **args;
2314     HTList *tag;
2315     char *cp = NULL;
2316     char *tmpdest = NULL;
2317     int count = 0;
2318     int n = 0;			/* indices into 'args[]' */
2319     static int src = -1;
2320     const char *program;
2321 
2322     if ((program = HTGetProgramPath(ppINSTALL)) == NULL) {
2323 	HTAlert(gettext("Install in the selected directory not permitted."));
2324 	return 0;
2325     }
2326 
2327     /*
2328      * Determine the status of the selected item.
2329      */
2330     if (srcpath) {
2331 	srcpath = strip_trailing_slash(srcpath);
2332 	if (is_url(srcpath)) {
2333 	    char *local_src = HTfullURL_toFile(srcpath);
2334 
2335 	    if (!ok_localname(savepath, local_src)) {
2336 		FREE(local_src);
2337 		return 0;
2338 	    }
2339 	    FREE(local_src);
2340 	} else if (!HTList_isEmpty(tagged) &&
2341 		   srcpath[0] == '\0') {
2342 	    savepath[0] = '\0';	/* will always use tagged list - kw */
2343 	} else if (!ok_localname(savepath, srcpath)) {
2344 	    return 0;
2345 	}
2346 	LYforce_no_cache = TRUE;
2347 	LYLocalFileToURL(newpath, Home_Dir());
2348 	LYAddHtmlSep(newpath);
2349 	StrAllocCat(*newpath, INSTALLDIRS_FILE);
2350 	LYRegisterUIPage(*newpath, UIP_INSTALL);
2351 	return 0;
2352     }
2353 
2354     /* deal with ~/ or /~/ at the beginning - kw */
2355     if (LYIsTilde(destpath[0]) &&
2356 	(LYIsPathSep(destpath[1]) || destpath[1] == '\0')) {
2357 	cp = &destpath[1];
2358     } else if (LYIsPathSep(destpath[0]) && LYIsTilde(destpath[1]) &&
2359 	       (LYIsPathSep(destpath[2]) || destpath[2] == '\0')) {
2360 	cp = &destpath[2];
2361     }
2362     if (cp) {
2363 	/* If found, allocate new string, make destpath point to it - kw */
2364 	StrAllocCopy(tmpdest, Home_Dir());
2365 	if (cp[0] && cp[1]) {
2366 	    LYAddPathSep(&tmpdest);
2367 	    StrAllocCat(tmpdest, cp + 1);
2368 	}
2369 	destpath = tmpdest;
2370     }
2371 
2372     destpath = strip_trailing_slash(destpath);
2373 
2374     if (!ok_stat(destpath, &dir_info)) {
2375 	FREE(tmpdest);
2376 	return 0;
2377     } else if (!S_ISDIR(dir_info.st_mode)) {
2378 	HTAlert(gettext("The selected item is not a directory!  Request ignored."));
2379 	FREE(tmpdest);
2380 	return 0;
2381     } else if (0 /*directory not writable */ ) {
2382 	HTAlert(gettext("Install in the selected directory not permitted."));
2383 	FREE(tmpdest);
2384 	return 0;
2385     }
2386 
2387     statusline(gettext("Just a moment, ..."));
2388 
2389     /* fill in the fixed args, if not already done - kw */
2390     if (src > 0 && install_argp) {
2391 	n = src;
2392 	n++;
2393     } else {
2394 	n = fill_argv_for_execv(&install_argp, &install_path,
2395 				"install",
2396 #ifdef INSTALL_ARGS
2397 				INSTALL_ARGS,
2398 #else
2399 				NULL,
2400 #endif /* INSTALL_ARGS */
2401 				2);
2402 	if (n <= 0) {
2403 	    src = 0;
2404 	    HTAlert(gettext("Error building install args"));
2405 	    FREE(tmpdest);
2406 	    return 0;
2407 	}
2408 	src = n++;
2409     }
2410     args = install_argp;
2411 
2412     args[n++] = destpath;
2413     args[n] = (char *) 0;
2414     tag = tagged;
2415 
2416     if (HTList_isEmpty(tagged)) {
2417 	/* simplistic detection of identical src and dest - kw */
2418 	if (!strcmp(savepath, destpath)) {
2419 	    HTUserMsg2(gettext("Source and target are the same: %s"),
2420 		       savepath);
2421 	    FREE(tmpdest);
2422 	    return (-1);	/* don't do it */
2423 	} else if (!StrNCmp(savepath, destpath, strlen(destpath)) &&
2424 		   LYIsPathSep(savepath[strlen(destpath)]) &&
2425 		   LYLastPathSep(savepath + strlen(destpath) + 1) == 0) {
2426 	    HTUserMsg2(gettext("Already in target directory: %s"),
2427 		       savepath);
2428 	    FREE(tmpdest);
2429 	    return 0;		/* don't do it */
2430 	}
2431 	args[src] = savepath;
2432 	HTSprintf0(&tmpbuf, "install %s in %s", savepath, destpath);
2433 	if (LYExecv(program, args, tmpbuf) <= 0) {
2434 	    FREE(tmpbuf);
2435 	    FREE(tmpdest);
2436 	    return (-1);
2437 	}
2438 	count++;
2439     } else {
2440 	char *name;
2441 
2442 	HTSprintf0(&tmpbuf, "install in %s", destpath);
2443 	while ((name = (char *) HTList_nextObject(tag))) {
2444 	    int err;
2445 
2446 	    args[src] = HTfullURL_toFile(name);
2447 
2448 	    /* simplistic detection of identical src and dest - kw */
2449 	    if (!strcmp(args[src], destpath)) {
2450 		HTUserMsg2(gettext("Source and target are the same: %s"),
2451 			   args[src]);
2452 		FREE(args[src]);
2453 		continue;	/* skip this source file */
2454 	    } else if (!StrNCmp(args[src], destpath, strlen(destpath)) &&
2455 		       LYIsPathSep(args[src][strlen(destpath)]) &&
2456 		       LYLastPathSep(args[src] + strlen(destpath) + 1) == 0) {
2457 		HTUserMsg2(gettext("Already in target directory: %s"),
2458 			   args[src]);
2459 		FREE(args[src]);
2460 		continue;	/* skip this source file */
2461 	    }
2462 	    err = (LYExecv(program, args, tmpbuf) <= 0);
2463 	    FREE(args[src]);
2464 	    if (err) {
2465 		FREE(tmpbuf);
2466 		FREE(tmpdest);
2467 		return ((count == 0) ? -1 : count);
2468 	    }
2469 	    count++;
2470 	}
2471 	clear_tags();
2472     }
2473     FREE(tmpbuf);
2474     FREE(tmpdest);
2475     HTInfoMsg(gettext("Installation complete"));
2476     return count;
2477 }
2478 #endif /* OK_INSTALL */
2479 
2480 /*
2481  * Clear DIRED tags.
2482  */
clear_tags(void)2483 void clear_tags(void)
2484 {
2485     char *cp = NULL;
2486 
2487     while ((cp = (char *) HTList_removeLastObject(tagged)) != NULL) {
2488 	FREE(cp);
2489     }
2490     if (HTList_isEmpty(tagged))
2491 	FREE(tagged);
2492 }
2493 
2494 /*
2495  * Handle DIRED menu item.
2496  */
add_menu_item(char * str)2497 void add_menu_item(char *str)
2498 {
2499     struct dired_menu *tmp, *mp;
2500     char *cp;
2501     BOOL used = FALSE;
2502 
2503     /*
2504      * First custom menu definition causes entire default menu to be discarded.
2505      */
2506     if (menu_head == defmenu)
2507 	menu_head = NULL;
2508 
2509     tmp = typecalloc(struct dired_menu);
2510 
2511     if (tmp == NULL)
2512 	outofmem(__FILE__, "add_menu_item");
2513 
2514     /*
2515      * Conditional on tagged != NULL ?
2516      */
2517     if ((cp = StrChr(str, ':')) != 0) {
2518 	*cp++ = '\0';
2519 	if (strcasecomp(str, "tag") == 0) {
2520 	    tmp->cond = DE_TAG;
2521 	} else if (strcasecomp(str, "dir") == 0) {
2522 	    tmp->cond = DE_DIR;
2523 	} else if (strcasecomp(str, "file") == 0) {
2524 	    tmp->cond = DE_FILE;
2525 #ifdef S_IFLNK
2526 	} else if (strcasecomp(str, "link") == 0) {
2527 	    tmp->cond = DE_SYMLINK;
2528 #endif /* S_IFLNK */
2529 	}
2530 
2531 	/*
2532 	 * Conditional on matching suffix.
2533 	 */
2534 	str = cp;
2535 	if ((cp = StrChr(str, ':')) != 0) {
2536 	    *cp++ = '\0';
2537 	    StrAllocCopy(tmp->sfx, str);
2538 
2539 	    str = cp;
2540 	    if ((cp = StrChr(str, ':')) != 0) {
2541 		*cp++ = '\0';
2542 		StrAllocCopy(tmp->link, str);
2543 
2544 		str = cp;
2545 		if ((cp = StrChr(str, ':')) != 0) {
2546 		    *cp++ = '\0';
2547 		    StrAllocCopy(tmp->rest, str);
2548 
2549 		    StrAllocCopy(tmp->href, cp);
2550 
2551 		    if (menu_head) {
2552 			for (mp = menu_head;
2553 			     mp && mp->next != NULL;
2554 			     mp = mp->next) {
2555 			    ;
2556 			}
2557 			if (mp != NULL) {
2558 			    mp->next = tmp;
2559 			    used = TRUE;
2560 			}
2561 		    } else {
2562 			menu_head = tmp;
2563 			used = TRUE;
2564 		    }
2565 		}
2566 	    }
2567 	}
2568     }
2569     if (!used)
2570 	FREE(tmp);
2571 }
2572 
reset_dired_menu(void)2573 void reset_dired_menu(void)
2574 {
2575     if (menu_head != defmenu) {
2576 	struct dired_menu *mp, *mp_next = NULL;
2577 
2578 	for (mp = menu_head; mp != NULL; mp = mp_next) {
2579 	    FREE(mp->sfx);
2580 	    FREE(mp->link);
2581 	    FREE(mp->rest);
2582 	    FREE(mp->href);
2583 	    mp_next = mp->next;
2584 	    FREE(mp);
2585 	}
2586 	menu_head = NULL;
2587     }
2588 }
2589 
2590 /*
2591  * Create URL for DIRED HREF value.
2592  */
render_item(const char * s,const char * path,const char * dir,char * buf,size_t bufsize,int url_syntax)2593 static char *render_item(const char *s,
2594 			 const char *path,
2595 			 const char *dir,
2596 			 char *buf,
2597 			 size_t bufsize,
2598 			 int url_syntax)
2599 {
2600     const char *cp;
2601     char *bp;
2602     char overrun = '\0';
2603     char *taglist = NULL;
2604 
2605 #define BP_INC (bp>buf+bufsize-2 ?  &overrun : bp++)
2606     /* Buffer overrun could happen for very long
2607        tag list, if %l or %t are used */
2608     bp = buf;
2609     while (*s && !overrun) {
2610 	if (*s == '%') {
2611 	    s++;
2612 	    switch (*s) {
2613 	    case '%':
2614 		*BP_INC = '%';
2615 		break;
2616 	    case 'p':
2617 		cp = path;
2618 		if (!LYIsHtmlSep(*cp))
2619 		    *BP_INC = '/';
2620 		while (*cp)
2621 		    *BP_INC = *cp++;
2622 		break;
2623 	    case 'd':
2624 		cp = dir;
2625 		if (!LYIsHtmlSep(*cp))
2626 		    *BP_INC = '/';
2627 		while (*cp)
2628 		    *BP_INC = *cp++;
2629 		break;
2630 	    case 'f':
2631 		cp = LYLastPathSep(path);
2632 		if (cp)
2633 		    cp++;
2634 		else
2635 		    cp = path;
2636 		while (*cp)
2637 		    *BP_INC = *cp++;
2638 		break;
2639 	    case 'l':
2640 	    case 't':
2641 		if (!HTList_isEmpty(tagged)) {
2642 		    HTList *cur = tagged;
2643 		    char *name;
2644 
2645 		    while (!overrun &&
2646 			   (name = (char *) HTList_nextObject(cur)) != NULL) {
2647 			if (*s == 'l' && (cp = strrchr(name, '/')))
2648 			    cp++;
2649 			else
2650 			    cp = name;
2651 			StrAllocCat(taglist, cp);
2652 			StrAllocCat(taglist, " ");	/* should this be %20? */
2653 		    }
2654 		}
2655 		if (taglist) {
2656 		    /* could HTUnescape here... */
2657 		    cp = taglist;
2658 		    while (*cp)
2659 			*BP_INC = *cp++;
2660 		    FREE(taglist);
2661 		}
2662 		break;
2663 	    default:
2664 		*BP_INC = '%';
2665 		*BP_INC = *s;
2666 		break;
2667 	    }
2668 	} else {
2669 	    /*
2670 	     * Other chars come from the lynx.cfg or the default.  Let's assume
2671 	     * there isn't anything weird there that needs escaping.
2672 	     */
2673 	    *BP_INC = *s;
2674 	}
2675 	s++;
2676     }
2677     if (overrun & url_syntax) {
2678 	HTAlert(gettext("Temporary URL or list would be too long."));
2679 	bp = buf;		/* set to start, will return empty string as URL */
2680     }
2681     *bp = '\0';
2682     return buf;
2683 }
2684 
2685 #endif /* DIRED_SUPPORT */
2686