« get me outta code hell

Let keybindings run shell commands, if --trust option is given - http-music - Command-line music player + utils (not a server!)
about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2017-11-09 00:21:45 -0400
committerFlorrie <towerofnix@gmail.com>2017-11-09 00:22:06 -0400
commit56c969110143652410c48785118ab2a2c1d3519e (patch)
tree514d2b3d50d72455713beffff661c9094a7e8db8
parent69383a7c1ff7240e664c6af97f84cc7e44050f42 (diff)
Let keybindings run shell commands, if --trust option is given
Keybinding action format:

  [[..combo..], 'runShellCommand', ..command name.., [..arguments..]]

(--trust is an alias for --trust-shell-commands. When --trust appears in
an "options" property of a playlist (or anywhere besides the `http-music
play` invocation on the command line, shell command permisisons are
*revoked* - they cannot be enabled, even by directly passing --trust to
the command line.)

Also adds a note to todo.txt.
-rw-r--r--src/loop-play.js1
-rwxr-xr-xsrc/play.js53
-rw-r--r--todo.txt3
3 files changed, 55 insertions, 2 deletions
diff --git a/src/loop-play.js b/src/loop-play.js
index 09b3cad..34ac4d5 100644
--- a/src/loop-play.js
+++ b/src/loop-play.js
@@ -11,7 +11,6 @@
 const { spawn } = require('child_process')
 const FIFO = require('fifo-js')
 const EventEmitter = require('events')
-const promisifyProcess = require('./promisify-process')
 const killProcess = require('./kill-process')
 const { HistoryController, generalPicker } = require('./pickers')
 
diff --git a/src/play.js b/src/play.js
index ac6a232..db2462c 100755
--- a/src/play.js
+++ b/src/play.js
@@ -3,12 +3,14 @@
 'use strict'
 
 const { promisify } = require('util')
+const { spawn } = require('child_process')
 const clone = require('clone')
 const fs = require('fs')
 const fetch = require('node-fetch')
 const commandExists = require('./command-exists')
 const startLoopPlay = require('./loop-play')
 const processArgv = require('./process-argv')
+const promisifyProcess = require('./promisify-process')
 const { compileKeybindings } = require('./keybinder')
 const processSmartPlaylist = require('./smart-playlist')
 
@@ -88,6 +90,15 @@ async function main(args) {
 
   let disablePlaybackStatus = false
 
+  // Trust shell commands - permits keybindings to activate console commands.
+  let trustShellCommands = false
+
+  // Whether or not "trust shell commands" *may* be set to true. Set to false
+  // when shell command permissions are revoked (to prevent them from being
+  // granted in the future). Basic protection against dumb attempts at Evil
+  // keybinding files.
+  let mayTrustShellCommands = true
+
   const keybindings = [
     [['space'], 'togglePause'],
     [['left'], 'seek', -5],
@@ -536,7 +547,31 @@ async function main(args) {
       disablePlaybackStatus = true
     },
 
-    '-hide-playback-status': util => util.alias('-disable-playback-status')
+    '-hide-playback-status': util => util.alias('-disable-playback-status'),
+
+    '-trust-shell-commands': function(util) {
+      // --trust-shell-commands  (alias: --trust)
+      // Lets keybindings run shell commands. Only use this when loading
+      // keybindings from a trusted source. Defaults to false (no shell
+      // permissions).
+
+      // We don't want an imported playlist to enable this! - Only arguments
+      // directly passed to http-music from the command line.
+      if (util.argv !== args) {
+        console.warn(
+          "--trust-shell-commands must be passed directly to http-music " +
+          "from the command line! (Revoking shell command permissions.)"
+        )
+
+        trustShellCommands = false
+        mayTrustShellCommands = false
+      } else {
+        console.log("Trusting shell commands.")
+        trustShellCommands = true
+      }
+    },
+
+    '-trust': util => util.alias('-trust-shell-commands')
   }
 
   await openPlaylist('./playlist.json', true)
@@ -660,6 +695,22 @@ async function main(args) {
       'showTrackInfo': function() {
         clearConsoleLine()
         playController.logTrackInfo()
+      },
+
+      'runShellCommand': async function(command, args) {
+        if (trustShellCommands) {
+          console.log(
+            'From keybinding, running shell command:',
+            `${command} ${args.join(' ')}`
+          )
+          await promisifyProcess(spawn(command, args))
+        } else {
+          console.warn(
+            'From keybinding, shell command requested but not executed',
+            '(no --trust):',
+            `${command} ${args.join(' ')}`
+          )
+        }
       }
     }
 
diff --git a/todo.txt b/todo.txt
index 66effde..7c2a765 100644
--- a/todo.txt
+++ b/todo.txt
@@ -404,3 +404,6 @@ TODO: A way to search the playlist for a path. Probably best to modify the
 
 TODO: Case-insensitive checking with command keybindings - I think this is
       broken with the new command system.
+
+TODO: Handle empty (active) playlists. Showing an error message and stopping
+      is best, I think.