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 &not allow") : _("Do &not 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 &not 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 &not 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.

Reply via email to