summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/link.py4
-rw-r--r--plugins/simple_notify.py17
-rw-r--r--src/core.py47
-rwxr-xr-xsrc/daemon.py21
4 files changed, 61 insertions, 28 deletions
diff --git a/plugins/link.py b/plugins/link.py
index 8d757be6..427d718a 100644
--- a/plugins/link.py
+++ b/plugins/link.py
@@ -7,7 +7,6 @@ from plugin import BasePlugin
from xhtml import clean_text
import common
import tabs
-import pipes
url_pattern = re.compile(r'\b(http[s]?://(?:\S+))\b', re.I|re.U)
@@ -42,8 +41,7 @@ class Plugin(BasePlugin):
nb = 1
link = self.find_link(nb)
if link:
- link = pipes.quote(link)
- self.core.exec_command("%s %s" % (self.config.get('browser', 'firefox'), link))
+ self.core.exec_command([self.config.get('browser', 'firefox'), link])
else:
self.core.information('No URL found.', 'Warning')
diff --git a/plugins/simple_notify.py b/plugins/simple_notify.py
index c2cbb198..d274e0ee 100644
--- a/plugins/simple_notify.py
+++ b/plugins/simple_notify.py
@@ -1,7 +1,7 @@
from plugin import BasePlugin
from xhtml import clean_text, get_body_from_message_stanza
from timed_events import DelayedEvent
-import pipes
+import shlex
class Plugin(BasePlugin):
def init(self):
@@ -25,14 +25,15 @@ class Plugin(BasePlugin):
body = clean_text(get_body_from_message_stanza(message))
if not body:
return
- command = self.config.get('command', '').strip()
- if not command:
+ command_str = self.config.get('command', '').strip()
+ if not command_str:
self.core.information('No notification command was provided in the configuration file', 'Warning')
return
- self.core.exec_command(command % {'body':pipes.quote(body), 'from':pipes.quote(fro)})
- after_command = self.config.get('after_command', '').strip()
- if not after_command:
+ command = [arg % {'body': body.replace('\n', ' '), 'from': fro} for arg in shlex.split(command_str)]
+ self.core.exec_command(command)
+ after_command_str = self.config.get('after_command', '').strip()
+ if not after_command_str:
return
- delayed_event = DelayedEvent(self.config.get('delay', 1), self.core.exec_command, after_command % {'body':pipes.quote(body), 'from':pipes.quote(fro)})
+ after_command = [arg % {'body': body.replace('\n', ' '), 'from': fro} for arg in shlex.split(after_command_str)]
+ delayed_event = DelayedEvent(self.config.get('delay', 1), self.core.exec_command, after_command)
self.core.add_timed_event(delayed_event)
-4
diff --git a/src/core.py b/src/core.py
index bd83c51f..9609ec44 100644
--- a/src/core.py
+++ b/src/core.py
@@ -11,6 +11,7 @@ import os
import sys
import time
import curses
+import pipes
import ssl
from functools import reduce
@@ -492,17 +493,32 @@ class Core(object):
def exec_command(self, command):
"""
- Execute an external command on the local or a remote
- machine, depending on the conf. For example, to open a link in a
- browser, do exec_command("firefox http://poezio.eu"),
- and this will call the command on the correct computer.
- The remote execution is done by writing the command on a fifo.
- That fifo has to be on the machine where poezio is running, and
- accessible (through sshfs for example) from the local machine (where
- poezio is not running). A very simple daemon reads on that fifo,
- and executes any command that is read in it.
- """
- command = '%s\n' % (command,)
+ Execute an external command on the local or a remote machine,
+ depending on the conf. For example, to open a link in a browser, do
+ exec_command(["firefox", "http://poezio.eu"]), and this will call
+ the command on the correct computer.
+
+ The command argument is a list of strings, not quoted or escaped in
+ any way. The escaping is done here if needed.
+
+ The remote execution is done
+ by writing the command on a fifo. That fifo has to be on the
+ machine where poezio is running, and accessible (through sshfs for
+ example) from the local machine (where poezio is not running). A
+ very simple daemon (daemon.py) reads on that fifo, and executes any
+ command that is read in it. Since we can only write strings to that
+ fifo, each argument has to be pipes.quote()d. That way the
+ shlex.split on the reading-side of the daemon will be safe.
+
+ You cannot use a real command line with pipes, redirections etc, but
+ this function supports a simple case redirection to file: if the
+ before-last argument of the command is ">" or ">>", then the last
+ argument is considered to be a filename where the command stdout
+ will be written. For example you can do exec_command(["echo",
+ "coucou les amis coucou coucou", ">", "output.txt"]) and this will
+ work. If you try to do anything else, your |, [, <<, etc will be
+ interpreted as normal command arguments, not shell special tokens.
+ """
if config.get('exec_remote', 'false') == 'true':
# We just write the command in the fifo
if not self.remote_fifo:
@@ -511,16 +527,17 @@ class Core(object):
except (OSError, IOError) as e:
self.information('Could not open fifo file for writing: %s' % (e,), 'Error')
return
+ command_str = ' '.join([pipes.quote(arg.replace('\n', ' ')) for arg in command]) + '\n'
try:
- self.remote_fifo.write(command)
+ self.remote_fifo.write(command_str)
except (IOError) as e:
- self.information('Could not execute [%s]: %s' % (command.strip(), e,), 'Error')
+ self.information('Could not execute %s: %s' % (command, e,), 'Error')
self.remote_fifo = None
else:
- e = Executor(command.strip())
+ e = Executor(command)
try:
e.start()
- except ValueError as e: # whenever shlex fails
+ except ValueError as e:
self.information('%s' % (e,), 'Error')
diff --git a/src/daemon.py b/src/daemon.py
index 5d8c9fab..08cf480f 100755
--- a/src/daemon.py
+++ b/src/daemon.py
@@ -38,17 +38,34 @@ class Executor(threading.Thread):
def __init__(self, command):
threading.Thread.__init__(self)
self.command = command
+ # check for > or >> special case
+ self.filename = None
+ self.redirection_mode = 'w'
+ if len(command) >= 3:
+ if command[-2] in ('>', '>>'):
+ self.filename = command.pop(-1)
+ if command[-1] == '>>':
+ self.redirection_mode = 'a'
+ command.pop(-1)
def run(self):
log.info('executing %s' % (self.command,))
- subprocess.call(['sh', '-c', self.command])
+ stdout = None
+ if self.filename:
+ try:
+ stdout = open(self.filename, self.redirection_mode)
+ except (OSError, IOError) as e:
+ log.error('Could not open redirection file: %s (%s)' % (self.filename, e,))
+ return
+ subprocess.call(self.command, stdout=stdout)
def main():
while True:
line = sys.stdin.readline()
if line == '':
break
- e = Executor(line)
+ command = shlex.split(line)
+ e = Executor(command)
e.start()
if __name__ == '__main__':