Commandline insertion plugin

Very simple plugin that executes a cmdline command and inserts the output in your page. Make sure the command you use does not prompt for userinput otherwise wikidpad will keep waiting for it and you have to kill the wikidpad process.

Update 2008-12-02

I have added a simple security mechanism. The default setting for this plugin is that a cmdline tag needs a security token, which has to be set first in the options. Otherwise the command will not be executed. Keep your security token to yourself and keep the security on. This security mechanism offers some protection when you paste in foreign content into your wiki but it still allows you to do really stupid things. The security mechanism can be turned off but this is not recommended. If you can not be trusted with the power of shell commands then using this plugin is not recommended.

WARNING

Before installing this plugin make sure you understand what it does. If you install this plugin and the string [:cmdline: "SOME DESTRUCTIVE COMMAND"] is present in your page and you turned of the security token or added the security token to your cmdline tag, then upon previewing your page the "SOME DESTRUCTIVE COMMAND" will be send to your operating system shell and executed WITHOUT WARNING. If you decide to install this plugin, make sure you understand the risk. Copying content in your page or opening somebody elses wiki and previewing it can be potentially dangerous when this plugin is installed and the security mechanism is turned off. Use it at your own risk.

See also the forum thread about this: http://tech.groups.yahoo.com/group/wikidPad/message/4666

Examples

For example, adding the insertion command below in your page will result in the directory listing being inserted in your page.

[:cmdline: "dir /b"]

Example with an example security token which must have been set in the options panel for this plugin:

Note: this is just an example token. You have to make up your own token, register it in the options and add it to every cmdline insert tag

[:cmdline: "tail path/to/file.log";"t0K3n"]

Sourcecode

Save the code below to a file called CmdLineIns.py in your user_extensions folder in the wikidpad program folder.

"""
2008-12-02: added simple security mechanism

2008-11-30: created by bousch_AT_gmail.com
Do with it what you want, no warranty whatsoever.
"""
import os
import wx

WIKIDPAD_PLUGIN = (("InsertionByKey", 1), ("Options", 1), )

def describeInsertionKeys(ver, app):
    """
    API function for "InsertionByKey" plugins
    Returns a sequence of tuples describing the supported
    insertion keys. Each tuple has the form (insKey, exportTypes, handlerFactory)
    where insKey is the insertion key handled, exportTypes is a sequence of
    strings describing the supported export types and handlerFactory is
    a factory function (normally a class) taking the wxApp object as
    parameter and returning a handler object fulfilling the protocol
    for "insertion by key" (see EqnHandler as example).

    ver -- API version (can only be 1 currently)
    app -- wxApp object
    """
    return (
            (u"cmdline", ("wikidpad_language",), CmdLineHandler),
            )


class CmdLineHandler:
    """
    Base class fulfilling the "insertion by key" protocol.
    """
    def __init__(self, app):
        self.app = app
        self.securityToken = None
        self.useSecurityToken = None
        
    def taskStart(self, exporter, exportType):
        """
        This is called before any call to createContent() during an
        export task.
        An export task can be a single HTML page for
        preview or a single page or a set of pages for export.
        exporter -- Exporter object calling the handler
        exportType -- string describing the export type
        
        Calls to createContent() will only happen after a 
        call to taskStart() and before the call to taskEnd()
        """
        self.securityToken = self.app.getGlobalConfig().get("main",
                "plugin_cmdLine_security_token", u"")
        
        self.useSecurityToken = str(self.app.getGlobalConfig().get("main",
                "plugin_cmdLine_use_security_token", u"")) == "True"


    def taskEnd(self):
        """
        Called after export task ended and after the last call to
        createContent().
        """
        pass


    def createContent(self, exporter, exportType, insToken):
        """
        Handle an insertion and create the appropriate content.

        exporter -- Exporter object calling the handler
        exportType -- string describing the export type
        insToken -- insertion token to create content for (see also 
                PageAst.Insertion)

        An insertion token has the following member variables:
            key: insertion key (unistring)
            value: value of an insertion (unistring)
            appendices: sequence of strings with the appendices

        Meaning and type of return value is solely defined by the type
        of the calling exporter.
        
        For HtmlXmlExporter a unistring is returned with the HTML code
        to insert instead of the insertion.        
        """
        if not insToken.value:
            # Nothing in, nothing out
            return u""

        cmdline = insToken.value

        # check for security
        if self.useSecurityToken:
            # security token must be set in options
            if not self.securityToken:
                return "<<pre\ncmdline: security token not set in options\n>>"
            # security token must be present in appendices
            if self.securityToken not in insToken.appendices:
                return "<<pre\ncmdline: security token missing or does not match\n>>"

        try:
            # Run external application
            childIn, childOut, childErr = os.popen3(cmdline, "b")
            
            if u"noerror" in [a.strip() for a in insToken.appendices]:
                childErr.read()
                errResponse = ""
            else:
                errResponse = childErr.read()
        except:
            pass

        result = "++++ Output from:\n<<pre\n%s\n>>\n" % cmdline
        result += childOut.read()
        result += "*End output*\n----\n"

        return result
        
        
    def getExtraFeatures(self):
        """
        Returns a list of bytestrings describing additional features supported
        by the plugin. Currently not specified further.
        """
        return ()
        


def registerOptions(ver, app):
    """
    API function for "Options" plugins
    Register configuration options and their GUI presentation
    ver -- API version (can only be 1 currently)
    app -- wxApp object
    """
    # Register options
    app.getDefaultGlobalConfigDict()[("main", "plugin_cmdLine_security_token")] \
            = u""
    app.getDefaultGlobalConfigDict()[("main",
            "plugin_cmdLine_use_security_token")] = u"True"

    # Register panel in options dialog
    app.addOptionsDlgPanel(CmdLineOptionsPanel, u"  CmdLine")


class CmdLineOptionsPanel(wx.Panel):
    def __init__(self, parent, optionsDlg, app):
        """
        Called when "Options" dialog is opened to show the panel.
        Transfer here all options from the configuration file into the
        text fields, check boxes, ...
        """
        wx.Panel.__init__(self, parent)
        self.app = app
        
        pt = self.app.getGlobalConfig().get("main",
                "plugin_cmdLine_security_token", u"")
        self.securityTokenField = wx.TextCtrl(self, -1, pt)
        self.securityTokenField.SetToolTip(wx.ToolTip("Must be 6 characters or more"))

        mainsizer = wx.FlexGridSizer(6, 2, 0, 0)
        mainsizer.AddGrowableCol(1, 1)

        mainsizer.Add(wx.StaticText(self, -1, _(u"")), 0,
                wx.ALL | wx.EXPAND, 5)

        self.useSecurityTokenCheckBox = wx.CheckBox(self, -1,
                'Use security token (Recommended)')
        self.useSecurityTokenCheckBox.SetToolTip(wx.ToolTip(
            " When checked the security token below must be added to the cmdline\ninsertion tag. Otherwise the command will not be executed."))
        pt = self.app.getGlobalConfig().get("main",
                "plugin_cmdLine_use_security_token", u"")

        #wx.SafeShowMessage("pt", pt)
        pt = (str(pt) == "True")
        self.useSecurityTokenCheckBox.SetValue(pt)
        mainsizer.Add(self.useSecurityTokenCheckBox, 0, wx.ALL | wx.EXPAND, 5)
        self.useSecurityTokenCheckBox.Bind(wx.EVT_CHECKBOX, self.clickedCB)

        mainsizer.Add(wx.StaticText(self, -1, _(u"Security token:")), 0,
                wx.ALL | wx.EXPAND, 5)
        mainsizer.Add(self.securityTokenField, 1, wx.ALL | wx.EXPAND, 5)


        self.SetSizer(mainsizer)
        self.Fit()

    def clickedCB(self, event):
        if not self.useSecurityTokenCheckBox.GetValue():
            wx.SafeShowMessage( "Security alert",
                    """
                    Turning of security can be dangerous!\n\n
                    All cmdline insertion tags will be executed when switching
                    to preview mode. Beware when copying foreign content to
                    your wiki or running a foreign wiki on your pc.
                    """)

    def setVisible(self, vis):
        """
        Called when panel is shown or hidden. The actual wxWindow.Show()
        function is called automatically.
        
        If a panel is visible and becomes invisible because another panel is
        selected, the plugin can veto by returning False.
        When becoming visible, the return value is ignored.
        """
        return True

    def checkOk(self):
        """
        Called when "OK" is pressed in dialog. The plugin should check here if
        all input values are valid. If not, it should return False, then the
        Options dialog automatically shows this panel.
        
        There should be a visual indication about what is wrong (e.g. red
        background in text field). Be sure to reset the visual indication
        if field is valid again.
        """
        if self.useSecurityTokenCheckBox.GetValue():
            token = self.securityTokenField.GetValue()
            if len(token) < 6:
                wx.SafeShowMessage("Security alert", "Security token must be set and longer then 6 characters") 
                return False

        return True

    def handleOk(self):
        """
        This is called if checkOk() returned True for all panels. Transfer here
        all values from text fields, checkboxes, ... into the configuration
        file.
        """
        pt = self.securityTokenField.GetValue()
        self.app.getGlobalConfig().set("main", "plugin_cmdLine_security_token", pt)

        pt = self.useSecurityTokenCheckBox.GetValue()
        self.app.getGlobalConfig().set("main",
                "plugin_cmdLine_use_security_token", str(pt))