* Fix not allow remote execution by adding PIPE_NOSHELL to the opening of a url by
[alpine.git] / alpine / osdep / execview.c
blobd7087caaa297fbef1a4ae381bc4acac6d7c78575
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: execview.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include <system.h>
20 #include <general.h>
22 #include "../../c-client/mail.h" /* for MAILSTREAM and friends */
23 #include "../../c-client/osdep.h"
24 #include "../../c-client/rfc822.h" /* for soutr_t and such */
25 #include "../../c-client/misc.h" /* for cpystr proto */
26 #include "../../c-client/utf8.h" /* for CHARSET and such*/
27 #include "../../c-client/imap4r1.h"
29 #include "../../pith/debug.h"
31 #include "../../pith/osdep/temp_nam.h"
32 #include "../../pith/osdep/color.h"
33 #include "../../pith/osdep/mimedisp.h"
35 #include "../../pith/charconv/utf8.h"
36 #include "../../pith/charconv/filesys.h"
38 #include "../pith/mailcap.h"
40 #include "../status.h"
41 #include "../radio.h"
42 #include "../signal.h"
43 #include "../../pico/estruct.h"
44 #include "../../pico/pico.h"
45 #include "../mailview.h"
46 #include "termin.gen.h"
48 #ifdef _WINDOWS
49 #include "../../pico/osdep/mswin.h"
50 #endif
52 /* Useful structures */
53 #if OSX_TARGET
54 typedef struct _execview_event_data_s {
55 int done;
56 ProcessSerialNumber pid;
57 int set_pid;
58 } EXEC_EVENT_DATA_S;
59 #endif
62 /* internal prototypes */
63 #if OSX_TARGET
64 pascal OSStatus osx_launch_app_callback(EventHandlerCallRef,
65 EventRef, void *);
66 int install_app_launch_cb(void *);
67 void osx_launch_special_handling(MCAP_CMD_S *, char *);
68 #endif
72 /* ----------------------------------------------------------------------
73 Execute the given mailcap command
75 Args: cmd -- the command to execute
76 image_file -- the file the data is in
77 needsterminal -- does this command want to take over the terminal?
78 ----*/
79 void
80 exec_mailcap_cmd(MCAP_CMD_S *mc_cmd, char *image_file, int needsterminal)
82 #ifdef _WINDOWS
83 STARTUPINFO start_info;
84 PROCESS_INFORMATION proc_info;
85 WINHAND childProcess;
86 int success = 0;
87 char *cmd;
88 LPTSTR image_file_lpt = NULL;
89 LPTSTR cmd_lpt = NULL;
91 /* no special handling yet, but could be used to replace '*' hack */
92 if(mc_cmd)
93 cmd = mc_cmd->command;
94 else
95 return;
97 dprint((9, "run_viewer: command=%s\n", cmd ? cmd : "?")) ;
99 if(image_file)
100 image_file_lpt = utf8_to_lptstr(image_file);
102 /* Set to READONLY so the viewer can't try to edit it and keep it around */
103 if(image_file_lpt)
104 SetFileAttributes(image_file_lpt, FILE_ATTRIBUTE_READONLY);
106 if(*cmd == '*' || (*cmd == '\"' && *(cmd+1) == '*')){
108 * It has been asked that there be the ability to do an
109 * "Open With..." on attachments like you can from the
110 * Windows file browser. After looking into this, it
111 * seems that the only way to do this would be through
112 * an undocumented hack. Here, we would pass "openas" as
113 * the verb to mswin_shell_exec (also some changes in
114 * mswin_shell_exec). Since this is the delicate world
115 * of attachment handling, it seems right not to rely on
116 * a hack. The interface wouldn't be too clean anyways,
117 * as we would have to download the attachment only to
118 * display the "Open With..." dialog. Go figure, some
119 * things Microsoft just wants to keep to themselves.
123 * 2/1/2007. No idea when the above comment was written, but it is
124 * documented now at least. The below two urls describe the "openas" verb:
126 * http://blogs.msdn.com/oldnewthing/archive/2004/11/26/270710.aspx
127 * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
128 * shellcc/platform/shell/programmersguide/shell_basics/
129 * shell_basics_extending/context.asp
131 success = mswin_shell_exec(cmd, &childProcess) == 0;
133 else{
134 memset(&proc_info, 0, sizeof(proc_info));
135 memset(&start_info, 0, sizeof(start_info));
136 start_info.dwFlags = STARTF_FORCEONFEEDBACK;
137 start_info.wShowWindow = SW_SHOWNORMAL;
139 if(cmd)
140 cmd_lpt = utf8_to_lptstr(cmd);
142 if(CreateProcess(NULL, cmd_lpt, NULL, NULL, FALSE,
143 CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
144 NULL, NULL, &start_info, &proc_info) == TRUE){
145 q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
146 dprint ((3, "CreatProcess(%s) Success.\n",
147 cmd ? cmd : "?"));
148 childProcess = proc_info.hProcess;
149 success = 1;
152 if(cmd_lpt)
153 fs_give((void **) &cmd_lpt);
156 if(!success){
157 int rc = (int) GetLastError();
158 if(image_file_lpt)
159 SetFileAttributes(image_file_lpt, FILE_ATTRIBUTE_NORMAL);
161 our_unlink(image_file);
162 q_status_message2(SM_ORDER, 3, 4, "\007Can't start viewer. %s%s.",
163 (rc == 2 || rc == 3) ? "Viewer not found: " :
164 (rc == 8) ? "Not enough memory" : "Windows error ",
165 (rc == 2 || rc == 3) ? cmd :
166 (rc == 8) ? "" : int2string(rc));
169 if(image_file_lpt)
170 fs_give((void **) &image_file_lpt);
172 #elif OSX_TARGET
174 char *command = NULL,
175 *result_file = NULL,
177 char **r_file_h;
178 PIPE_S *syspipe;
179 int mode;
181 if(!mc_cmd)
182 return;
183 if(mc_cmd->special_handling){
184 char *rhost;
186 if(mime_os_specific_access())
187 osx_launch_special_handling(mc_cmd, image_file);
188 else{
189 q_status_message(SM_ORDER, 0, 4, "VIEWER command cancelled");
190 our_unlink(image_file);
193 else {
194 char *cmd = mc_cmd->command;
195 size_t l;
197 l = 32 + strlen(cmd) + (2*strlen(image_file));
198 p = command = (char *) fs_get((l+1) * sizeof(char));
199 if(!needsterminal) /* put in background if it doesn't need terminal */
200 *p++ = '(';
202 snprintf(p, l+1-(p-command), "%s", cmd);
203 p += strlen(p);
204 if(!needsterminal){
205 if(p-command+2 < l+1){
206 *p++ = ')';
207 *p++ = ' ';
208 *p++ = '&';
212 if(p-command < l+1)
213 *p++ = '\n';
215 if(p-command < l+1)
216 *p = '\0';
218 dprint((9, "exec_mailcap_cmd: command=%s\n",
219 command ? command : "?"));
221 mode = PIPE_RESET;
222 if(needsterminal == 1)
223 r_file_h = NULL;
224 else{
225 mode |= PIPE_WRITE | PIPE_STDERR;
226 result_file = temp_nam(NULL, "pine_cmd");
227 r_file_h = &result_file;
230 if(syspipe = open_system_pipe(command, r_file_h, NULL, mode, 0, pipe_callback, NULL)){
231 close_system_pipe(&syspipe, NULL, pipe_callback);
232 if(needsterminal == 1)
233 q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
234 else if(needsterminal == 2)
235 display_output_file(result_file, "VIEWER", " command result", 1);
236 else
237 display_output_file(result_file, "VIEWER", " command launched", 1);
239 else
240 q_status_message1(SM_ORDER, 3, 4, "Cannot spawn command : %s", cmd);
242 fs_give((void **)&command);
243 if(result_file)
244 fs_give((void **)&result_file);
246 #else
247 char *command = NULL,
248 *result_file = NULL,
249 *p, *cmd;
250 char **r_file_h;
251 PIPE_S *syspipe;
252 int mode;
253 size_t l;
255 /* no os-specific command handling */
256 if(mc_cmd)
257 cmd = mc_cmd->command;
258 else
259 return;
260 l = 32 + strlen(cmd) + 2*strlen(image_file);
261 p = command = (char *)fs_get((l+1) * sizeof(char));
262 if(!needsterminal) /* put in background if it doesn't need terminal */
263 *p++ = '(';
264 snprintf(p, l+1-(p-command), "%s ; sleep %d ; rm -f %s", cmd, ps_global->sleep, image_file);
265 command[l] = '\0';
266 p += strlen(p);
267 if(!needsterminal && (p-command)+5 < l){
268 *p++ = ')';
269 *p++ = ' ';
270 *p++ = '&';
273 *p++ = '\n';
274 *p = '\0';
276 dprint((9, "exec_mailcap_cmd: command=%s\n",
277 command ? command : "?"));
279 mode = PIPE_RESET;
280 if(needsterminal == 1)
281 r_file_h = NULL;
282 else{
283 mode |= PIPE_WRITE | PIPE_STDERR;
284 result_file = temp_nam(NULL, "pine_cmd");
285 r_file_h = &result_file;
288 if((syspipe = open_system_pipe(command, r_file_h, NULL, mode, 0, pipe_callback, NULL)) != NULL){
289 close_system_pipe(&syspipe, NULL, pipe_callback);
290 if(needsterminal == 1)
291 q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
292 else if(needsterminal == 2)
293 display_output_file(result_file, "VIEWER", " command result", 1);
294 else
295 display_output_file(result_file, "VIEWER", " command launched", 1);
297 else
298 q_status_message1(SM_ORDER, 3, 4, "Cannot spawn command : %s", cmd);
300 fs_give((void **)&command);
302 if(result_file)
303 fs_give((void **)&result_file);
304 #endif
308 /* ----------------------------------------------------------------------
309 Execute the given mailcap test= cmd
311 Args: cmd -- command to execute
312 Returns exit status
314 ----*/
316 exec_mailcap_test_cmd(char *cmd)
318 #ifdef _WINDOWS
319 return((WinExec(cmd, SW_SHOWMINNOACTIVE) < 32) ? 1 : 0);
320 #else
321 PIPE_S *syspipe;
323 return((syspipe = open_system_pipe(cmd, NULL, NULL, PIPE_SILENT, 0,
324 pipe_callback, NULL))
325 ? close_system_pipe(&syspipe, NULL, pipe_callback) : -1);
326 #endif
330 char *
331 url_os_specified_browser(char *url)
333 #ifdef _WINDOWS
334 return(mswin_reg_default_browser(url));
335 #elif OSX_TARGET
336 if(mime_os_specific_access()){
337 return(cpystr("open"));
339 #else
340 /* do nothing here */
341 return(NULL);
342 #endif
346 * Return a pretty command, on some OS's we might do something
347 * different than just display the command.
349 * free_ret - whether or not to free the return value
351 char *
352 execview_pretty_command(MCAP_CMD_S *mc_cmd, int *free_ret)
354 char *str;
355 int rv_to_free = 0;
357 if(free_ret)
358 *free_ret = rv_to_free;
360 if(!mc_cmd)
361 return NULL;
363 str = mc_cmd->command;
365 #ifdef _WINDOWS
366 if(*str == '*' || (*str == '\"' && str[1] == '*')){
367 if(!strncmp(str + ((*str == '\"') ? 2 : 1), "DDE*", 4))
368 str = cpystr("via app already running");
369 else if(!strncmp(str + ((*str == '\"') ? 2 : 1),"ShellEx*",8))
370 str = cpystr("via Explorer defined app");
371 else
372 str = cpystr("via Windows-specific method");
374 rv_to_free = 1;
376 #elif OSX_TARGET
377 if(mc_cmd->special_handling){
378 CFStringRef str_ref = NULL, kind_str_ref = NULL;
379 CFURLRef url_ref;
380 char buf[256];
382 if((str_ref = CFStringCreateWithCString(NULL, mc_cmd->command,
383 kCFStringEncodingASCII)) == NULL)
384 return "";
386 if((url_ref = CFURLCreateWithString(NULL, str_ref, NULL)) == NULL)
387 return "";
389 if(LSCopyDisplayNameForURL(url_ref, &kind_str_ref) != noErr)
390 return "";
392 if(CFStringGetCString(kind_str_ref, buf, (CFIndex)255,
393 kCFStringEncodingASCII) == false)
394 return "";
396 buf[255] = '\0';
397 str = cpystr(buf);
398 rv_to_free = 1;
399 if(kind_str_ref)
400 CFRelease(kind_str_ref);
402 #else
403 /* always pretty */
404 #endif
406 if(free_ret)
407 *free_ret = rv_to_free;
409 return(str);
413 #if OSX_TARGET
414 void
415 osx_launch_special_handling(MCAP_CMD_S *mc_cmd, char *image_file)
417 CFStringRef str_ref = NULL;
418 CFURLRef url_ref = NULL;
419 LSLaunchFSRefSpec launch_spec;
420 FSRef app_ref, file_ref;
421 static EXEC_EVENT_DATA_S event_data;
423 install_app_launch_cb((void *)&event_data);
424 if((str_ref = CFStringCreateWithCString(NULL, mc_cmd->command,
425 kCFStringEncodingASCII)) == NULL)
426 return;
427 if((url_ref = CFURLCreateWithString(NULL, str_ref, NULL)) == NULL)
428 return;
429 if(CFURLGetFSRef(url_ref, &app_ref) == false)
430 return;
431 if(FSPathMakeRef((unsigned char *)image_file,
432 &file_ref, NULL) != noErr)
433 return;
434 launch_spec.appRef = &app_ref;
435 launch_spec.numDocs = 1;
436 launch_spec.itemRefs = &file_ref;
437 launch_spec.passThruParams = NULL;
438 launch_spec.launchFlags = kLSLaunchDontAddToRecents | kLSLaunchNoParams
439 | kLSLaunchAsync | kLSLaunchNewInstance;
440 /* would want to use this if we ever did true event handling */
441 launch_spec.asyncRefCon = 0;
443 if(LSOpenFromRefSpec( &launch_spec, NULL) == noErr){
445 * Here's the strategy: we want to be able to just launch
446 * the app and then just delete the temp file, but that
447 * doesn't work because the called app needs the temp file
448 * at least until it's finished loading. Being that there's
449 * no way to tell when the app has finished loading, we wait
450 * until the program has exited, which is the safest thing to
451 * do and is what we do for windows. Since we haven't totally
452 * embraced event handling at this point, we must do the waiting
453 * synchronously. We allow for a keystroke to stop waiting, and
454 * just delete the temp file.
455 * Ideally, we would launch the app, and keep running, checking
456 * the events until the process terminates, and then delete the
457 * temp file. In this method, we would delete the temp file
458 * at close time if the app was still running.
460 int ch;
461 OSStatus rne_rv;
462 EventTargetRef target;
463 EventRef out_event;
464 EventTypeSpec event_types[2] = {
465 {kEventClassApplication, kEventAppTerminated},
466 {kEventClassApplication, kEventAppLaunchNotification}};
468 q_status_message(SM_ORDER, 0, 4,
469 "Waiting for program to finish, or press a key to stop waiting...");
470 flush_status_messages(1);
471 target = GetEventDispatcherTarget();
472 event_data.done = 0;
473 event_data.set_pid = 0;
474 while(!event_data.done){
475 if((rne_rv = ReceiveNextEvent(2, event_types, 1,
476 true, &out_event)) == noErr){
477 SendEventToEventTarget(out_event, target);
478 ReleaseEvent(out_event);
480 else if(rne_rv == eventLoopTimedOutErr){
481 ch = read_char(1);
482 if(ch)
483 event_data.done = 1;
485 else if(rne_rv == eventLoopQuitErr)
486 event_data.done = 1;
488 our_unlink(image_file);
490 q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
493 pascal OSStatus osx_launch_app_callback(EventHandlerCallRef next_h, EventRef event,void *user_data)
495 EXEC_EVENT_DATA_S *ev_datap = (EXEC_EVENT_DATA_S *)user_data;
496 ProcessSerialNumber pid;
497 Boolean res = 0;
499 static int dont_do_anything_yet = 0;
500 switch(GetEventClass(event)){
501 case kEventClassKeyboard:
502 ev_datap->done = 1;
503 break;
504 case kEventClassApplication:
505 switch(GetEventKind(event)){
506 case kEventAppTerminated:
507 GetEventParameter(event,
508 kEventParamProcessID,
509 typeProcessSerialNumber, NULL,
510 sizeof(pid), NULL,
511 &pid);
512 SameProcess(&ev_datap->pid, &pid, &res);
513 if(res){
514 ev_datap->done = 1;
515 ev_datap->set_pid = 0;
517 break;
518 case kEventAppLaunchNotification:
519 /* could check asyncRef too */
520 if(!ev_datap->set_pid){ /* should always be true */
521 GetEventParameter(event,
522 kEventParamProcessID,
523 typeProcessSerialNumber, NULL,
524 sizeof(ev_datap->pid), NULL,
525 &(ev_datap->pid));
526 ev_datap->set_pid = 1;
528 break;
530 break;
532 return(noErr);
537 install_app_launch_cb(void *user_data)
539 static int already_installed = 0;
541 if(!already_installed){
542 EventHandlerUPP cb_upp;
543 EventTypeSpec event_types[2] = {
544 {kEventClassApplication, kEventAppTerminated},
545 {kEventClassApplication, kEventAppLaunchNotification}};
547 if((cb_upp = NewEventHandlerUPP(osx_launch_app_callback)) == NULL)
548 return 1;
549 InstallApplicationEventHandler(cb_upp, 2, event_types,
550 user_data, NULL);
551 already_installed = 1;
553 return 0;
555 #endif /* OSX_TARGET */