/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ /* Please leave outside of ifdef for windows precompiled headers */ #include "xp.h" #include "prmem.h" #include "plstr.h" #include "netutils.h" #include "mkselect.h" #include "mktcp.h" #include "net_xp_file.h" #include "mkgeturl.h" #include #ifdef MOZILLA_CLIENT #include "mkstream.h" #include "mkparse.h" #include "cvview.h" #include "libmime.h" /* for select(), fd_set, struct timeval */ #if defined(XP_UNIX) #include #include #include #if defined(AIX) #include #endif #elif defined(XP_WIN) #include #elif defined(XP_MAC) #include "macsocket.h" #endif /* for XP_GetString() */ #include extern int XP_CONFIRM_EXEC_UNIXCMD_ARE; extern int XP_CONFIRM_EXEC_UNIXCMD_MAYBE; extern int XP_ALERT_UNABLE_INVOKEVIEWER; extern int MK_UNABLE_TO_OPEN_TMP_FILE; typedef struct _CV_DataObject { FILE * fp; char * filename; char * command; char * url; unsigned int stream_block_size; int32 cur_size; int32 tot_size; MWContext * context; } CV_DataObject; /* ** build_viewer_cmd ** Build up the command for forking the external viewer. ** Argument list is the template for the command and the a set of ** (char,char*) pairs of characters to be recognized as '%' escapes ** and what they should expand to, terminated with a 0. ** ** Return value is a malloc'ed string, must be freed when command is done. ** ** Example: ** char* s=build_viewer(line_from_mailcap, 's', tmpFile, 'u', url, 0); ** ** I'm completely unsure what to do about security considerations and ** encodings. Should the URL get % encoded. What if it contains "bad" ** characters. etc. etc. etc */ char* build_viewer_cmd(char *template, ...) { va_list args; char *ret, *from, *to; int len; if (template == NULL) return NULL; len = strlen(template); ret = (char*) malloc(len+1); if (ret == NULL) return NULL; from = template, to = ret; while (*from) { if (*from != '%' || *++from == '%') { *to++ = *from++; } else { /* ** We have a % escape, now look through all the arguments for ** a matching one. When one is found, substitute in the ** passed value. If none is found, the % and following character ** get swallowed. */ char argc; char* argv; va_start(args, template); while ((argc = va_arg(args, int)) != 0) { argv = va_arg(args, char*); if (*from == argc) { int off = to - ret; int arglen = strlen(argv); len = len + arglen - 2; ret = (char*) realloc(ret, len + 1); if (ret == NULL) return NULL; PL_strcpy(ret + off, argv); to = ret + off + arglen; break; } } if (*from) from++; /* skip char following % unless it was last */ va_end(args); } } *to = '\0'; return ret; } PRIVATE int net_ExtViewWrite (NET_StreamClass *stream, CONST char* s, int32 l) { CV_DataObject *obj=stream->data_object; if(obj->tot_size) { obj->cur_size += l; obj->context->funcs->SetProgressBarPercent(obj->context, (obj->cur_size*100)/obj->tot_size); } /* TRACEMSG(("Length of string passed to display: %d\n",l)); */ return(fwrite((char *) s, 1, l, obj->fp)); } PRIVATE int net_ExtViewWriteReady (NET_StreamClass * stream) { CV_DataObject *obj=stream->data_object; fd_set write_fds; struct timeval timeout; int ret; if(obj->command) { return(MAX_WRITE_READY); /* never wait for files */ } timeout.tv_sec = 0; timeout.tv_usec = 1; /* minimum hopefully */ memset(&write_fds, 0, sizeof(fd_set)); FD_SET(fileno(obj->fp), &write_fds); ret = select(fileno(obj->fp)+1, NULL, &write_fds, NULL, &timeout); if(ret) return(obj->stream_block_size); /* read in a max of 8000 bytes */ else return(0); } PRIVATE void net_ExtViewComplete (NET_StreamClass *stream) { CV_DataObject *obj=stream->data_object; obj->context->funcs->SetProgressBarPercent(obj->context, 100); if(obj->command) { char *p_tmp; char *command; /* restrict to allowed url chars * */ for(p_tmp = obj->url; *p_tmp != '\0'; p_tmp++) if( (*p_tmp >= '0' && *p_tmp <= '9') || (*p_tmp >= 'A' && *p_tmp <= 'Z') || (*p_tmp >= 'a' && *p_tmp <= 'z') || (*p_tmp == '_') || (*p_tmp == '?') || (*p_tmp == '#') || (*p_tmp == '&') || (*p_tmp == '%') || (*p_tmp == '/') || (*p_tmp == ':') || (*p_tmp == '+') || (*p_tmp == '.') || (*p_tmp == '~') || (*p_tmp == '=') || (*p_tmp == '-')) { /* this is a good character. Allow it. */ } else { *p_tmp = '\0'; break; } command=build_viewer_cmd(obj->command, 's', obj->filename, 'u', obj->url, 0); fclose(obj->fp); TRACEMSG(("Invoking: %s", command)); system(command); PR_FREEIF(obj->command); } else { pclose(obj->fp); } PR_FREEIF(obj->filename); PR_FREEIF(obj->url); PR_Free(obj); return; } PRIVATE void net_ExtViewAbort (NET_StreamClass *stream, int status) { CV_DataObject *obj=stream->data_object; obj->context->funcs->SetProgressBarPercent(obj->context, 100); fclose(obj->fp); if(obj->filename) { remove(obj->filename); PR_Free(obj->filename); } PR_FREEIF(obj->url); PR_FREEIF(obj->command); PR_Free(obj); return; } #ifdef XP_UNIX extern char **fe_encoding_extensions; /* gag! */ #endif PUBLIC NET_StreamClass * NET_ExtViewerConverter (int format_out, void *data_obj, URL_Struct *URL_s, MWContext *window_id) { CV_DataObject* obj; NET_StreamClass* stream; char *tmp_filename; char *dot; char *path; CV_ExtViewStruct * view_struct = (CV_ExtViewStruct *)data_obj; char small_buf[256]; int yes_stream=0; /* If this URL is a mail or news attachment, use the name of that attachment as the URL -- this is so the temp file gets the right extension on it (some helper apps are picky about that...) */ path = MimeGuessURLContentName(window_id, URL_s->address); if (!path) path = NET_ParseURL(URL_s->address, GET_PATH_PART); if (!path) return 0; TRACEMSG(("Setting up display stream. Have URL: %s\n", URL_s->address)); stream = PR_NEW(NET_StreamClass); if(stream == NULL) return(NULL); memset(stream, 0, sizeof(NET_StreamClass)); obj = PR_NEW(CV_DataObject); if (obj == NULL) return(NULL); memset(obj, 0, sizeof(CV_DataObject)); obj->context = window_id; if(URL_s->content_length) { obj->tot_size = URL_s->content_length; } else { /* start the progress bar cyloning */ obj->context->funcs->SetProgressBarPercent(window_id, -1); } stream->name = "Execute external viewer"; stream->complete = (MKStreamCompleteFunc) net_ExtViewComplete; stream->abort = (MKStreamAbortFunc) net_ExtViewAbort; stream->put_block = (MKStreamWriteFunc) net_ExtViewWrite; stream->is_write_ready = (MKStreamWriteReadyFunc) net_ExtViewWriteReady; stream->data_object = obj; /* document info object */ stream->window_id = window_id; #ifdef XP_UNIX /* Some naive people may have trustingly put application/x-sh; sh %s application/x-csh; csh %s in their mailcap files without realizing how dangerous that is. Worse, it might be there and they might not realize it. So, if we're about to execute a shell, pop up a dialog box first. */ { char *prog = PL_strdup (view_struct->system_command); char *s, *start, *end; int danger = 0; /* strip end space */ end = XP_StripLine(prog); /* Extract the leaf name of the program: " /bin/sh -foo" ==> "sh". */ for (; *end && !NET_IS_SPACE(*end); end++) ; *end = 0; if ((start = PL_strrchr (prog, '/'))) start++; else start = XP_StripLine(prog); /* start at first non-white space */ /* Strip off everything after the first nonalphabetic. This is so "perl-4.0" is compared as "perl" and "emacs19" is compared as "emacs". */ for (s = start; *s; s++) if (!isalpha (*s)) *s = 0; /* These are things known to be shells - very bad. */ if (!PL_strcmp (start, "ash") || !PL_strcmp (start, "bash") || !PL_strcmp (start, "csh") || !PL_strcmp (start, "jsh") || !PL_strcmp (start, "ksh") || !PL_strcmp (start, "pdksh") || !PL_strcmp (start, "sh") || !PL_strcmp (start, "tclsh") || !PL_strcmp (start, "tcsh") || !PL_strcmp (start, "wish") || /* a tcl thing */ !PL_strcmp (start, "wksh") || !PL_strcmp (start, "zsh")) danger = 2; /* Remote shells are potentially dangerous, in the case of "rsh somehost some-dangerous-program", but it's hard to parse that out, since rsh could take arbitrarily complicated args, like "rsh somehost -u something -pass8 /bin/sh %s". And we don't want to squawk about "rsh somehost playulaw -". So... allow rsh to possibly be a security hole. */ else if (!PL_strcmp (start, "remsh") || /* remote shell */ !PL_strcmp (start, "rksh") || !PL_strcmp (start, "rsh") /* remote- or restricted- */ ) danger = 0; /* These are things which aren't really shells, but can do the same damage anyway since they can write files and/or execute other programs. */ else if (!PL_strcmp (start, "awk") || !PL_strcmp (start, "e") || !PL_strcmp (start, "ed") || !PL_strcmp (start, "ex") || !PL_strcmp (start, "gawk") || !PL_strcmp (start, "m4") || !PL_strcmp (start, "sed") || !PL_strcmp (start, "vi") || !PL_strcmp (start, "emacs") || !PL_strcmp (start, "lemacs") || !PL_strcmp (start, "xemacs") || !PL_strcmp (start, "temacs") || /* Other dangerous interpreters */ !PL_strcmp (start, "basic") || !PL_strcmp (start, "expect") || !PL_strcmp (start, "expectk") || !PL_strcmp (start, "perl") || !PL_strcmp (start, "python") || !PL_strcmp (start, "rexx") ) danger = 1; /* Be suspicious of anything ending in "sh". */ else if (PL_strlen (start) > 2 && !PL_strcmp (start + PL_strlen (start) - 2, "sh")) danger = 1; if (danger) { char msg [2048]; PR_snprintf (msg, sizeof(msg), (danger > 1 ? XP_GetString(XP_CONFIRM_EXEC_UNIXCMD_ARE) : XP_GetString(XP_CONFIRM_EXEC_UNIXCMD_MAYBE)), start ); if (!FE_Confirm (window_id, msg)) { PR_Free (stream); PR_Free (obj); PR_Free (path); PR_Free (prog); return(NULL); } } PR_Free (prog); } #endif /* XP_UNIX */ if(view_struct->stream_block_size) { /* asks the user if they want to stream data. * -1 cancel * 0 No, don't stream data, play from the file * 1 Yes, stream the data from the network */ if (NET_URL_Type (URL_s->address) == ABOUT_TYPE_URL) yes_stream = 1; else yes_stream = FE_AskStreamQuestion(window_id); if(yes_stream == -1) { PR_Free(stream); PR_Free(obj); PR_Free(path); return(NULL); } } if(yes_stream && view_struct->stream_block_size) { /* use popen */ obj->fp = popen(view_struct->system_command, "w"); if(!obj->fp) { FE_Alert(window_id, XP_GetString(XP_ALERT_UNABLE_INVOKEVIEWER)); return(NULL); } obj->stream_block_size = view_struct->stream_block_size; signal(SIGPIPE, SIG_IGN); } else { dot = PL_strrchr(path, '.'); #ifdef XP_UNIX /* Gag. foo.ps.gz --> tmpXXXXX.ps, not tmpXXXXX.gz. */ if (dot && fe_encoding_extensions) { int i = 0; while (fe_encoding_extensions [i]) { if (!PL_strcmp (dot, fe_encoding_extensions [i])) { *dot = 0; dot--; while (dot > path && *dot != '.') dot--; if (*dot != '.') dot = 0; break; } i++; } } #endif /* XP_UNIX */ tmp_filename = WH_TempName(xpTemporary, "MO"); if (!tmp_filename) { PR_FREEIF(stream); PR_FREEIF(obj); return NULL; } if (dot) { char * p_tmp; StrAllocCopy(obj->filename, tmp_filename); /* restrict to ascii alphanumeric chars * * this fixes really bad security hole */ for(p_tmp = dot+1; *p_tmp != '\0'; p_tmp++) if( (*p_tmp >= '0' && *p_tmp <= '9') || (*p_tmp >= 'A' && *p_tmp <= 'Z') || (*p_tmp >= 'a' && *p_tmp <= 'z') || (*p_tmp == '_') || (*p_tmp == '+') || (*p_tmp == '-')) { /* this is a good character. Allow it. */ } else { *p_tmp = '\0'; break; } StrAllocCat(obj->filename, dot); } else { StrAllocCopy(obj->filename, tmp_filename); } PR_Free(path); PR_Free(tmp_filename); obj->fp = NET_XP_FileOpen(obj->filename, xpTemporary, XP_FILE_WRITE); TRACEMSG(("Trying to open output file: %s\n", obj->filename)); if(!obj->fp) { char *s = NET_ExplainErrorDetails (MK_UNABLE_TO_OPEN_TMP_FILE, obj->filename); if (s) { FE_Alert (window_id, s); PR_Free (s); } return(NULL); } /* construct the command like this * * (( COMMAND ); rm %s )& */ StrAllocCopy(obj->command, "(("); /* this is a stream writable program that the user wants * to use non streaming */ if(view_struct->stream_block_size) StrAllocCat(obj->command, "cat %s | "); StrAllocCat(obj->command, view_struct->system_command); PR_snprintf(small_buf, sizeof(small_buf), "); rm %.200s )&", obj->filename); StrAllocCat(obj->command, small_buf); } StrAllocCopy(obj->url, URL_s->address); TRACEMSG(("Returning stream from NET_ExtViewer\n")); return stream; } #endif /* MOZILLA_CLIENT */