On Tue, Jun 20, 2017 at 08:48:20PM +0200, Jürgen Spitzmüller wrote:

> Am Dienstag, den 20.06.2017, 20:29 +0200 schrieb Enrico Forestieri:
> > Ok, so you want to support shell-escape only for supported packages.
> > The next iteration of the patch attached here allows this only for
> > documents actually using minted. When all minted listings are removed
> > from the document, the permission of using shell-escape is revoked,
> > whatever the choice of the Syntax Highlighting Package.
> 
> Thanks. I didn't check the patch in detail, but I like the approach.

The previous patch was revoking permission even when no listing was
left in the document during an editing session. The attached one
performs instead the check only when the document is closed.

-- 
Enrico
diff --git a/src/Converter.cpp b/src/Converter.cpp
index 6e10b18704..45b725e8c6 100644
--- a/src/Converter.cpp
+++ b/src/Converter.cpp
@@ -19,14 +19,18 @@
 #include "Encoding.h"
 #include "ErrorList.h"
 #include "Format.h"
+#include "InsetList.h"
 #include "Language.h"
 #include "LaTeX.h"
 #include "LyXRC.h"
 #include "Mover.h"
+#include "ParagraphList.h"
 #include "Session.h"
 
 #include "frontends/alert.h"
 
+#include "insets/InsetInclude.h"
+
 #include "support/debug.h"
 #include "support/FileNameList.h"
 #include "support/filetools.h"
@@ -279,20 +283,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>This document uses minted listings, so the LaTeX "
+               "backend needs to be able to launch 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 +324,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 +495,30 @@ bool Converters::convert(Buffer const * buffer,
                                                   "tmpfile.out"));
                }
 
-               if (!checkAuth(conv, buffer ? buffer->absFileName() : string()))
+               bool use_shell_escape = false;
+               if (buffer && buffer->params().use_minted) {
+                       // Check for minted listings insets
+                       for (Paragraph const & par : buffer->paragraphs()) {
+                               InsetList const & insets = par.insetList();
+                               pos_type lstpos = insets.find(LISTINGS_CODE, 0);
+                               pos_type incpos = insets.find(INCLUDE_CODE, 0);
+                               if (incpos >= 0) {
+                                       InsetInclude const * include =
+                                               static_cast<InsetInclude *>
+                                                       (insets.get(incpos));
+                                       if (include->params().getCmdName() !=
+                                                               "inputminted") {
+                                               incpos = -1;
+                                       }
+                               }
+                               if (lstpos >= 0 || incpos >= 0) {
+                                       use_shell_escape = true;
+                                       break;
+                               }
+                       }
+               }
+               if (!checkAuth(conv, buffer ? buffer->absFileName() : string(),
+                              use_shell_escape))
                        return false;
 
                if (conv.latex()) {
@@ -470,6 +529,8 @@ bool Converters::convert(Buffer const * buffer,
                        command = subst(command, token_from, "");
                        command = subst(command, token_latex_encoding,
                                        
buffer->params().encoding().latexName());
+                       if (use_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/Session.cpp b/src/Session.cpp
index 310aa74c9b..347426a0fe 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,34 @@ void AuthFilesSection::write(ostream & os) const
 }
 
 
+void ShellEscapeSection::read(istream & is)
+{
+       string s;
+       do {
+               char c = is.peek();
+               if (c == '[')
+                       break;
+               getline(is, s);
+               c = s[0];
+               if (c == 0 || c == '#' || c == ' ' || !FileName::isAbsolute(s))
+                       continue;
+
+               // read shellescape files
+               FileName const file(s);
+               if (file.exists() && !file.isDirectory())
+                       shellescape_files_.insert(s);
+               else
+                       LYXERR(Debug::INIT, "LyX: Warning: Ignore shellescape 
file: " << s);
+       } 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..1698ba3537 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.
diff --git a/src/frontends/qt4/GuiView.cpp b/src/frontends/qt4/GuiView.cpp
index a2ef74bf53..18debc73b6 100644
--- a/src/frontends/qt4/GuiView.cpp
+++ b/src/frontends/qt4/GuiView.cpp
@@ -50,6 +50,7 @@
 #include "Format.h"
 #include "FuncStatus.h"
 #include "FuncRequest.h"
+#include "InsetList.h"
 #include "Intl.h"
 #include "Layout.h"
 #include "Lexer.h"
@@ -66,6 +67,8 @@
 #include "Toolbars.h"
 #include "version.h"
 
+#include "insets/InsetInclude.h"
+
 #include "support/convert.h"
 #include "support/debug.h"
 #include "support/ExceptionMessage.h"
@@ -2905,6 +2908,38 @@ bool GuiView::closeBuffer(Buffer & buf)
        // buffer, we can close or release the child buffers here too.
        bool success = true;
        if (!closing_) {
+               if (buf.params().use_minted && buf.isClean()) {
+                       std::set<std::string> & shellescape_files =
+                           theSession().shellescapeFiles().shellescapeFiles();
+                       std::set<std::string>::iterator it =
+                               shellescape_files.find(buf.absFileName());
+                       if (it != shellescape_files.end()) {
+                               // Check for minted listings insets
+                               bool found_minted_listings = false;
+                               for (Paragraph const & par : buf.paragraphs()) {
+                                       InsetList const & insets = 
par.insetList();
+                                       pos_type lstpos = 
insets.find(LISTINGS_CODE, 0);
+                                       pos_type incpos = 
insets.find(INCLUDE_CODE, 0);
+                                       if (incpos >= 0) {
+                                               InsetInclude const * include =
+                                                   static_cast<InsetInclude *>
+                                                       (insets.get(incpos));
+                                               if 
(include->params().getCmdName()
+                                                           != "inputminted") {
+                                                       incpos = -1;
+                                               }
+                                       }
+                                       if (lstpos >= 0 && incpos >= 0) {
+                                               found_minted_listings = true;
+                                               break;
+                                       }
+                               }
+                               // If the document doesn't use minted listings
+                               // revoke permission to use shell-escape
+                               if (!found_minted_listings)
+                                       shellescape_files.erase(it);
+                       }
+               }
                ListOfBuffers clist = buf.getChildren();
                ListOfBuffers::const_iterator it = clist.begin();
                ListOfBuffers::const_iterator const bend = clist.end();

Reply via email to