about summary refs log tree commit diff stats
path: root/main.c
blob: d06a1b3efe8d7ca99c0e3ce785126f1ec9cf81ae (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
 * See LICENSE file for license details.
 */

#include "dwm.h"
#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>

/* extern */

char stext[256];
int screen, sx, sy, sw, sh, wax, way, waw, wah;
unsigned int bh, ntags, numlockmask;
Atom wmatom[WMLast], netatom[NetLast];
Bool *seltag;
Bool selscreen = True;
Client *clients = NULL;
Client *sel = NULL;
Client *stack = NULL;
Cursor cursor[CurLast];
Display *dpy;
DC dc = {0};
Window root, barwin;

/* static */

static int (*xerrorxlib)(Display *, XErrorEvent *);
static Bool otherwm, readin;
static Bool running = True;

static void
cleanup(void) {
	close(STDIN_FILENO);
	while(stack) {
		if(stack->isbanned)
			XMoveWindow(dpy, stack->win, stack->x, stack->y);
		unmanage(stack);
	}
	if(dc.font.set)
		XFreeFontSet(dpy, dc.font.set);
	else
		XFreeFont(dpy, dc.font.xfont);
	XUngrabKey(dpy, AnyKey, AnyModifier, root);
	XFreePixmap(dpy, dc.drawable);
	XFreeGC(dpy, dc.gc);
	XDestroyWindow(dpy, barwin);
	XFreeCursor(dpy, cursor[CurNormal]);
	XFreeCursor(dpy, cursor[CurResize]);
	XFreeCursor(dpy, cursor[CurMove]);
	XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
	XSync(dpy, False);
	free(seltag);
}

static unsigned long
initcolor(const char *colstr) {
	Colormap cmap = DefaultColormap(dpy, screen);
	XColor color;

	if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
		eprint("error, cannot allocate color '%s'\n", colstr);
	return color.pixel;
}

static void
initfont(const char *fontstr) {
	char *def, **missing;
	int i, n;

	missing = NULL;
	if(dc.font.set)
		XFreeFontSet(dpy, dc.font.set);
	dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
	if(missing) {
		while(n--)
			fprintf(stderr, "missing fontset: %s\n", missing[n]);
		XFreeStringList(missing);
	}
	if(dc.font.set) {
		XFontSetExtents *font_extents;
		XFontStruct **xfonts;
		char **font_names;
		dc.font.ascent = dc.font.descent = 0;
		font_extents = XExtentsOfFontSet(dc.font.set);
		n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
		for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
			if(dc.font.ascent < (*xfonts)->ascent)
				dc.font.ascent = (*xfonts)->ascent;
			if(dc.font.descent < (*xfonts)->descent)
				dc.font.descent = (*xfonts)->descent;
			xfonts++;
		}
	}
	else {
		if(dc.font.xfont)
			XFreeFont(dpy, dc.font.xfont);
		dc.font.xfont = NULL;
		if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr)))
			eprint("error, cannot load font: '%s'\n", fontstr);
		dc.font.ascent = dc.font.xfont->ascent;
		dc.font.descent = dc.font.xfont->descent;
	}
	dc.font.height = dc.font.ascent + dc.font.descent;
}

static void
scan(void) {
	unsigned int i, num;
	Window *wins, d1, d2;
	XWindowAttributes wa;

	wins = NULL;
	if(XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
		for(i = 0; i < num; i++) {
			if(!XGetWindowAttributes(dpy, wins[i], &wa)
			|| wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1))
				continue;
			if(wa.map_state == IsViewable)
				manage(wins[i], &wa);
		}
	}
	if(wins)
		XFree(wins);
}

static void
setup(void) {
	int i, j;
	unsigned int mask;
	Window w;
	XModifierKeymap *modmap;
	XSetWindowAttributes wa;

	/* init atoms */
	wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
	wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
	wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False);
	netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
	netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
	XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
			PropModeReplace, (unsigned char *) netatom, NetLast);
	/* init cursors */
	cursor[CurNormal] = XCreateFontCursor(dpy, XC_left_ptr);
	cursor[CurResize] = XCreateFontCursor(dpy, XC_sizing);
	cursor[CurMove] = XCreateFontCursor(dpy, XC_fleur);
	/* init modifier map */
	numlockmask = 0;
	modmap = XGetModifierMapping(dpy);
	for (i = 0; i < 8; i++)
		for (j = 0; j < modmap->max_keypermod; j++) {
			if(modmap->modifiermap[i * modmap->max_keypermod + j]
					== XKeysymToKeycode(dpy, XK_Num_Lock))
				numlockmask = (1 << i);
		}
	XFreeModifiermap(modmap);
	/* select for events */
	wa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask
		| EnterWindowMask | LeaveWindowMask;
	wa.cursor = cursor[CurNormal];
	XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &wa);
	grabkeys();
	compileregs();
	for(ntags = 0; tags[ntags]; ntags++);
	seltag = emallocz(sizeof(Bool) * ntags);
	seltag[0] = True;
	/* style */
	dc.norm[ColBorder] = initcolor(NORMBORDERCOLOR);
	dc.norm[ColBG] = initcolor(NORMBGCOLOR);
	dc.norm[ColFG] = initcolor(NORMFGCOLOR);
	dc.sel[ColBorder] = initcolor(SELBORDERCOLOR);
	dc.sel[ColBG] = initcolor(SELBGCOLOR);
	dc.sel[ColFG] = initcolor(SELFGCOLOR);
	initfont(FONT);
	/* geometry */
	sx = sy = 0;
	sw = DisplayWidth(dpy, screen);
	sh = DisplayHeight(dpy, screen);
	initlayouts();
	/* bar */
	dc.h = bh = dc.font.height + 2;
	wa.override_redirect = 1;
	wa.background_pixmap = ParentRelative;
	wa.event_mask = ButtonPressMask | ExposureMask;
	barwin = XCreateWindow(dpy, root, sx, sy + (TOPBAR ? 0 : sh - bh), sw, bh, 0,
			DefaultDepth(dpy, screen), CopyFromParent, DefaultVisual(dpy, screen),
			CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
	XDefineCursor(dpy, barwin, cursor[CurNormal]);
	XMapRaised(dpy, barwin);
	strcpy(stext, "dwm-"VERSION);
	/* windowarea */
	wax = sx;
	way = sy + (TOPBAR ? bh : 0);
	wah = sh - bh;
	waw = sw;
	/* pixmap for everything */
	dc.drawable = XCreatePixmap(dpy, root, sw, bh, DefaultDepth(dpy, screen));
	dc.gc = XCreateGC(dpy, root, 0, 0);
	XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
	/* multihead support */
	selscreen = XQueryPointer(dpy, root, &w, &w, &i, &i, &i, &i, &mask);
}

/*
 * Startup Error handler to check if another window manager
 * is already running.
 */
static int
xerrorstart(Display *dsply, XErrorEvent *ee) {
	otherwm = True;
	return -1;
}

/* extern */

void
sendevent(Window w, Atom a, long value) {
	XEvent e;

	e.type = ClientMessage;
	e.xclient.window = w;
	e.xclient.message_type = a;
	e.xclient.format = 32;
	e.xclient.data.l[0] = value;
	e.xclient.data.l[1] = CurrentTime;
	XSendEvent(dpy, w, False, NoEventMask, &e);
	XSync(dpy, False);
}

void
quit(Arg arg) {
	readin = running = False;
}

/* There's no way to check accesses to destroyed windows, thus those cases are
 * ignored (especially on UnmapNotify's).  Other types of errors call Xlibs
 * default error handler, which may call exit.
 */
int
xerror(Display *dpy, XErrorEvent *ee) {
	if(ee->error_code == BadWindow
	|| (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
	|| (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
	|| (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
	|| (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
	|| (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
	|| (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
	|| (ee->request_code == X_CopyArea && ee->error_code == BadDrawable))
		return 0;
	fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n",
		ee->request_code, ee->error_code);
	return xerrorxlib(dpy, ee); /* may call exit */
}

int
main(int argc, char *argv[]) {
	char *p;
	int r, xfd;
	fd_set rd;
	XEvent ev;

	if(argc == 2 && !strncmp("-v", argv[1], 3))
		eprint("dwm-"VERSION", (C)opyright MMVI-MMVII Anselm R. Garbe\n");
	else if(argc != 1)
		eprint("usage: dwm [-v]\n");
	setlocale(LC_CTYPE, "");
	if(!(dpy = XOpenDisplay(0)))
		eprint("dwm: cannot open display\n");
	xfd = ConnectionNumber(dpy);
	screen = DefaultScreen(dpy);
	root = RootWindow(dpy, screen);
	otherwm = False;
	XSetErrorHandler(xerrorstart);
	/* this causes an error if some other window manager is running */
	XSelectInput(dpy, root, SubstructureRedirectMask);
	XSync(dpy, False);
	if(otherwm)
		eprint("dwm: another window manager is already running\n");

	XSync(dpy, False);
	XSetErrorHandler(NULL);
	xerrorxlib = XSetErrorHandler(xerror);
	XSync(dpy, False);
	setup();
	drawstatus();
	scan();

	/* main event loop, also reads status text from stdin */
	XSync(dpy, False);
	readin = True;
	while(running) {
		FD_ZERO(&rd);
		if(readin)
			FD_SET(STDIN_FILENO, &rd);
		FD_SET(xfd, &rd);
		if(select(xfd + 1, &rd, NULL, NULL, NULL) == -1) {
			if(errno == EINTR)
				continue;
			eprint("select failed\n");
		}
		if(FD_ISSET(STDIN_FILENO, &rd)) {
			switch(r = read(STDIN_FILENO, stext, sizeof stext - 1)) {
			case -1:
				strncpy(stext, strerror(errno), sizeof stext - 1);
				stext[sizeof stext - 1] = '\0';
				readin = False;
				break;
			case 0:
				strncpy(stext, "EOF", 4);
				readin = False;
				break;
			default:
				for(stext[r] = '\0', p = stext + strlen(stext) - 1; p >= stext && *p == '\n'; *p-- = '\0');
				for(; p >= stext && *p != '\n'; --p);
				if(p > stext)
					strncpy(stext, p + 1, sizeof stext);
			}
			drawstatus();
		}
		while(XPending(dpy)) {
			XNextEvent(dpy, &ev);
			if(handler[ev.type])
				(handler[ev.type])(&ev); /* call handler */
		}
	}
	cleanup();
	XCloseDisplay(dpy);
	return 0;
}
ted symbolic link)", "LYNXDIRED://MODIFY_NAME%p", NULL }, #ifdef OK_PERMIT { DE_FILE, "", "Modify File Permissions", "(of current selection)", "LYNXDIRED://PERMIT_SRC%p", NULL }, { DE_DIR, "", "Modify Directory Permissions", "(of current selection)", "LYNXDIRED://PERMIT_SRC%p", NULL }, #endif /* OK_PERMIT */ { DE_FILE, "", "Change Location", "(of selected file)" , "LYNXDIRED://MODIFY_LOCATION%p", NULL }, { DE_DIR, "", "Change Location", "(of selected directory)", "LYNXDIRED://MODIFY_LOCATION%p", NULL }, { DE_SYMLINK, "", "Change Location", "(of selected symbolic link)", "LYNXDIRED://MODIFY_LOCATION%p", NULL }, { DE_FILE, "", "Remove File", "(current selection)", "LYNXDIRED://REMOVE_SINGLE%p", NULL }, { DE_DIR, "", "Remove Directory", "(current selection)", "LYNXDIRED://REMOVE_SINGLE%p", NULL }, { DE_SYMLINK, "", "Remove Symbolic Link", "(current selection)", "LYNXDIRED://REMOVE_SINGLE%p", NULL }, #if defined(OK_UUDECODE) && !defined(ARCHIVE_ONLY) { DE_FILE, "", "UUDecode", "(current selection)", "LYNXDIRED://UUDECODE%p", NULL }, #endif /* OK_UUDECODE && !ARCHIVE_ONLY */ #if defined(OK_TAR) && !defined(ARCHIVE_ONLY) { DE_FILE, ".tar.Z", "Expand", "(current selection)", "LYNXDIRED://UNTAR_Z%p", NULL }, #endif /* OK_TAR && !ARCHIVE_ONLY */ #if defined(OK_TAR) && defined(OK_GZIP) && !defined(ARCHIVE_ONLY) { DE_FILE, ".tar.gz", "Expand", "(current selection)", "LYNXDIRED://UNTAR_GZ%p", NULL }, { DE_FILE, ".tgz", "Expand", "(current selection)", "LYNXDIRED://UNTAR_GZ%p", NULL }, #endif /* OK_TAR && OK_GZIP && !ARCHIVE_ONLY */ #ifndef ARCHIVE_ONLY { DE_FILE, ".Z", "Uncompress", "(current selection)", "LYNXDIRED://DECOMPRESS%p", NULL }, #endif /* ARCHIVE_ONLY */ #if defined(OK_GZIP) && !defined(ARCHIVE_ONLY) { DE_FILE, ".gz", "Uncompress", "(current selection)", "LYNXDIRED://UNGZIP%p", NULL }, #endif /* OK_GZIP && !ARCHIVE_ONLY */ #if defined(OK_ZIP) && !defined(ARCHIVE_ONLY) { DE_FILE, ".zip", "Uncompress", "(current selection)", "LYNXDIRED://UNZIP%p", NULL }, #endif /* OK_ZIP && !ARCHIVE_ONLY */ #if defined(OK_TAR) && !defined(ARCHIVE_ONLY) { DE_FILE, ".tar", "UnTar", "(current selection)", "LYNXDIRED://UNTAR%p", NULL }, #endif /* OK_TAR && !ARCHIVE_ONLY */ #ifdef OK_TAR { DE_DIR, "", "Tar", "(current selection)", "LYNXDIRED://TAR%p", NULL }, #endif /* OK_TAR */ #if defined(OK_TAR) && defined(OK_GZIP) { DE_DIR, "", "Tar and compress", "(using GNU gzip)", "LYNXDIRED://TAR_GZ%p", NULL }, #endif /* OK_TAR && OK_GZIP */ #ifdef OK_ZIP { DE_DIR, "", "Package and compress", "(using zip)", "LYNXDIRED://ZIP%p", NULL }, #endif /* OK_ZIP */ { DE_FILE, "", "Compress", "(using Unix compress)", "LYNXDIRED://COMPRESS%p", NULL }, #ifdef OK_GZIP { DE_FILE, "", "Compress", "(using gzip)", "LYNXDIRED://GZIP%p", NULL }, #endif /* OK_GZIP */ #ifdef OK_ZIP { DE_FILE, "", "Compress", "(using zip)", "LYNXDIRED://ZIP%p", NULL }, #endif /* OK_ZIP */ { DE_TAG, "", "Move all tagged items to another location.", "", "LYNXDIRED://MOVE_TAGGED%d", NULL }, { DE_TAG, "", "Remove all tagged files and directories.", "", "LYNXDIRED://REMOVE_TAGGED", NULL }, { DE_TAG, "", "Untag all tagged files and directories.", "", "LYNXDIRED://CLEAR_TAGGED", NULL }, { 0, NULL, NULL, NULL, NULL, NULL } }; PRIVATE BOOLEAN ok_stat ARGS2(char *, name, struct stat*, sb) { CTRACE(tfp, "testing ok_stat(%s)\n", name); if (stat(name, sb) < 0) { char *tmpbuf = 0; HTSprintf(&tmpbuf, gettext("Unable to get status of '%s'."), name); HTAlert(tmpbuf); FREE(tmpbuf); return FALSE; } return TRUE; } #ifdef HAVE_LSTAT PRIVATE BOOLEAN ok_lstat ARGS2(char *, name, struct stat*, sb) { CTRACE(tfp, "testing ok_lstat(%s)\n", name); if (lstat(name, sb) < 0) { char *tmpbuf = 0; HTSprintf(&tmpbuf, gettext("Unable to get status of '%s'."), name); HTAlert(tmpbuf); FREE(tmpbuf); return FALSE; } return TRUE; } #else #define ok_lstat(name,sb) ok_stat(name,sb) #endif PRIVATE BOOLEAN ok_file_or_dir ARGS1(struct stat*, sb) { if (!S_ISDIR(sb->st_mode) && !S_ISREG(sb->st_mode)) { HTAlert(gettext("The selected item is not a file or a directory! Request ignored.")); return FALSE; } return TRUE; } PRIVATE BOOLEAN ok_localname ARGS2(char*, dst, char*, src) { char *s = HTfullURL_toFile(strip_trailing_slash(src)); struct stat dir_info; if (!ok_stat(s, &dir_info) || !ok_file_or_dir(&dir_info)) { free(s); return FALSE; } strcpy(dst, s); free(s); return TRUE; } /* * Remove all tagged files and directories. */ PRIVATE BOOLEAN remove_tagged NOARGS { int c, ans; BOOL will_clear = TRUE; char *cp; char *tmpbuf = NULL; char *testpath = NULL; struct stat dir_info; int count; HTList *tag; char *args[5]; if (HTList_isEmpty(tagged)) /* should never happen */ return 0; _statusline(gettext("Remove all tagged files and directories (y or n): ")); c = LYgetch(); ans = TOUPPER(c); count = 0; tag = tagged; while (ans == 'Y' && (cp = (char *)HTList_nextObject(tag)) != NULL) { if (is_url(cp) == FILE_URL_TYPE) { /* unnecessary check */ testpath = HTfullURL_toFile(cp); LYTrimPathSep(testpath); will_clear = TRUE; /* * Check the current status of the path to be deleted. */ if (!ok_stat(testpath, &dir_info)) { will_clear = FALSE; break; } else { args[0] = "rm"; args[1] = "-rf"; args[2] = testpath; args[3] = (char *) 0; HTSprintf0(&tmpbuf, gettext("remove %s"), testpath); if (LYExecv(RM_PATH, args, tmpbuf) <= 0) { if (count == 0) count = -1; will_clear = FALSE; break; } ++count; } } } FREE(testpath); FREE(tmpbuf); if (will_clear) clear_tags(); return count; } /* * Move all tagged files and directories to a new location. * Input is current directory. * The tests in this function can, at best, prevent some user mistakes - * anybody who relies on them for security is seriously misguided. * If a user has enough permissions to move a file somewhere, the same * uid with Lynx & dired can do the same thing. */ PRIVATE BOOLEAN modify_tagged ARGS1( char *, testpath) { char *cp; dev_t dev; ino_t inode; uid_t owner; char tmpbuf[1024]; char *savepath = NULL; char *srcpath = NULL; struct stat dir_info; char *args[5]; int count = 0; HTList *tag; if (HTList_isEmpty(tagged)) /* should never happen */ return 0; _statusline(gettext("Enter new location for tagged items: ")); tmpbuf[0] = '\0'; LYgetstr(tmpbuf, VISIBLE, sizeof(tmpbuf), NORECALL); if (strlen(tmpbuf)) { /* * Determine the ownership of the current location. */ /* * This test used to always fail from the dired menu... * changed to something that hopefully makes more sense - KW */ if (testpath && *testpath && 0!=strcmp(testpath,"/")) { /* * testpath passed in and is not empty and not a single "/" * (which would probably be bogus) - use it. */ cp = testpath; } else { /* * Prepare to get directory path from one of the tagged files. */ cp = HTList_lastObject(tagged); testpath = NULL; /* Won't be needed any more in this function, set to NULL as a flag. */ if (!cp) /* Last resort, should never happen. */ cp = "/"; } if (testpath == NULL) { /* * Get the directory containing the file or subdir. */ cp = HTfullURL_toFile(strip_trailing_slash(cp)); savepath = HTParse(".", cp, PARSE_PATH+PARSE_PUNCTUATION); } else { cp = HTfullURL_toFile(cp); StrAllocCopy(savepath, cp); } free(cp); if (!ok_stat(savepath, &dir_info)) { FREE(savepath); return 0; } /* * Save the owner of the current location for later use. * Also save the device and inode for location checking/ */ dev = dir_info.st_dev; inode = dir_info.st_ino; owner = dir_info.st_uid; /* * Replace ~/ references to the home directory. */ if (!strncmp(tmpbuf, "~/", 2)) { char *cp1 = NULL; StrAllocCopy(cp1, Home_Dir()); StrAllocCat(cp1, (tmpbuf + 1)); if (strlen(cp1) > (sizeof(tmpbuf) - 1)) { HTAlert(gettext("Path too long")); FREE(savepath); FREE(cp1); return 0; } strcpy(tmpbuf, cp1); FREE(cp1); } /* * If path is relative, prefix it with current location. */ if (!LYIsPathSep(tmpbuf[0])) { LYAddPathSep(&savepath); StrAllocCat(savepath,tmpbuf); } else { StrAllocCopy(savepath,tmpbuf); } /* * stat() the target location to determine type and ownership. */ if (!ok_stat(savepath, &dir_info)) { FREE(savepath); return 0; } /* * Make sure the source and target locations are not the same place. */ if (dev == dir_info.st_dev && inode == dir_info.st_ino) { HTAlert(gettext("Source and destination are the same location - request ignored!")); FREE(savepath); return 0; } /* * Make sure the target location is a directory which is owned * by the same uid as the owner of the current location. */ if (S_ISDIR(dir_info.st_mode)) { if (dir_info.st_uid == owner) { count = 0; tag = tagged; /* * Move all tagged items to the target location. */ while ((cp = (char *)HTList_nextObject(tag)) != NULL) { cp = HTfullURL_toFile(cp); StrAllocCopy(srcpath, cp); HTSprintf0(&cp, gettext("move %s to %s"), srcpath, savepath); args[0] = "mv"; args[1] = srcpath; args[2] = savepath; args[3] = (char *) 0; if (LYExecv(MV_PATH, args, cp) <= 0) { FREE(cp); if (count == 0) count = -1; break; } FREE(cp); ++count; } FREE(srcpath); FREE(savepath); clear_tags(); return count; } else { HTAlert(gettext("Destination has different owner! Request denied.")); FREE(srcpath); FREE(savepath); return 0; } } else { HTAlert(gettext("Destination is not a valid directory! Request denied.")); FREE(savepath); return 0; } } return 0; } /* * Modify the name of the specified item. */ PRIVATE BOOLEAN modify_name ARGS1( char *, testpath) { char *cp; char tmpbuf[512]; char newpath[512]; char savepath[512]; struct stat dir_info; char *args[5]; /* * Determine the status of the selected item. */ testpath = strip_trailing_slash(testpath); if (ok_stat(testpath, &dir_info)) { /* * Change the name of the file or directory. */ if (S_ISDIR(dir_info.st_mode)) { cp = gettext("Enter new name for directory: "); } else if (S_ISREG(dir_info.st_mode)) { cp = gettext("Enter new name for file: "); } else { HTAlert(gettext("The selected item is not a file or a directory! Request ignored.")); return 0; } if (filename(cp, tmpbuf, sizeof(tmpbuf)) == NULL) return 0; /* * Do not allow the user to also change the location at this time. */ if (strchr(tmpbuf, '/') != NULL) { HTAlert(gettext("Illegal character \"/\" found! Request ignored.")); } else if (strlen(tmpbuf) && (cp = strrchr(testpath, '/')) != NULL) { strcpy(savepath,testpath); *(++cp) = '\0'; strcpy(newpath,testpath); strcat(newpath,tmpbuf); /* * Make sure the destination does not already exist. */ if (stat(newpath, &dir_info) == -1) { char *msg = 0; if (errno != ENOENT) { HTSprintf(&msg, gettext("Unable to determine status of '%s'."), newpath); HTAlert(msg); FREE(msg); } else { int code; HTSprintf(&msg, gettext("move %s to %s"), savepath, newpath); args[0] = "mv"; args[1] = savepath; args[2] = newpath; args[3] = (char *) 0; code = (LYExecv(MV_PATH, args, msg) <= 0) ? -1 : 1; FREE(msg); return code; } } else if (S_ISDIR(dir_info.st_mode)) { HTAlert(gettext("There is already a directory with that name! Request ignored.")); } else if (S_ISREG(dir_info.st_mode)) { HTAlert(gettext("There is already a file with that name! Request ignored.")); } else { HTAlert(gettext("The specified name is already in use! Request ignored.")); } } } return 0; } /* * Change the location of a file or directory. */ PRIVATE BOOLEAN modify_location ARGS1( char *, testpath) { int mode; char *cp; dev_t dev; ino_t inode; uid_t owner; char tmpbuf[1024]; char newpath[512]; char savepath[512]; struct stat dir_info; char *args[5]; /* * Determine the status of the selected item. */ testpath = strip_trailing_slash(testpath); if (!ok_stat(testpath, &dir_info)) { return 0; } /* * Change the location of the file or directory. */ if (S_ISDIR(dir_info.st_mode)) { cp = gettext("Enter new location for directory: "); } else if (S_ISREG(dir_info.st_mode)) { cp = gettext("Enter new location for file: "); } else { HTAlert(gettext("The specified item is not a file or a directory - request ignored.")); return 0; } if (filename(cp, tmpbuf, sizeof(tmpbuf)) == NULL) return 0; if (strlen(tmpbuf)) { strcpy(savepath, testpath); strcpy(newpath, testpath); /* * Allow ~/ references to the home directory. */ if (!strncmp(tmpbuf, "~/", 2) || !strcmp(tmpbuf,"~")) { strcpy(newpath, Home_Dir()); strcat(newpath, (tmpbuf + 1)); strcpy(tmpbuf, newpath); } if (!LYIsPathSep(tmpbuf[0])) { if ((cp = strrchr(newpath,'/')) != NULL) { *++cp = '\0'; strcat(newpath,tmpbuf); } else { HTAlert(gettext("Unexpected failure - unable to find trailing \"/\"")); return 0; } } else { strcpy(newpath,tmpbuf); } /* * Make sure the source and target have the same owner (uid). */ dev = dir_info.st_dev; mode = dir_info.st_mode; inode = dir_info.st_ino; owner = dir_info.st_uid; if (!ok_stat(newpath, &dir_info)) { return 0; } if (!S_ISDIR(dir_info.st_mode)) { HTAlert(gettext("Destination is not a valid directory! Request denied.")); return 0; } /* * Make sure the source and target are not the same location. */ if (dev == dir_info.st_dev && inode == dir_info.st_ino) { HTAlert(gettext("Source and destination are the same location! Request ignored!")); return 0; } if (dir_info.st_uid == owner) { int code; char *msg = 0; HTSprintf(&msg,gettext("move %s to %s"),savepath,newpath); args[0] = "mv"; args[1] = savepath; args[2] = newpath; args[3] = (char *) 0; code = (LYExecv(MV_PATH, args, msg) <= 0) ? -1 : 1; FREE(msg); return code; } else { HTAlert(gettext("Destination has different owner! Request denied.")); return 0; } } return 0; } /* * Modify name or location of a file or directory on localhost. */ PUBLIC BOOLEAN local_modify ARGS2( document *, doc, char **, newpath) { int c, ans; char *cp; char testpath[512]; /* a bit ridiculous */ int count; if (!HTList_isEmpty(tagged)) { cp = HTpartURL_toFile(doc->address); strcpy(testpath, cp); free(cp); count = modify_tagged(testpath); if (doc->link > (nlinks-count - 1)) doc->link = (nlinks-count - 1); doc->link = (doc->link < 0) ? 0 : doc->link; return count; } else if (doc->link < 0 || doc->link > nlinks) { /* * Added protection. */ return 0; } /* * Do not allow simultaneous change of name and location as in Unix. * This reduces functionality but reduces difficulty for the novice. */ #ifdef OK_PERMIT _statusline(gettext("Modify name, location, or permission (n, l, or p): ")); #else _statusline(gettext("Modify name, or location (n or l): ")); #endif /* OK_PERMIT */ c = LYgetch(); ans = TOUPPER(c); if (strchr("NLP", ans) != NULL) { cp = HTfullURL_toFile(links[doc->link].lname); strcpy(testpath, cp); free(cp); if (ans == 'N') { return(modify_name(testpath)); } else if (ans == 'L') { if (modify_location(testpath)) { if (doc->link == (nlinks-1)) --doc->link; return 1; } #ifdef OK_PERMIT } else if (ans == 'P') { return(permit_location(NULL, testpath, newpath)); #endif /* OK_PERMIT */ } else { /* * Code for changing ownership needed here. */ HTAlert(gettext("This feature not yet implemented!")); } } return 0; } /* * Create a new empty file in the current directory. */ PRIVATE BOOLEAN create_file ARGS1( char *, current_location) { char tmpbuf[512]; char testpath[512]; struct stat dir_info; char *args[5]; char *bad_chars = ".~/"; if (filename(gettext("Enter name of file to create: "), tmpbuf, sizeof(tmpbuf)) == NULL) { return 0; } if (!no_dotfiles && show_dotfiles) { bad_chars = "~/"; } if (strstr(tmpbuf, "//") != NULL) { HTAlert(gettext("Illegal redirection \"//\" found! Request ignored.")); } else if (strlen(tmpbuf) && strchr(bad_chars, tmpbuf[0]) == NULL) { strcpy(testpath,current_location); LYAddPathSep0(testpath); /* * Append the target filename to the current location. */ strcat(testpath, tmpbuf); /* * Make sure the target does not already exist */ if (stat(testpath, &dir_info) == -1) { int code; char *msg = 0; if (errno != ENOENT) { HTSprintf(&msg, gettext("Unable to determine status of '%s'."), testpath); HTAlert(msg); code = 0; } else { HTSprintf(&msg,gettext("create %s"),testpath); args[0] = "touch"; args[1] = testpath; args[2] = (char *) 0; code = (LYExecv(TOUCH_PATH, args, msg) <= 0) ? -1 : 1; } FREE(msg); return code; } else if (S_ISDIR(dir_info.st_mode)) { HTAlert(gettext("There is already a directory with that name! Request ignored.")); } else if (S_ISREG(dir_info.st_mode)) { HTAlert(gettext("There is already a file with that name! Request ignored.")); } else { HTAlert(gettext("The specified name is already in use! Request ignored.")); } } return 0; } /* * Create a new directory in the current directory. */ PRIVATE BOOLEAN create_directory ARGS1( char *, current_location) { char tmpbuf[512]; char testpath[512]; struct stat dir_info; char *args[5]; char *bad_chars = ".~/"; if (filename(gettext("Enter name for new directory: "), tmpbuf, sizeof(tmpbuf)) == NULL) { return 0; } if (!no_dotfiles && show_dotfiles) { bad_chars = "~/"; } if (strstr(tmpbuf, "//") != NULL) { HTAlert(gettext("Illegal redirection \"//\" found! Request ignored.")); } else if (strlen(tmpbuf) && strchr(bad_chars, tmpbuf[0]) == NULL) { strcpy(testpath,current_location); LYAddPathSep0(testpath); strcat(testpath, tmpbuf); /* * Make sure the target does not already exist. */ if (stat(testpath, &dir_info) == -1) { int code; char *msg = 0; if (errno != ENOENT) { HTSprintf(&msg, gettext("Unable to determine status of '%s'."), testpath); HTAlert(msg); code = 0; } else { HTSprintf(&msg,"make directory %s",testpath); args[0] = "mkdir"; args[1] = testpath; args[2] = (char *) 0; code = (LYExecv(MKDIR_PATH, args, msg) <= 0) ? -1 : 1; } FREE(msg); return code; } else if (S_ISDIR(dir_info.st_mode)) { HTAlert(gettext("There is already a directory with that name! Request ignored.")); } else if (S_ISREG(dir_info.st_mode)) { HTAlert(gettext("There is already a file with that name! Request ignored.")); } else { HTAlert(gettext("The specified name is already in use! Request ignored.")); } } return 0; } /* * Create a file or a directory at the current location. */ PUBLIC BOOLEAN local_create ARGS1( document *, doc) { int c, ans; char *cp; char testpath[512]; _statusline(gettext("Create file or directory (f or d): ")); c = LYgetch(); ans = TOUPPER(c); cp = HTfullURL_toFile(doc->address); strcpy(testpath,cp); free(cp); if (ans == 'F') { return(create_file(testpath)); } else if (ans == 'D') { return(create_directory(testpath)); } else { return 0; } } /* * Remove a single file or directory. */ PRIVATE BOOLEAN remove_single ARGS1( char *, testpath) { int c; int code = 0; char *cp; char *tmpbuf = 0; struct stat dir_info; char *args[5]; if (!ok_lstat(testpath, &dir_info)) { return 0; } /* * Locate the filename portion of the path. */ if ((cp = strrchr(testpath, '/')) != NULL) { ++cp; } else { cp = testpath; } if (S_ISDIR(dir_info.st_mode)) { /*** This strlen stuff will probably screw up intl translations /jes ***/ /*** Course, it's probably broken for screen sizes other 80, too ***/ if (strlen(cp) < 37) { HTSprintf0(&tmpbuf, gettext("Remove '%s' and all of its contents (y or n): "), cp); } else { HTSprintf0(&tmpbuf, gettext("Remove directory and all of its contents (y or n): ")); } } else if (S_ISREG(dir_info.st_mode)) { if (strlen(cp) < 60) { HTSprintf0(&tmpbuf, gettext("Remove file '%s' (y or n): "), cp); } else { HTSprintf0(&tmpbuf, gettext("Remove file (y or n): ")); } #ifdef S_IFLNK } else if (S_ISLNK(dir_info.st_mode)) { if (strlen(cp) < 50) { HTSprintf0(&tmpbuf, gettext("Remove symbolic link '%s' (y or n): "), cp); } else { HTSprintf0(&tmpbuf, gettext("Remove symbolic link (y or n): ")); } #endif } else { HTSprintf0(&tmpbuf, gettext("Unable to determine status of '%s'."), testpath); HTAlert(tmpbuf); FREE(tmpbuf); return 0; } _statusline(tmpbuf); c = LYgetch(); if (TOUPPER(c) == 'Y') { HTSprintf0(&tmpbuf,"remove %s",testpath); args[0] = "rm"; args[1] = "-rf"; args[2] = testpath; args[3] = (char *) 0; code = (LYExecv(RM_PATH, args, tmpbuf) <= 0) ? -1 : 1; } FREE(tmpbuf); return code; } /* * Remove a file or a directory. */ PUBLIC BOOLEAN local_remove ARGS1( document *, doc) { char *cp, *tp; char testpath[512]; int count, i; if (!HTList_isEmpty(tagged)) { count = remove_tagged(); if (doc->link > (nlinks-count - 1)) doc->link = (nlinks-count - 1); doc->link = (doc->link < 0) ? 0 : doc->link; return count; } else if (doc->link < 0 || doc->link > nlinks) { return 0; } cp = links[doc->link].lname; if (is_url(cp) == FILE_URL_TYPE) { tp = HTfullURL_toFile(cp); strcpy(testpath, tp); free(tp); if ((i = strlen(testpath)) && testpath[i - 1] == '/') testpath[(i - 1)] = '\0'; if (remove_single(testpath)) { if (doc->link == (nlinks - 1)) --doc->link; return 1; } } return 0; } #ifdef OK_PERMIT /* * Table of permission strings and chmod values. * Makes the code a bit cleaner. */ static struct { CONST char *string_mode; /* Key for value below */ long permit_bits; /* Value for chmod/whatever */ } permissions[] = { {"IRUSR", S_IRUSR}, {"IWUSR", S_IWUSR}, {"IXUSR", S_IXUSR}, {"IRGRP", S_IRGRP}, {"IWGRP", S_IWGRP}, {"IXGRP", S_IXGRP}, {"IROTH", S_IROTH}, {"IWOTH", S_IWOTH}, {"IXOTH", S_IXOTH}, {NULL, 0} /* Don't include setuid and friends; use shell access for that. */ }; PRIVATE char LYValidPermitFile[LY_MAXPATH] = "\0"; /* * Handle DIRED permissions. */ PRIVATE BOOLEAN permit_location ARGS3( char *, destpath, char *, srcpath, char **, newpath) { #ifndef UNIX HTAlert(gettext("Sorry, don't know how to permit non-UNIX files yet.")); return(0); #else static char tempfile[LY_MAXPATH] = "\0"; char *cp; char *tmpbuf = NULL; char tmpdst[LY_MAXPATH]; struct stat dir_info; if (srcpath) { /* * Create form. */ FILE *fp0; char local_src[LY_MAXPATH]; char * user_filename; char * group_name; cp = HTfullURL_toFile(strip_trailing_slash(srcpath)); strcpy(local_src, cp); free(cp); /* * A couple of sanity tests. */ if (!ok_lstat(local_src, &dir_info) || !ok_file_or_dir(&dir_info)) return 0; user_filename = LYPathLeaf(local_src); LYRemoveTemp(tempfile); if ((fp0 = LYOpenTemp(tempfile, HTML_SUFFIX, "w")) == NULL) { HTAlert(gettext("Unable to open permit options file")); return(0); } /* * Make the tempfile a URL. */ LYLocalFileToURL(newpath, tempfile); strcpy(LYPermitFileURL, *newpath); group_name = HTAA_GidToName (dir_info.st_gid); LYstrncpy(LYValidPermitFile, local_src, (sizeof(LYValidPermitFile) - 1)); fprintf(fp0, "<Html><Head>\n<Title>%s</Title>\n</Head>\n<Body>\n", PERMIT_OPTIONS_TITLE); fprintf(fp0,"<H1>%s%s</H1>\n", PERMISSIONS_SEGMENT, user_filename); { /* * Prevent filenames which include '#' or '?' from messing it up. */ char * srcpath_url = HTEscape(srcpath, URL_PATH); fprintf(fp0, "<Form Action=\"LYNXDIRED://PERMIT_LOCATION%s\">\n", srcpath_url); FREE(srcpath_url); } fprintf(fp0, "<Ol><Li>%s<Br><Br>\n", gettext("Specify permissions below:")); fprintf(fp0, "%s:<Br>\n", gettext("Owner:")); fprintf(fp0, "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IRUSR\" %s> Read<Br>\n", (dir_info.st_mode & S_IRUSR) ? "checked" : ""); fprintf(fp0, "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IWUSR\" %s> Write<Br>\n", (dir_info.st_mode & S_IWUSR) ? "checked" : ""); /* * If restricted, only change eXecute permissions on directories. */ if (!no_change_exec_perms || S_ISDIR(dir_info.st_mode)) fprintf(fp0, "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IXUSR\" %s> %s<Br>\n", (dir_info.st_mode & S_IXUSR) ? "checked" : "", S_ISDIR(dir_info.st_mode) ? "Search" : "Execute"); fprintf(fp0, "%s %s:<Br>\n", gettext("Group"), group_name); fprintf(fp0, "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IRGRP\" %s> Read<Br>\n", (dir_info.st_mode & S_IRGRP) ? "checked" : ""); fprintf(fp0, "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IWGRP\" %s> Write<Br>\n", (dir_info.st_mode & S_IWGRP) ? "checked" : ""); /* * If restricted, only change eXecute permissions on directories. */ if (!no_change_exec_perms || S_ISDIR(dir_info.st_mode)) fprintf(fp0, "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IXGRP\" %s> %s<Br>\n", (dir_info.st_mode & S_IXGRP) ? "checked" : "", S_ISDIR(dir_info.st_mode) ? "Search" : "Execute"); fprintf(fp0, "%s<Br>\n", gettext("Others:")); fprintf(fp0, "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IROTH\" %s> Read<Br>\n", (dir_info.st_mode & S_IROTH) ? "checked" : ""); fprintf(fp0, "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IWOTH\" %s> Write<Br>\n", (dir_info.st_mode & S_IWOTH) ? "checked" : ""); /* * If restricted, only change eXecute permissions on directories. */ if (!no_change_exec_perms || S_ISDIR(dir_info.st_mode)) fprintf(fp0, "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IXOTH\" %s> %s<Br>\n", (dir_info.st_mode & S_IXOTH) ? "checked" : "", S_ISDIR(dir_info.st_mode) ? "Search" : "Execute"); fprintf(fp0, "<Br>\n<Li><Input Type=\"submit\" Value=\"Submit\"> %s %s %s.\n</Ol>\n</Form>\n", gettext("form to permit"), S_ISDIR(dir_info.st_mode) ? "directory" : "file", user_filename); fprintf(fp0, "</Body></Html>"); LYCloseTempFP(fp0); LYforce_no_cache = TRUE; return(PERMIT_FORM_RESULT); /* Special flag for LYMainLoop */ } else { /* The form being activated. */ mode_t new_mode = 0; char *args[5]; char amode[10]; /* * Make sure we have a valid set-permission * file comparison string loaded via a previous * call with srcpath != NULL. - KW */ if (LYValidPermitFile[0] == '\0') { if (LYCursesON) HTAlert(INVALID_PERMIT_URL); else fprintf(stderr, "%s\n", INVALID_PERMIT_URL); CTRACE(tfp, "permit_location: called for <%s>.\n", (destpath ? destpath : "NULL URL pointer")); return 0; } cp = destpath; while (*cp != '\0' && *cp != '?') { /* Find filename */ cp++; } if (*cp == '\0') { return(0); /* Nothing to permit. */ } *cp++ = '\0'; /* Null terminate file name and start working on the masks. */ if ((destpath = HTfullURL_toFile(destpath)) == 0) return(0); strcpy(tmpdst, destpath); /* operate only on filename */ free(destpath); destpath = tmpdst; /* * Make sure that the file string is the one from * the last displayed File Permissions menu. - KW */ if (strcmp(destpath, LYValidPermitFile)) { if (LYCursesON) HTAlert(INVALID_PERMIT_URL); else fprintf(stderr, "%s\n", INVALID_PERMIT_URL); CTRACE(tfp, "permit_location: called for file '%s'.\n", destpath); return 0; } /* * A couple of sanity tests. */ destpath = strip_trailing_slash(destpath); if (!ok_stat(destpath, &dir_info) || !ok_file_or_dir(&dir_info)) { return 0; } /* * Cycle over permission strings. */ while(*cp != '\0') { char *cr = cp; while(*cr != '\0' && *cr != '&') { /* GET data split by '&'. */ cr++; } if (*cr != '\0') { *cr++ = '\0'; } if (strncmp(cp, "mode=", 5) == 0) { /* Magic string. */ int i; for(i = 0; permissions[i].string_mode != NULL; i++) { if (strcmp(permissions[i].string_mode, cp+5) == 0) { /* * If restricted, only change eXecute * permissions on directories. */ if (!no_change_exec_perms || strchr(cp+5,'X') == NULL || S_ISDIR(dir_info.st_mode)) new_mode |= permissions[i].permit_bits; break; } } if (permissions[i].string_mode == NULL) { HTAlert(gettext("Invalid mode format.")); return 0; } } else { HTAlert(gettext("Invalid syntax format.")); return 0; } cp = cr; } #ifdef UNIX /* * Call chmod(). */ HTSprintf(&tmpbuf, "chmod %.4o %s", (unsigned int)new_mode, destpath); sprintf(amode, "%.4o", (unsigned int)new_mode); args[0] = "chmod"; args[1] = amode; args[2] = destpath; args[3] = (char *) 0; if (LYExecv(CHMOD_PATH, args, tmpbuf) <= 0) { FREE(tmpbuf); return (-1); } FREE(tmpbuf); #endif /* UNIX */ LYforce_no_cache = TRUE; /* Force update of dired listing. */ return 1; } #endif /* !UNIX */ } #endif /* OK_PERMIT */ /* * Display or remove a tag from a given link. */ PUBLIC void tagflag ARGS2( int, flag, int, cur) { if (nlinks > 0) { move(links[cur].ly, 2); stop_reverse(); if (flag == ON) { addch('+'); } else { addch(' '); } #if defined(FANCY_CURSES) || defined(USE_SLANG) if (!LYShowCursor) move((LYlines - 1), (LYcols - 1)); /* get cursor out of the way */ else #endif /* FANCY CURSES || USE_SLANG */ /* * Never hide the cursor if there's no FANCY CURSES. */ move(links[cur].ly, links[cur].lx); refresh(); } } /* * Handle DIRED tags. */ PUBLIC void showtags ARGS1( HTList *, t) { int i; HTList *s; char *name; for (i = 0; i < nlinks; i++) { s = t; while ((name = HTList_nextObject(s)) != NULL) { if (!strcmp(links[i].lname, name)) { tagflag(ON, i); break; } } } } /* * Perform file management operations for LYNXDIRED URL's. * Attempt to be consistent. These are (pseudo) URLs - i.e. they should * be in URL syntax: some bytes will be URL-escaped with '%'. This is * necessary because these (pseudo) URLs will go through some of the same * kinds of interpretations and mutilations as real ones: HTParse, stripping * off #fragments etc. (Some access schemes currently have special rules * about not escaping parsing '#' "the URL way" built into HTParse, but that * doesn't look like a clean way.) */ #ifdef VMS extern BOOLEAN HadVMSInterrupt; #endif /* VMS */ PUBLIC int local_dired ARGS1( document *, doc) { char *line_url; /* will point to doc's address, which is a URL */ char *line = NULL; /* same as line_url, but HTUnEscaped, will be alloced */ char *cp, *tp, *bp; char *tmpbuf = NULL; char *buffer = NULL; line_url = doc->address; CTRACE(tfp, "local_dired: called for <%s>.\n", (line_url ? line_url : gettext("NULL URL pointer"))); HTUnEscapeSome(line_url, "/"); /* don't mess too much with *doc */ StrAllocCopy(line, line_url); HTUnEscape(line); /* _file_ (not URL) syntax, for those functions that need it. Don't forget to FREE it. */ tp = NULL; if (!strncmp(line, "LYNXDIRED://NEW_FILE", 20)) { if (create_file(&line[20]) > 0) LYforce_no_cache = TRUE; } else if (!strncmp(line, "LYNXDIRED://NEW_FOLDER", 22)) { if (create_directory(&line[22]) > 0) LYforce_no_cache = TRUE; } else if (!strncmp(line, "LYNXDIRED://INSTALL_SRC", 23)) { local_install(NULL, &line[23], &tp); StrAllocCopy(doc->address, tp); FREE(line); return 0; } else if (!strncmp(line, "LYNXDIRED://INSTALL_DEST", 24)) { local_install(&line[24], NULL, &tp); LYpop(doc); } else if (!strncmp(line, "LYNXDIRED://MODIFY_NAME", 23)) { if (modify_name(&line[23]) > 0) LYforce_no_cache = TRUE; } else if (!strncmp(line, "LYNXDIRED://MODIFY_LOCATION", 27)) { if (modify_location(&line[27]) > 0) LYforce_no_cache = TRUE; } else if (!strncmp(line, "LYNXDIRED://MOVE_TAGGED", 23)) { if (modify_tagged(&line_url[23]) > 0) LYforce_no_cache = TRUE; #ifdef OK_PERMIT } else if (!strncmp(line, "LYNXDIRED://PERMIT_SRC", 22)) { permit_location(NULL, &line[22], &tp); if (tp) /* * One of the checks may have failed. */ StrAllocCopy(doc->address, tp); FREE(line); return 0; } else if (!strncmp(line, "LYNXDIRED://PERMIT_LOCATION", 27)) { permit_location(&line_url[27], NULL, &tp); #endif /* OK_PERMIT */ } else if (!strncmp(line, "LYNXDIRED://REMOVE_SINGLE", 25)) { if (remove_single(&line[25]) > 0) LYforce_no_cache = TRUE; } else if (!strncmp(line, "LYNXDIRED://REMOVE_TAGGED", 25)) { if (remove_tagged()) LYforce_no_cache = TRUE; } else if (!strncmp(line, "LYNXDIRED://CLEAR_TAGGED", 24)) { clear_tags(); } else if (!strncmp(line, "LYNXDIRED://UPLOAD", 18)) { /* * They're written by LYUpload_options() HTUnEscaped; * don't want to change that for now... so pass through * without more unescaping. Directory names containing * '#' will probably fail. */ if (LYUpload(line_url)) LYforce_no_cache = TRUE; } else { LYTrimPathSep(line); if ((cp = strrchr(line, '/')) == NULL) { FREE(line); return 0; } /* * Construct the appropriate system command taking care to * escape all path references to avoid spoofing the shell. */ if (!strncmp(line, "LYNXDIRED://DECOMPRESS", 22)) { tp = quote_pathname(line + 22); HTSprintf0(&buffer,"%s %s", UNCOMPRESS_PATH, tp); FREE(tp); #if defined(OK_UUDECODE) && !defined(ARCHIVE_ONLY) } else if (!strncmp(line, "LYNXDIRED://UUDECODE", 20)) { tp = quote_pathname(line + 20); HTSprintf0(&buffer,"%s %s", UUDECODE_PATH, tp); HTAlert(gettext("Warning! UUDecoded file will exist in the directory you started Lynx.")); FREE(tp); #endif /* OK_UUDECODE && !ARCHIVE_ONLY */ #ifdef OK_TAR # ifndef ARCHIVE_ONLY # ifdef OK_GZIP } else if (!strncmp(line, "LYNXDIRED://UNTAR_GZ", 20)) { tp = quote_pathname(line+20); *cp++ = '\0'; cp = quote_pathname(line + 20); HTSprintf0(&buffer, "%s -qdc %s | (cd %s; %s -xf -)", GZIP_PATH, tp, cp, TAR_PATH); FREE(cp); FREE(tp); # endif /* OK_GZIP */ } else if (!strncmp(line, "LYNXDIRED://UNTAR_Z", 19)) { tp = quote_pathname(line + 19); *cp++ = '\0'; cp = quote_pathname(line + 19); HTSprintf0(&buffer, "%s %s | (cd %s; %s -xf -)", ZCAT_PATH, tp, cp, TAR_PATH); FREE(cp); FREE(tp); } else if (!strncmp(line, "LYNXDIRED://UNTAR", 17)) { tp = quote_pathname(line + 17); *cp++ = '\0'; cp = quote_pathname(line + 17); HTSprintf0(&buffer, "cd %s; %s -xf %s", cp, TAR_PATH, tp); FREE(cp); FREE(tp); # endif /* !ARCHIVE_ONLY */ # ifdef OK_GZIP } else if (!strncmp(line, "LYNXDIRED://TAR_GZ", 18)) { *cp++ = '\0'; cp = quote_pathname(cp); tp = quote_pathname(line + 18); HTSprintf0(&buffer, "(cd %s; %s -cf - %s) | %s -qc >%s/%s.tar.gz", tp, TAR_PATH, cp, GZIP_PATH, tp, cp); FREE(cp); FREE(tp); # endif /* OK_GZIP */ } else if (!strncmp(line, "LYNXDIRED://TAR_Z", 17)) { *cp++ = '\0'; cp = quote_pathname(cp); tp = quote_pathname(line + 17); HTSprintf0(&buffer, "(cd %s; %s -cf - %s) | %s >%s/%s.tar.Z", tp, TAR_PATH, cp, COMPRESS_PATH, tp, cp); FREE(cp); FREE(tp); } else if (!strncmp(line, "LYNXDIRED://TAR", 15)) { *cp++ = '\0'; cp = quote_pathname(cp); tp = quote_pathname(line + 15); HTSprintf0(&buffer, "(cd %s; %s -cf %s.tar %s)", tp, TAR_PATH, cp, cp); FREE(cp); FREE(tp); #endif /* OK_TAR */ #ifdef OK_GZIP } else if (!strncmp(line, "LYNXDIRED://GZIP", 16)) { tp = quote_pathname(line + 16); HTSprintf0(&buffer, "%s -q %s", GZIP_PATH, tp); FREE(tp); #ifndef ARCHIVE_ONLY } else if (!strncmp(line, "LYNXDIRED://UNGZIP", 18)) { tp = quote_pathname(line + 18); HTSprintf0(&buffer, "%s -d %s", GZIP_PATH, tp); FREE(tp); #endif /* !ARCHIVE_ONLY */ #endif /* OK_GZIP */ #ifdef OK_ZIP } else if (!strncmp(line, "LYNXDIRED://ZIP", 15)) { tp = quote_pathname(line + 15); *cp++ = '\0'; bp = quote_pathname(cp); cp = quote_pathname(line + 15); HTSprintf0(&buffer, "cd %s; %s -rq %s.zip %s", cp, ZIP_PATH, tp, bp); FREE(cp); FREE(bp); FREE(tp); #ifndef ARCHIVE_ONLY } else if (!strncmp(line, "LYNXDIRED://UNZIP", 17)) { tp = quote_pathname(line + 17); *cp = '\0'; cp = quote_pathname(line + 17); HTSprintf0(&buffer, "cd %s; %s -q %s", cp, UNZIP_PATH, tp); FREE(cp); FREE(tp); # endif /* !ARCHIVE_ONLY */ #endif /* OK_ZIP */ } else if (!strncmp(line, "LYNXDIRED://COMPRESS", 20)) { tp = quote_pathname(line + 20); HTSprintf0(&buffer, "%s %s", COMPRESS_PATH, tp); FREE(tp); } if (buffer != 0) { if (strlen(buffer) < 60) { HTSprintf0(&tmpbuf, gettext("Executing %s "), buffer); } else { HTSprintf0(&tmpbuf, gettext("Executing system command. This might take a while.")); } _statusline(tmpbuf); stop_curses(); printf("%s\n", tmpbuf); LYSystem(buffer); #ifdef VMS HadVMSInterrupt = FALSE; #endif /* VMS */ start_curses(); LYforce_no_cache = TRUE; } } FREE(tmpbuf); FREE(buffer); FREE(line); LYpop(doc); return 0; } /* * Provide a menu of file management options. */ PUBLIC int dired_options ARGS2( document *, doc, char **, newfile) { static char tempfile[LY_MAXPATH]; char path[512], dir[512]; /* much too large */ lynx_html_item_type *nxt; struct stat dir_info; FILE *fp0; char *cp = NULL; char *dir_url; char *path_url; BOOLEAN nothing_tagged; int count; struct dired_menu *mp; char buf[2048]; LYRemoveTemp(tempfile); if ((fp0 = LYOpenTemp(tempfile, HTML_SUFFIX, "w")) == NULL) { HTAlert(gettext("Unable to open file management menu file.")); return(0); } /* * Make the tempfile a URL. */ LYLocalFileToURL(newfile, tempfile); strcpy(LYDiredFileURL, *newfile); cp = HTpartURL_toFile(doc->address); strcpy(dir, cp); LYTrimPathSep(dir); if (doc->link > -1 && doc->link < (nlinks+1)) { cp = HTfullURL_toFile(links[doc->link].lname); strcpy(path, cp); LYTrimPathSep(path); free(cp); if (!ok_lstat(path, &dir_info)) { LYCloseTempFP(fp0); return 0; } } else { path[0] = '\0'; } nothing_tagged = (HTList_isEmpty(tagged)); BeginInternalPage(fp0, DIRED_MENU_TITLE, DIRED_MENU_HELP); fprintf(fp0, "<em>%s</em> %s<br>\n", gettext("Current directory:"), dir); if (nothing_tagged) { fprintf(fp0, "<em>%s</em> ", gettext("Current selection:")); if (strlen(path)) { fprintf(fp0, "%s<p>\n", path); } else { fprintf(fp0, "%s.<p>\n", gettext("Nothing currently selected.")); } } else { /* * Write out number of tagged items, and names of first * few of them relative to current (in the DIRED sense) * directory. */ int n = HTList_count(tagged); char *cp1 = NULL; char *cd = NULL; int i, m; #define NUM_TAGS_TO_WRITE 10 fprintf(fp0, "<em>%s</em> %d %s", gettext("Current selection:"), n, ((n == 1) ? gettext("tagged item:") : gettext("tagged items:"))); StrAllocCopy(cd, doc->address); HTUnEscapeSome(cd, "/"); LYAddHtmlSep(&cd); m = (n < NUM_TAGS_TO_WRITE) ? n : NUM_TAGS_TO_WRITE; for (i = 1; i <= m; i++) { cp1 = HTRelative(HTList_objectAt(tagged, i-1), (*cd ? cd : "file://localhost")); HTUnEscape(cp1); LYEntify(&cp1, TRUE); /* _should_ do this everywhere... */ fprintf(fp0, "%s<br>\n&nbsp;&nbsp;&nbsp;%s", (i == 1 ? "" : " ,"), cp1); FREE(cp1); } if (n > m) { fprintf(fp0," , ..."); } fprintf(fp0, "<p>\n"); FREE(cd); } /* * If menu_head is NULL then use defaults and link them together now. */ if (menu_head == NULL) { for (mp = defmenu; mp->href != NULL; mp++) mp->next = (mp + 1); (--mp)->next = NULL; menu_head = defmenu; } for (mp = menu_head; mp != NULL; mp = mp->next) { if (mp->cond != DE_TAG && !nothing_tagged) continue; if (mp->cond == DE_TAG && nothing_tagged) continue; if (mp->cond == DE_DIR && (!*path || !S_ISDIR(dir_info.st_mode))) continue; if (mp->cond == DE_FILE && (!*path || !S_ISREG(dir_info.st_mode))) continue; #ifdef S_IFLNK if (mp->cond == DE_SYMLINK && (!*path || !S_ISLNK(dir_info.st_mode))) continue; #endif if (*mp->sfx && (strlen(path) < strlen(mp->sfx) || strcmp(mp->sfx, &path[(strlen(path) - strlen(mp->sfx))]) != 0)) continue; dir_url = HTEscape(dir, URL_PATH); path_url = HTEscape(path, URL_PATH); fprintf(fp0, "<a href=\"%s", render_item(mp->href, path_url, dir_url, buf,sizeof(buf), YES)); fprintf(fp0, "\">%s</a> ", render_item(mp->link, path, dir, buf,sizeof(buf), NO)); fprintf(fp0, "%s<br>\n", render_item(mp->rest, path, dir, buf,sizeof(buf), NO)); FREE(dir_url); FREE(path_url); } if (uploaders != NULL) { fprintf(fp0, "<p>Upload to current directory:<p>\n"); for (count = 0, nxt = uploaders; nxt != NULL; nxt = nxt->next, count++) { fprintf(fp0, "<a href=\"LYNXDIRED://UPLOAD=%d/TO=%s\"> %s </a><br>\n", count, dir, nxt->name); } } EndInternalPage(fp0); LYCloseTempFP(fp0); LYforce_no_cache = TRUE; return(0); } /* * Check DIRED filename. */ PRIVATE char *filename ARGS3( char *, prompt, char *, buf, size_t, bufsize) { char *cp; _statusline(prompt); *buf = '\0'; LYgetstr(buf, VISIBLE, bufsize, NORECALL); if (strstr(buf, "../") != NULL) { HTAlert(gettext("Illegal filename; request ignored.")); return NULL; } if (no_dotfiles || !show_dotfiles) { cp = strrchr(buf, '/'); /* find last slash */ if (cp) cp += 1; else cp = buf; if (*cp == '.') { HTAlert(gettext("Illegal filename; request ignored.")); return NULL; } } return buf; } /* * Install the specified file or directory. */ PUBLIC BOOLEAN local_install ARGS3( char *, destpath, char *, srcpath, char **, newpath) { char *tmpbuf = NULL; static char savepath[512]; /* This will be the link that is to be installed. */ struct stat dir_info; char *args[6]; HTList *tag; int count = 0; int n = 0, src; /* indices into 'args[]' */ /* * Determine the status of the selected item. */ if (srcpath) { if (!ok_localname(savepath, srcpath)) return 0; LYforce_no_cache = TRUE; LYLocalFileToURL(newpath, Home_Dir()); StrAllocCat(*newpath, "/.installdirs.html"); return 0; } destpath = strip_trailing_slash(destpath); if (!ok_stat(destpath, &dir_info)) { return 0; } else if (!S_ISDIR(dir_info.st_mode)) { HTAlert(gettext("The selected item is not a directory! Request ignored.")); return 0; } else if (0 /*directory not writable*/) { HTAlert(gettext("Install in the selected directory not permitted.")); return 0; } statusline(gettext("Just a moment, ...")); args[n++] = "install"; #ifdef INSTALL_ARGS args[n++] = INSTALL_ARGS; #endif /* INSTALL_ARGS */ src = n++; args[n++] = destpath; args[n] = (char *)0; HTSprintf(&tmpbuf, "install %s", destpath); tag = tagged; if (HTList_isEmpty(tagged)) { args[src] = savepath; if (LYExecv(INSTALL_PATH, args, tmpbuf) <= 0) return (-1); count++; } else { char *name; while ((name = (char *)HTList_nextObject(tag))) { int err; args[src] = HTfullURL_toFile(name); err = (LYExecv(INSTALL_PATH, args, tmpbuf) <= 0); free(args[src]); if (err) return ((count == 0) ? -1 : count); count++; } clear_tags(); } FREE(tmpbuf); HTInfoMsg(gettext("Installation complete")); return count; } /* * Clear DIRED tags. */ PUBLIC void clear_tags NOARGS { char *cp = NULL; while ((cp = HTList_removeLastObject(tagged)) != NULL) { FREE(cp); } if (HTList_isEmpty(tagged)) FREE(tagged); } /* * Handle DIRED menu item. */ PUBLIC void add_menu_item ARGS1( char *, str) { struct dired_menu *new, *mp; char *cp; /* * First custom menu definition causes entire default menu to be * discarded. */ if (menu_head == defmenu) menu_head = NULL; new = (struct dired_menu *)calloc(1, sizeof(*new)); /* * Conditional on tagged != NULL ? */ cp = strchr(str, ':'); *cp++ = '\0'; if (strcasecomp(str, "tag") == 0) { new->cond = DE_TAG; } else if (strcasecomp(str, "dir") == 0) { new->cond = DE_DIR; } else if (strcasecomp(str, "file") == 0) { new->cond = DE_FILE; } else if (strcasecomp(str, "link") == 0) { new->cond = DE_SYMLINK; } /* * Conditional on matching suffix. */ str = cp; cp = strchr(str, ':'); *cp++ = '\0'; StrAllocCopy(new->sfx, str); str = cp; cp = strchr(str, ':'); *cp++ = '\0'; StrAllocCopy(new->link, str); str = cp; cp = strchr(str, ':'); *cp++ = '\0'; StrAllocCopy(new->rest, str); StrAllocCopy(new->href, cp); if (menu_head) { for (mp = menu_head; mp && mp->next != NULL; mp = mp->next) ; mp->next = new; } else menu_head = new; } /* * Create URL for DIRED HREF value. */ PRIVATE char * render_item ARGS6( CONST char *, s, char *, path, char *, dir, char *, buf, int, bufsize, BOOLEAN, url_syntax) { char *cp; char *bp; char overrun = '\0'; char *taglist = NULL; #define BP_INC (bp>buf+bufsize-2 ? &overrun : bp++) /* Buffer overrun could happen for very long tag list, if %l or %t are used */ bp = buf; while (*s && !overrun) { if (*s == '%') { s++; switch (*s) { case '%': *BP_INC = '%'; break; case 'p': cp = path; if (!LYIsHtmlSep(*cp)) *BP_INC = '/'; while (*cp) *BP_INC = *cp++; break; case 'd': cp = dir; if (!LYIsHtmlSep(*cp)) *BP_INC = '/'; while (*cp) *BP_INC = *cp++; break; case 'f': cp = strrchr(path, '/'); if (cp) cp++; else cp = path; while (*cp) *BP_INC = *cp++; break; case 'l': case 't': if (!HTList_isEmpty(tagged)) { HTList *cur = tagged; char *name; while (!overrun && (name = (char *)HTList_nextObject(cur))!=NULL) { if (*s == 'l' && (cp = strrchr(name, '/'))) cp++; else cp = name; StrAllocCat(taglist, cp); StrAllocCat(taglist, " "); /* should this be %20?*/ } } if (taglist) { /* could HTUnescape here... */ cp = taglist; while (*cp) *BP_INC = *cp++; FREE(taglist); } break; default: *BP_INC = '%'; *BP_INC =*s; break; } } else { /* * Other chars come from the lynx.cfg or * the default. Let's assume there isn't * anything weird there that needs escaping. */ *BP_INC =*s; } s++; } if (overrun & url_syntax) { strcpy(buf,gettext("Temporary URL or list would be too long.")); HTAlert(buf); bp = buf; /* set to start, will return empty string as URL */ } *bp = '\0'; return buf; } #endif /* DIRED_SUPPORT */ /* * Execute DIRED command. */ PRIVATE int LYExecv ARGS3( char *, path, char **, argv, char *, msg) { #if defined(VMS) || defined(_WINDOWS) CTRACE(tfp, "LYExecv: Called inappropriately!\n"); return(0); #else int rc; char *tmpbuf = 0; pid_t pid; #if HAVE_TYPE_UNIONWAIT union wait wstatus; #else int wstatus; #endif if (TRACE) { int n; CTRACE(tfp, "LYExecv path='%s'\n", path); for (n = 0; argv[n] != 0; n++) CTRACE(tfp, "argv[%d] = '%s'\n", n, argv[n]); } rc = 1; /* It will work */ stop_curses(); pid = fork(); /* fork and execute rm */ switch (pid) { case -1: HTSprintf(&tmpbuf, gettext("Unable to %s due to system error!"), msg); rc = 0; break; /* don't fall thru! - KW */ case 0: /* child */ execv(path, argv); exit(-1); /* execv failed, give wait() something to look at */ default: /* parent */ #if !HAVE_WAITPID while (wait(&wstatus) != pid) ; /* do nothing */ #else while (-1 == waitpid(pid, &wstatus, 0)) { /* wait for child */ #ifdef EINTR if (errno == EINTR) continue; #endif /* EINTR */ #ifdef ERESTARTSYS if (errno == ERESTARTSYS) continue; #endif /* ERESTARTSYS */ break; } #endif /* !HAVE_WAITPID */ if (WEXITSTATUS(wstatus) != 0 || WTERMSIG(wstatus) > 0) { /* error return */ HTSprintf(&tmpbuf, gettext("Probable failure to %s due to system error!"), msg); rc = 0; } } if (rc == 0) { /* * Screen may have message from the failed execv'd command. * Give user time to look at it before screen refresh. */ sleep(AlertSecs); } start_curses(); if (tmpbuf != 0) { HTAlert(tmpbuf); FREE(tmpbuf); } return(rc); #endif /* VMS */ }