On Mon, Jun 19, 2017 at 06:39:22AM +0200, Jürgen Spitzmüller wrote: > Am Sonntag, den 18.06.2017, 19:56 +0200 schrieb Enrico Forestieri: > > > I think we need to provide an option to add -shell-escape only to > > > specific documents and only on the given machine. This prevents > > > sending > > > documents with -shell-escape (main problem of a document setting). > > > > This is contradictory. We avoid sending documents with -shell-escape > > but then add it to specific documents. So, it is the same thing. > > No, it isn't. I didn't propose a document property, but a per-document > session setting. This is a completely different thing.
Sorry, it was not clear to me what you meant. Here is a patch following this strategy. - We never store in the document the need for -shell-escape. - When the user checks the toolbar button and then runs a latex backend, he is alerted that the backend will be allowed to run external programs. - At this point, he can decide to let the backend run (and be asked again next time), or to always allow execution with -shell-escape for this doc. - If the user chooses to always allow -shell-escape for the current document, the document path is stored in the session file, so that next time it is loaded on the current machine, the toolbar button will be automatically toggled and no question will be asked. - If the user manually toggles the toolbar button so that to disallow the -shell-escape option for an authorized document, the document is automatically removed from the list of authorized documents. This patch does not introduce a format change, because nothing is recorded in the document (the document status is only recorded in the session file). -- Enrico
diff --git a/lib/ui/stdtoolbars.inc b/lib/ui/stdtoolbars.inc index 9da37ecf75..794325665a 100644 --- a/lib/ui/stdtoolbars.inc +++ b/lib/ui/stdtoolbars.inc @@ -103,6 +103,7 @@ ToolbarSet Item "Update" "buffer-update" Item "View master document" "master-buffer-view" Item "Update master document" "master-buffer-update" + Item "Allow running external programs" "buffer-toggle-shell-escape" Item "Enable Forward/Reverse Search" "buffer-toggle-output-sync" Separator StickyPopupMenu "view-others" "View other formats" diff --git a/src/Buffer.cpp b/src/Buffer.cpp index 61b89200c1..b66c2522fe 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -55,6 +55,7 @@ #include "ParagraphParameters.h" #include "ParIterator.h" #include "PDFOptions.h" +#include "Session.h" #include "SpellChecker.h" #include "sgml.h" #include "texstream.h" @@ -980,6 +981,11 @@ int Buffer::readHeader(Lexer & lex) errorList.push_back(ErrorItem(_("Document header error"), s)); } + std::set<std::string> & shellescape_files = + theSession().shellescapeFiles().shellescapeFiles(); + if (shellescape_files.find(absFileName()) != shellescape_files.end()) + params().shell_escape = true; + params().makeDocumentClass(); return unknown_tokens; @@ -2613,6 +2619,11 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) break; } + case LFUN_BUFFER_TOGGLE_SHELL_ESCAPE: { + flag.setOnOff(params().shell_escape); + break; + } + case LFUN_BUFFER_TOGGLE_OUTPUT_SYNC: { flag.setOnOff(params().output_sync); break; @@ -2888,6 +2899,18 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) params().compressed = !params().compressed; break; + case LFUN_BUFFER_TOGGLE_SHELL_ESCAPE: + params().shell_escape = !params().shell_escape; + if (!params().shell_escape) { + std::set<std::string> & shellescape_files = + theSession().shellescapeFiles().shellescapeFiles(); + std::set<std::string>::iterator it = + shellescape_files.find(absFileName()); + if (it != shellescape_files.end()) + shellescape_files.erase(it); + } + break; + case LFUN_BUFFER_TOGGLE_OUTPUT_SYNC: undo().recordUndoBufferParams(CursorData()); params().output_sync = !params().output_sync; diff --git a/src/BufferParams.cpp b/src/BufferParams.cpp index 38ca643400..8b282171ef 100644 --- a/src/BufferParams.cpp +++ b/src/BufferParams.cpp @@ -459,6 +459,7 @@ BufferParams::BufferParams() html_css_as_file = false; display_pixel_ratio = 1.0; + shell_escape = false; output_sync = false; use_refstyle = true; use_minted = false; diff --git a/src/BufferParams.h b/src/BufferParams.h index 9f20ce14c6..aa33b9a61e 100644 --- a/src/BufferParams.h +++ b/src/BufferParams.h @@ -535,6 +535,8 @@ public: std::string html_latex_end; /// bool html_css_as_file; + /// allow the LaTeX backend to run external programs + bool shell_escape; /// generate output usable for reverse/forward search bool output_sync; /// custom LaTeX macro from user instead our own diff --git a/src/Converter.cpp b/src/Converter.cpp index 6e10b18704..4e29c29a1e 100644 --- a/src/Converter.cpp +++ b/src/Converter.cpp @@ -279,20 +279,38 @@ OutputParams::FLAVOR Converters::getFlavor(Graph::EdgePath const & path, } -bool Converters::checkAuth(Converter const & conv, string const & doc_fname) +bool Converters::checkAuth(Converter const & conv, string const & doc_fname, + bool use_shell_escape) { - if (!conv.need_auth()) + if (!conv.latex()) + use_shell_escape = false; + if (!conv.need_auth() && !use_shell_escape) return true; - const docstring security_warning = bformat( - _("<p>The requested operation requires the use of a converter from " - "%2$s to %3$s:</p>" + string conv_command = conv.command(); + bool const has_shell_escape = contains(conv_command, "-shell-escape"); + size_t const token_pos = conv_command.find("$$"); + bool const has_token = token_pos != string::npos; + string const command = use_shell_escape && !has_shell_escape + ? (has_token ? conv_command.insert(token_pos, "-shell-escape ") + : conv_command.append(" -shell-escape")) + : conv_command; + docstring const security_warning = (use_shell_escape + ? bformat(_("<p>The following LaTeX backend has been requested " + "to allow execution of external programs:</p>" + "<center><p><tt>%1$s</tt></p></center>" + "<p>The external programs can execute arbitrary commands on " + "your system, including dangerous ones, if instructed to do " + "so by a maliciously crafted LyX document.</p>"), + from_utf8(command)) + : bformat(_("<p>The requested operation requires the use of a " + "converter from %2$s to %3$s:</p>" "<blockquote><p><tt>%1$s</tt></p></blockquote>" - "<p>This external program can execute arbitrary commands on your " - "system, including dangerous ones, if instructed to do so by a " - "maliciously crafted .lyx document.</p>"), - from_utf8(conv.command()), from_utf8(conv.from()), - from_utf8(conv.to())); - if (lyxrc.use_converter_needauth_forbidden) { + "<p>This external program can execute arbitrary commands on " + "your system, including dangerous ones, if instructed to do " + "so by a maliciously crafted LyX document.</p>"), + from_utf8(command), from_utf8(conv.from()), + from_utf8(conv.to()))); + if (lyxrc.use_converter_needauth_forbidden && !use_shell_escape) { frontend::Alert::error( _("An external converter is disabled for security reasons"), security_warning + _( @@ -302,29 +320,43 @@ bool Converters::checkAuth(Converter const & conv, string const & doc_fname) "Forbid needauth converters</i>.)"), false); return false; } - if (!lyxrc.use_converter_needauth) + if (!lyxrc.use_converter_needauth && !use_shell_escape) return true; - static const docstring security_title = - _("An external converter requires your authorization"); + docstring const security_title = use_shell_escape + ? _("A LaTeX backend requires your authorization") + : _("An external converter requires your authorization"); int choice; - const docstring security_warning2 = security_warning + - _("<p>Would you like to run this converter?</p>" - "<p><b>Only run if you trust the origin/sender of the LyX " - "document!</b></p>"); + docstring const security_warning2 = security_warning + (use_shell_escape + ? _("<p>Should LaTeX backends be allowed to run external " + "programs?</p><p><b>Allow them only if you trust the " + "origin/sender of the LyX document!</b></p>") + : _("<p>Would you like to run this converter?</p>" + "<p><b>Only run if you trust the origin/sender of the LyX " + "document!</b></p>")); + docstring const no = use_shell_escape + ? _("Do ¬ allow") : _("Do ¬ run"); + docstring const yes = use_shell_escape ? _("A&llow") : _("&Run"); + docstring const always = use_shell_escape + ? _("&Always allow for this document") + : _("&Always run for this document"); if (!doc_fname.empty()) { LYXERR(Debug::FILES, "looking up: " << doc_fname); - std::set<std::string> & auth_files = theSession().authFiles().authFiles(); + std::set<std::string> & auth_files = use_shell_escape + ? theSession().shellescapeFiles().shellescapeFiles() + : theSession().authFiles().authFiles(); if (auth_files.find(doc_fname) == auth_files.end()) { - choice = frontend::Alert::prompt(security_title, security_warning2, - 0, 0, _("Do ¬ run"), _("&Run"), _("&Always run for this document")); + choice = frontend::Alert::prompt(security_title, + security_warning2, + 0, 0, no, yes, always); if (choice == 2) auth_files.insert(doc_fname); } else { choice = 1; } } else { - choice = frontend::Alert::prompt(security_title, security_warning2, - 0, 0, _("Do ¬ run"), _("&Run")); + choice = frontend::Alert::prompt(security_title, + security_warning2, + 0, 0, no, yes); } return choice != 0; } @@ -459,7 +491,8 @@ bool Converters::convert(Buffer const * buffer, "tmpfile.out")); } - if (!checkAuth(conv, buffer ? buffer->absFileName() : string())) + if (!checkAuth(conv, buffer ? buffer->absFileName() : string(), + buffer && buffer->params().shell_escape)) return false; if (conv.latex()) { @@ -470,6 +503,9 @@ bool Converters::convert(Buffer const * buffer, command = subst(command, token_from, ""); command = subst(command, token_latex_encoding, buffer->params().encoding().latexName()); + if (buffer->params().shell_escape + && !contains(command, "-shell-escape")) + command += " -shell-escape "; LYXERR(Debug::FILES, "Running " << command); if (!runLaTeX(*buffer, command, runparams, errorList)) return false; diff --git a/src/Converter.h b/src/Converter.h index 1ea73279b2..1963980c4f 100644 --- a/src/Converter.h +++ b/src/Converter.h @@ -193,8 +193,14 @@ public: /// able to execute arbitrary code, tagged with the 'needauth' option, /// authorization is: always denied if lyxrc.use_converter_needauth_forbidden /// is enabled; always allowed if the lyxrc.use_converter_needauth - /// is disabled; user is prompted otherwise - bool checkAuth(Converter const & conv, std::string const & doc_fname); + /// is disabled; user is prompted otherwise. + /// However, if use_shell_escape is true and a LaTeX backend is + /// going to be executed, both lyxrc.use_converter_needauth and + /// lyxrc.use_converter_needauth_forbidden are ignored, because in + /// this case the backend has to be executed and LyX will add the + /// -shell-escape option, so that user consent is always needed. + bool checkAuth(Converter const & conv, std::string const & doc_fname, + bool use_shell_escape = false); private: /// diff --git a/src/FuncCode.h b/src/FuncCode.h index 7949bce41a..33249b4635 100644 --- a/src/FuncCode.h +++ b/src/FuncCode.h @@ -473,6 +473,8 @@ enum FuncCode LFUN_BUFFER_ZOOM, // daniel, 20161028 LFUN_TOOLBAR_MOVABLE, // daniel, 20160712 LFUN_FONT_CROSSOUT, // uwestoehr 20170404 + LFUN_BUFFER_TOGGLE_SHELL_ESCAPE,// ef 20170618 + // 370 LFUN_LASTACTION // end of the table }; diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp index c068db9bea..2b8b6ecbb4 100644 --- a/src/LyXAction.cpp +++ b/src/LyXAction.cpp @@ -780,6 +780,17 @@ void LyXAction::init() { LFUN_BUFFER_TOGGLE_COMPRESSION, "buffer-toggle-compression", Noop, Buffer }, /*! + * \var lyx::FuncCode lyx::LFUN_BUFFER_TOGGLE_SHELL_ESCAPE + * \li Action: Toggles consent to run external programs by the LaTeX backend. + * \li Notion: When toggled on, the -shell-escape option is added to the + command that runs a LaTeX backend. + * \li Syntax: buffer-toggle-shell-escape + * \li Origin: ef, 18 June 2017 + * \endvar + */ + { LFUN_BUFFER_TOGGLE_SHELL_ESCAPE, "buffer-toggle-shell-escape", Noop, System }, + +/*! * \var lyx::FuncCode lyx::LFUN_BUFFER_TOGGLE_OUTPUT_SYNC * \li Action: Toggles including of resources for forward/reverse search of the given document. * \li Notion: When toggled on, SyncTeX is invoked for PDF, while srcltx package diff --git a/src/Session.cpp b/src/Session.cpp index 310aa74c9b..ec3d56a1df 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -35,6 +35,7 @@ string const sec_session = "[session info]"; string const sec_toolbars = "[toolbars]"; string const sec_lastcommands = "[last commands]"; string const sec_authfiles = "[auth files]"; +string const sec_shellescape = "[shell escape files]"; } // anon namespace @@ -422,6 +423,8 @@ void Session::readFile() lastCommands().read(is); else if (tmp == sec_authfiles) authFiles().read(is); + else if (tmp == sec_shellescape) + shellescapeFiles().read(is); else LYXERR(Debug::INIT, "LyX: Warning: unknown Session section: " << tmp); @@ -442,6 +445,7 @@ void Session::writeFile() const lastCommands().write(os); bookmarks().write(os); authFiles().write(os); + shellescapeFiles().write(os); } else LYXERR(Debug::INIT, "LyX: Warning: unable to save Session: " << session_file); @@ -480,4 +484,36 @@ void AuthFilesSection::write(ostream & os) const } +ShellEscapeSection::ShellEscapeSection() { } + + +void ShellEscapeSection::read(istream & is) +{ + string tmp; + do { + char c = is.peek(); + if (c == '[') + break; + getline(is, tmp); + if (tmp.empty() || tmp[0] == '#' || tmp[0] == ' ' || !FileName::isAbsolute(tmp)) + continue; + + // read lastfiles + FileName const file(tmp); + if (file.exists() && !file.isDirectory()) + shellescape_files_.insert(tmp); + else + LYXERR(Debug::INIT, "LyX: Warning: Ignore shellescape file: " << tmp); + } while (is.good()); +} + + +void ShellEscapeSection::write(ostream & os) const +{ + os << '\n' << sec_shellescape << '\n'; + copy(shellescape_files_.begin(), shellescape_files_.end(), + ostream_iterator<std::string>(os, "\n")); +} + + } diff --git a/src/Session.h b/src/Session.h index f471f4d28e..f404a4e577 100644 --- a/src/Session.h +++ b/src/Session.h @@ -341,6 +341,27 @@ private: }; +class ShellEscapeSection : SessionSection +{ +public: + /// + explicit ShellEscapeSection(); + + /// + void read(std::istream & is); + + /// + void write(std::ostream & os) const; + + /// + std::set<std::string> & shellescapeFiles() { return shellescape_files_; } + +private: + /// set of document files authorized for external conversion + std::set<std::string> shellescape_files_; +}; + + class Session { public: @@ -373,6 +394,10 @@ public: AuthFilesSection & authFiles() { return auth_files; } /// AuthFilesSection const & authFiles() const { return auth_files; } + /// + ShellEscapeSection & shellescapeFiles() { return shellescape_files; } + /// + ShellEscapeSection const & shellescapeFiles() const { return shellescape_files; } private: friend class LyX; @@ -402,6 +427,8 @@ private: LastCommandsSection last_commands; /// AuthFilesSection auth_files; + /// + ShellEscapeSection shellescape_files; }; /// This is a singleton class. Get the instance.