1 /*
2  * $LynxId: LYExtern.c,v 1.55 2018/02/15 01:53:07 tom Exp $
3  *
4  External application support.
5  This feature allows lynx to pass a given URL to an external program.
6  It was written for three reasons.
7  1) To overcome the deficiency	of Lynx_386 not supporting ftp and news.
8     External programs can be used instead by passing the URL.
9 
10  2) To allow for background transfers in multitasking systems.
11     I use wget for http and ftp transfers via the external command.
12 
13  3) To allow for new URLs to be used through lynx.
14     URLs can be made up such as mymail: to spawn desired applications
15     via the external command.
16 
17  See lynx.cfg for other info.
18 */
19 
20 #include <LYUtils.h>
21 
22 #ifdef USE_EXTERNALS
23 
24 #include <HTAlert.h>
25 #include <LYGlobalDefs.h>
26 #include <LYExtern.h>
27 #include <LYLeaks.h>
28 #include <LYCurses.h>
29 #include <LYReadCFG.h>
30 #include <LYStrings.h>
31 
32 #ifdef WIN_EX
33 /* ASCII char -> HEX digit */
34 #define ASC2HEXD(x) ((UCH(x) >= '0' && UCH(x) <= '9') ?               \
35 		     (UCH(x) - '0') : (toupper(UCH(x)) - 'A' + 10))
36 
37 /* Decodes the forms %xy in a URL to the character the hexadecimal
38    code of which is xy. xy are hexadecimal digits from
39    [0123456789ABCDEF] (case-insensitive). If x or y are not hex-digits
40    or '%' is near '\0', the whole sequence is inserted literally. */
41 
decode_string(char * s)42 static char *decode_string(char *s)
43 {
44     char *save_s;
45     char *p = s;
46 
47     save_s = s;
48     for (; *s; s++, p++) {
49 	if (*s != '%')
50 	    *p = *s;
51 	else {
52 	    /* Do nothing if at the end of the string. Or if the chars
53 	       are not hex-digits. */
54 	    if (!*(s + 1) || !*(s + 2)
55 		|| !(isxdigit(UCH(*(s + 1))) && isxdigit(UCH(*(s + 2))))) {
56 		*p = *s;
57 		continue;
58 	    }
59 	    *p = (char) ((ASC2HEXD(*(s + 1)) << 4) + ASC2HEXD(*(s + 2)));
60 	    s += 2;
61 	}
62     }
63     *p = '\0';
64     return save_s;
65 }
66 #endif /* WIN_EX */
67 
68 #ifdef WIN_EX
69 /*
70  *  Delete dangerous characters as local path.
71  *  We delete '<>|' and also '%"'.
72  *  '%' should be deleted because it's difficut to escape for all cases.
73  *  So we can't treat paths which include '%'.
74  *  '"' should be deleted because it's a obstacle to quote whole path.
75  */
delete_danger_characters(char * src)76 static void delete_danger_characters(char *src)
77 {
78     char *dst;
79 
80     for (dst = src; *src != '\0'; src++) {
81 	if (StrChr("<>|%\"", *src) == NULL) {
82 	    *dst = *src;
83 	    dst++;
84 	}
85     }
86     *dst = '\0';
87 }
88 
escapeParameter(CONST char * parameter)89 static char *escapeParameter(CONST char *parameter)
90 {
91     size_t i;
92     size_t last = strlen(parameter);
93     size_t n = 0;
94     size_t encoded = 0;
95     size_t escaped = 0;
96     char *result;
97     char *needs_encoded = "<>|";
98     char *needs_escaped = "%";
99     char *needs_escaped_NT = "%&^";
100 
101     for (i = 0; i < last; ++i) {
102 	if (StrChr(needs_encoded, parameter[i]) != NULL) {
103 	    ++encoded;
104 	}
105 	if (system_is_NT) {
106 	    if (StrChr(needs_escaped_NT, parameter[i]) != NULL) {
107 		++escaped;
108 	    }
109 	} else if (StrChr(needs_escaped, parameter[i]) != NULL) {
110 	    ++escaped;
111 	}
112     }
113 
114     result = (char *) malloc(last + encoded * 2 + escaped + 1);
115     if (result == NULL)
116 	outofmem(__FILE__, "escapeParameter");
117 
118     n = 0;
119     for (i = 0; i < last; i++) {
120 	if (StrChr(needs_encoded, parameter[i]) != NULL) {
121 	    sprintf(result + n, "%%%02X", (unsigned char) parameter[i]);
122 	    n += 3;
123 	    continue;
124 	}
125 	if (system_is_NT) {
126 	    if (StrChr(needs_escaped_NT, parameter[i]) != NULL) {
127 		result[n++] = '^';
128 		result[n++] = parameter[i];
129 		continue;
130 	    }
131 	} else if (StrChr(needs_escaped, parameter[i]) != NULL) {
132 	    result[n++] = '%';	/* parameter[i] is '%' */
133 	    result[n++] = parameter[i];
134 	    continue;
135 	}
136 	result[n++] = parameter[i];
137     }
138     result[n] = '\0';
139 
140     return result;
141 }
142 #endif /* WIN_EX */
143 
format(char ** result,char * fmt,char * parm)144 static void format(char **result,
145 		   char *fmt,
146 		   char *parm)
147 {
148     *result = NULL;
149     HTAddParam(result, fmt, 1, parm);
150     HTEndParam(result, fmt, 1);
151 }
152 
153 /*
154  * Format the given command into a buffer, returning the resulting string.
155  *
156  * It is too dangerous to leave any URL that may come along unquoted.  They
157  * often contain '&', ';', and '?' chars, and who knows what else may occur.
158  * Prevent spoofing of the shell.  Dunno how this needs to be modified for VMS
159  * or DOS.  - kw
160  */
format_command(char * command,char * param)161 static char *format_command(char *command,
162 			    char *param)
163 {
164     char *cmdbuf = NULL;
165 
166 #if defined(WIN_EX)
167     char pram_string[LY_MAXPATH];
168     char *escaped = NULL;
169 
170     if (strncasecomp("file://localhost/", param, 17) == 0) {
171 	/* decode local path parameter for programs to be
172 	   able to interpret - TH */
173 	LYStrNCpy(pram_string, param, sizeof(pram_string) - 1);
174 	decode_string(pram_string);
175 	param = pram_string;
176     } else {
177 	/* encode or escape URL parameter - TH */
178 	escaped = escapeParameter(param);
179 	param = escaped;
180     }
181 
182     if (isMAILTO_URL(param)) {
183 	format(&cmdbuf, command, param + 7);
184     } else if (strncasecomp("telnet://", param, 9) == 0) {
185 	char host[sizeof(pram_string)];
186 	int last_pos;
187 
188 	LYStrNCpy(host, param + 9, sizeof(host));
189 	last_pos = (int) strlen(host) - 1;
190 	if (last_pos > 1 && host[last_pos] == '/')
191 	    host[last_pos] = '\0';
192 
193 	format(&cmdbuf, command, host);
194     } else if (strncasecomp("file://localhost/", param, 17) == 0) {
195 	char e_buff[LY_MAXPATH], *p;
196 
197 	p = param + 17;
198 	delete_danger_characters(p);
199 	*e_buff = 0;
200 	if (StrChr(p, ':') == NULL) {
201 	    sprintf(e_buff, "%.3s/", windows_drive);
202 	}
203 	strncat(e_buff, p, sizeof(e_buff) - strlen(e_buff) - 1);
204 	p = strrchr(e_buff, '.');
205 	if (p) {
206 	    trimPoundSelector(p);
207 	}
208 
209 	/* Less ==> short filename with backslashes,
210 	 * less ==> long filename with forward slashes, may be quoted
211 	 */
212 	if (ISUPPER(command[0])) {
213 	    char *short_name = HTDOS_short_name(e_buff);
214 
215 	    p = quote_pathname(short_name);
216 	    format(&cmdbuf, command, p);
217 	    FREE(p);
218 	} else {
219 	    p = quote_pathname(e_buff);
220 	    format(&cmdbuf, command, p);
221 	    FREE(p);
222 	}
223     } else {
224 	format(&cmdbuf, command, param);
225     }
226     FREE(escaped);
227 #else
228     format(&cmdbuf, command, param);
229 #endif
230     return cmdbuf;
231 }
232 
233 /*
234  * Find the EXTERNAL command which matches the given name 'param'.  If there is
235  * more than one possibility, make a popup menu of the matching commands and
236  * allow the user to select one.  Return the selected command.
237  */
lookup_external(char * param,int only_overriders)238 static char *lookup_external(char *param,
239 			     int only_overriders)
240 {
241     int pass, num_disabled, num_matched, num_choices, cur_choice;
242     size_t length = 0;
243     char *cmdbuf = NULL;
244     char **actions = 0;
245     char **choices = 0;
246     lynx_list_item_type *ptr = 0;
247 
248     for (pass = 0; pass < 2; pass++) {
249 	num_disabled = 0;
250 	num_matched = 0;
251 	num_choices = 0;
252 	for (ptr = externals; ptr != 0; ptr = ptr->next) {
253 
254 	    if (match_item_by_name(ptr, param, only_overriders)) {
255 		++num_matched;
256 		CTRACE((tfp, "EXTERNAL: '%s' <==> '%s'\n", ptr->name, param));
257 		if (no_externals && !ptr->always_enabled && !only_overriders) {
258 		    ++num_disabled;
259 		} else {
260 		    if (pass == 0) {
261 			length++;
262 		    } else if (pass != 0) {
263 			cmdbuf = format_command(ptr->command, param);
264 			if (length > 1) {
265 			    actions[num_choices] = cmdbuf;
266 			    choices[num_choices] =
267 				format_command(ptr->menu_name, param);
268 			}
269 		    }
270 		    num_choices++;
271 		}
272 	    }
273 	}
274 	if (length > 1) {
275 	    if (pass == 0) {
276 		actions = typecallocn(char *, length + 1);
277 		choices = typecallocn(char *, length + 1);
278 
279 		if (actions == 0 || choices == 0)
280 		    outofmem(__FILE__, "lookup_external");
281 	    } else {
282 		actions[num_choices] = 0;
283 		choices[num_choices] = 0;
284 	    }
285 	}
286     }
287 
288     if (num_disabled != 0
289 	&& num_disabled == num_matched) {
290 	HTUserMsg(EXTERNALS_DISABLED);
291     } else if (num_choices > 1) {
292 	int old_y, old_x;
293 
294 	LYGetYX(old_y, old_x);
295 	cur_choice = LYhandlePopupList(-1,
296 				       0,
297 				       old_x,
298 				       (STRING2PTR) choices,
299 				       -1,
300 				       -1,
301 				       FALSE,
302 				       TRUE);
303 	wmove(LYwin, old_y, old_x);
304 	CTRACE((tfp, "selected choice %d of %d\n", cur_choice, num_choices));
305 	if (cur_choice < 0) {
306 	    HTInfoMsg(CANCELLED);
307 	    cmdbuf = 0;
308 	}
309 	for (pass = 0; choices[pass] != 0; pass++) {
310 	    if (pass == cur_choice) {
311 		cmdbuf = actions[pass];
312 	    } else {
313 		FREE(actions[pass]);
314 	    }
315 	    FREE(choices[pass]);
316 	}
317     }
318 
319     if (actions) {
320 	for (pass = 0; actions[pass] != 0; ++pass) {
321 	    if (actions[pass] != cmdbuf)
322 		FREE(actions[pass]);
323 	}
324 	FREE(actions);
325     }
326 
327     if (choices) {
328 	for (pass = 0; choices[pass] != 0; ++pass) {
329 	    FREE(choices[pass]);
330 	}
331 	FREE(choices);
332     }
333 
334     return cmdbuf;
335 }
336 
run_external(char * param,int only_overriders)337 BOOL run_external(char *param,
338 		  int only_overriders)
339 {
340 #ifdef WIN_EX
341     int status;
342 #endif
343     int redraw_flag = TRUE;
344     char *cmdbuf = NULL;
345     BOOL found = FALSE;
346     int confirmed = TRUE;
347 
348     if (externals == NULL)
349 	return 0;
350 
351 #ifdef WIN_EX			/* 1998/01/26 (Mon) 09:16:13 */
352     if (param == NULL) {
353 	HTInfoMsg(gettext("External command is null"));
354 	return 0;
355     }
356 #endif
357 
358     cmdbuf = lookup_external(param, only_overriders);
359     if (non_empty(cmdbuf)) {
360 #ifdef WIN_EX			/* 1997/10/17 (Fri) 14:07:50 */
361 	int len;
362 	char buff[LY_MAXPATH];
363 
364 	CTRACE((tfp, "Lynx EXTERNAL: '%s'\n", cmdbuf));
365 #ifdef WIN_GUI			/* 1997/11/06 (Thu) 14:17:15 */
366 	confirmed = MessageBox(GetForegroundWindow(), cmdbuf,
367 			       "Lynx (EXTERNAL COMMAND EXEC)",
368 			       MB_ICONQUESTION | MB_SETFOREGROUND | MB_OKCANCEL)
369 	    != IDCANCEL;
370 #else
371 	confirmed = HTConfirm(LYElideString(cmdbuf, 40)) != NO;
372 #endif
373 	if (confirmed) {
374 	    len = (int) strlen(cmdbuf);
375 	    if (len > 255) {
376 		sprintf(buff, "Lynx: command line too long (%d > 255)", len);
377 #ifdef WIN_GUI			/* 1997/11/06 (Thu) 14:17:02 */
378 		MessageBox(GetForegroundWindow(), buff,
379 			   "Lynx (EXTERNAL COMMAND EXEC)",
380 			   MB_ICONEXCLAMATION | MB_SETFOREGROUND | MB_OK);
381 		SetConsoleTitle("Lynx for Win32");
382 #else
383 		HTConfirm(LYElideString(buff, 40));
384 #endif
385 		confirmed = FALSE;
386 	    } else {
387 		SetConsoleTitle(cmdbuf);
388 	    }
389 	}
390 
391 	if (strncasecomp(cmdbuf, "start ", 6) == 0)
392 	    redraw_flag = FALSE;
393 	else
394 	    redraw_flag = TRUE;
395 #else
396 	HTUserMsg(cmdbuf);
397 #endif
398 	found = TRUE;
399 	if (confirmed) {
400 	    if (redraw_flag) {
401 		stop_curses();
402 		fflush(stdout);
403 	    }
404 
405 	    /* command running. */
406 #ifdef WIN_EX			/* 1997/10/17 (Fri) 14:07:50 */
407 #if defined(__CYGWIN__) || defined(__MINGW32__)
408 	    status = system(cmdbuf);
409 #else
410 	    status = xsystem(cmdbuf);
411 #endif
412 	    if (status != 0) {
413 		sprintf(buff,
414 			"EXEC code = %04x (%2d, %2d)\r\n"
415 			"'%s'",
416 			status, (status / 256), (status & 0xff),
417 			cmdbuf);
418 #ifdef SH_EX			/* WIN_GUI for ERROR only */
419 		MessageBox(GetForegroundWindow(), buff,
420 			   "Lynx (EXTERNAL COMMAND EXEC)",
421 			   MB_ICONSTOP | MB_SETFOREGROUND | MB_OK);
422 #else
423 		HTConfirm(LYElideString(buff, 40));
424 #endif /* 1 */
425 	    }
426 #else /* Not WIN_EX */
427 	    LYSystem(cmdbuf);
428 #endif /* WIN_EX */
429 
430 #if defined(WIN_EX)
431 	    SetConsoleTitle("Lynx for Win32");
432 #endif
433 	    if (redraw_flag) {
434 		fflush(stdout);
435 		start_curses();
436 	    }
437 	}
438     }
439 
440     FREE(cmdbuf);
441     return found;
442 }
443 #endif /* USE_EXTERNALS */
444