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