2018-04-17 18:28:11 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2018-05-24 14:30:53 +10:00
|
|
|
import json
|
2018-04-17 18:28:11 +01:00
|
|
|
import os
|
2018-05-24 14:30:53 +10:00
|
|
|
import pathlib
|
2018-05-11 10:20:22 -04:00
|
|
|
import re
|
2018-05-23 18:07:32 +10:00
|
|
|
import shutil
|
2018-04-17 18:28:11 +01:00
|
|
|
import struct
|
2018-05-24 12:56:06 +10:00
|
|
|
import subprocess
|
2018-05-24 14:30:53 +10:00
|
|
|
import sys
|
|
|
|
import tempfile
|
2018-05-28 00:32:30 +10:00
|
|
|
import time
|
2018-05-11 10:20:22 -04:00
|
|
|
import unicodedata
|
2018-04-17 18:28:11 +01:00
|
|
|
|
2018-05-28 00:32:30 +10:00
|
|
|
DEBUG = False
|
2018-07-30 17:56:05 +01:00
|
|
|
VERSION = "0.1.8"
|
2018-04-17 18:28:11 +01:00
|
|
|
|
2018-05-11 10:20:22 -04:00
|
|
|
|
2018-04-17 18:28:11 +01:00
|
|
|
class NoConnectionError(Exception):
|
|
|
|
""" Exception thrown when stdin cannot be read """
|
|
|
|
|
2018-05-24 12:56:06 +10:00
|
|
|
|
2018-05-23 18:07:32 +10:00
|
|
|
def is_command_on_path(command):
|
|
|
|
""" Returns 'True' if the if the specified command is found on
|
|
|
|
user's $PATH.
|
|
|
|
"""
|
|
|
|
if shutil.which(command):
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
2018-04-17 18:28:11 +01:00
|
|
|
|
2018-05-24 12:56:06 +10:00
|
|
|
|
2018-04-17 18:28:11 +01:00
|
|
|
def eprint(*args, **kwargs):
|
2018-05-24 12:56:06 +10:00
|
|
|
""" Print to stderr, which gets echoed in the browser console
|
|
|
|
when run by Firefox
|
2018-04-17 18:28:11 +01:00
|
|
|
"""
|
|
|
|
print(*args, file=sys.stderr, flush=True, **kwargs)
|
|
|
|
|
|
|
|
|
2018-05-09 22:09:10 +02:00
|
|
|
def getenv(variable, default):
|
2018-04-17 18:28:11 +01:00
|
|
|
""" Get an environment variable value, or use the default provided """
|
|
|
|
return os.environ.get(variable) or default
|
|
|
|
|
2018-05-11 10:20:22 -04:00
|
|
|
|
2018-04-17 18:28:11 +01:00
|
|
|
def getMessage():
|
2018-04-18 21:49:33 +01:00
|
|
|
"""Read a message from stdin and decode it.
|
|
|
|
|
|
|
|
"Each message is serialized using JSON, UTF-8 encoded and is preceded with
|
|
|
|
a 32-bit value containing the message length in native byte order."
|
|
|
|
|
|
|
|
https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Native_messaging#App_side
|
|
|
|
|
|
|
|
"""
|
2018-04-17 18:28:11 +01:00
|
|
|
rawLength = sys.stdin.buffer.read(4)
|
|
|
|
if len(rawLength) == 0:
|
|
|
|
sys.exit(0)
|
2018-05-28 09:04:01 +10:00
|
|
|
messageLength = struct.unpack("@I", rawLength)[0]
|
|
|
|
message = sys.stdin.buffer.read(messageLength).decode("utf-8")
|
2018-04-17 18:28:11 +01:00
|
|
|
return json.loads(message)
|
|
|
|
|
|
|
|
|
|
|
|
# Encode a message for transmission,
|
|
|
|
# given its content.
|
|
|
|
def encodeMessage(messageContent):
|
|
|
|
""" Encode a message for transmission, given its content."""
|
2018-05-28 09:04:01 +10:00
|
|
|
encodedContent = json.dumps(messageContent).encode("utf-8")
|
|
|
|
encodedLength = struct.pack("@I", len(encodedContent))
|
|
|
|
return {"length": encodedLength, "content": encodedContent}
|
2018-04-17 18:28:11 +01:00
|
|
|
|
|
|
|
|
|
|
|
# Send an encoded message to stdout
|
|
|
|
def sendMessage(encodedMessage):
|
|
|
|
""" Send an encoded message to stdout."""
|
2018-05-28 09:04:01 +10:00
|
|
|
sys.stdout.buffer.write(encodedMessage["length"])
|
|
|
|
sys.stdout.buffer.write(encodedMessage["content"])
|
2018-04-26 12:33:01 +01:00
|
|
|
try:
|
2018-05-28 09:04:01 +10:00
|
|
|
sys.stdout.buffer.write(encodedMessage["code"])
|
2018-04-26 12:33:01 +01:00
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
2018-04-17 18:28:11 +01:00
|
|
|
sys.stdout.buffer.flush()
|
|
|
|
|
|
|
|
|
2018-05-10 21:22:14 +01:00
|
|
|
def findUserConfigFile():
|
|
|
|
""" Find a user config file, if it exists. Return the file path, or None
|
|
|
|
if not found
|
|
|
|
"""
|
2018-05-28 09:04:01 +10:00
|
|
|
home = os.path.expanduser("~")
|
|
|
|
config_dir = getenv(
|
|
|
|
"XDG_CONFIG_HOME", os.path.expanduser("~/.config")
|
|
|
|
)
|
2018-05-10 21:22:14 +01:00
|
|
|
|
|
|
|
# Will search for files in this order
|
|
|
|
candidate_files = [
|
|
|
|
os.path.join(config_dir, "tridactyl", "tridactylrc"),
|
2018-05-28 09:04:01 +10:00
|
|
|
os.path.join(home, ".tridactylrc"),
|
2018-07-30 17:57:08 +01:00
|
|
|
os.path.join(home, , "_config", "tridactyl", "tridactylrc"),
|
2018-07-30 17:56:05 +01:00
|
|
|
os.path.join(home, "_tridactylrc"),
|
2018-05-10 21:22:14 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
config_path = None
|
|
|
|
|
|
|
|
# find the first path in the list that exists
|
|
|
|
for path in candidate_files:
|
|
|
|
if os.path.isfile(path):
|
|
|
|
config_path = path
|
|
|
|
break
|
|
|
|
|
|
|
|
return config_path
|
|
|
|
|
|
|
|
|
|
|
|
def getUserConfig():
|
|
|
|
# look it up freshly each time - the user could have moved or killed it
|
|
|
|
cfg_file = findUserConfigFile()
|
|
|
|
|
|
|
|
# no file, return
|
|
|
|
if not cfg_file:
|
|
|
|
return None
|
|
|
|
|
|
|
|
# for now, this is a simple file read, but if the files can
|
|
|
|
# include other files, that will need more work
|
2018-05-28 09:04:01 +10:00
|
|
|
return open(cfg_file, "r").read()
|
2018-05-10 21:22:14 +01:00
|
|
|
|
|
|
|
|
2018-05-11 10:20:22 -04:00
|
|
|
def sanitizeFilename(fn):
|
|
|
|
""" Transform a string to make it suitable for use as a filename.
|
|
|
|
|
|
|
|
From https://stackoverflow.com/a/295466/147356"""
|
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
fn = (
|
|
|
|
unicodedata.normalize("NFKD", fn)
|
|
|
|
.encode("ascii", "ignore")
|
|
|
|
.decode("ascii")
|
|
|
|
)
|
|
|
|
fn = re.sub("[^\w\s/.-]", "", fn).strip().lower()
|
|
|
|
fn = re.sub("\.\.+", "", fn)
|
|
|
|
fn = re.sub("[-/\s]+", "-", fn)
|
2018-05-11 10:20:22 -04:00
|
|
|
return fn
|
|
|
|
|
2018-05-24 14:30:53 +10:00
|
|
|
|
2018-05-24 12:56:06 +10:00
|
|
|
def is_valid_firefox_profile(profile_dir):
|
|
|
|
is_valid = False
|
|
|
|
validity_indicator = "times.json"
|
|
|
|
|
|
|
|
if pathlib.WindowsPath(profile_dir).is_dir():
|
|
|
|
test_path = "%s\\%s" % (profile_dir, validity_indicator)
|
|
|
|
|
|
|
|
if pathlib.WindowsPath(test_path).is_file():
|
|
|
|
is_valid = True
|
|
|
|
|
|
|
|
return is_valid
|
|
|
|
|
|
|
|
|
|
|
|
def win_firefox_restart(message):
|
|
|
|
"""Handle 'win_firefox_restart' message."""
|
|
|
|
reply = {}
|
2018-05-28 00:32:30 +10:00
|
|
|
profile_dir = None
|
|
|
|
browser_cmd = None
|
2018-05-24 12:56:06 +10:00
|
|
|
|
2018-05-28 00:32:30 +10:00
|
|
|
try:
|
|
|
|
profile_dir = message["profiledir"].strip()
|
|
|
|
browser_cmd = message["browsercmd"].strip()
|
|
|
|
except KeyError:
|
|
|
|
reply = {
|
|
|
|
"code": -1,
|
|
|
|
"cmd": "error",
|
|
|
|
"error": "Error parsing 'restart' message.",
|
|
|
|
}
|
|
|
|
return reply
|
2018-05-24 12:56:06 +10:00
|
|
|
|
2018-05-28 00:32:30 +10:00
|
|
|
if (
|
|
|
|
profile_dir
|
|
|
|
and profile_dir != "auto"
|
|
|
|
and not is_valid_firefox_profile(profile_dir)
|
|
|
|
):
|
2018-05-24 12:56:06 +10:00
|
|
|
reply = {
|
2018-05-28 00:32:30 +10:00
|
|
|
"code": -1,
|
2018-05-24 12:56:06 +10:00
|
|
|
"cmd": "error",
|
2018-05-28 00:32:30 +10:00
|
|
|
"error": "%s %s %s"
|
|
|
|
% (
|
2018-05-24 12:56:06 +10:00
|
|
|
"Invalid profile directory specified.",
|
|
|
|
"Vaild profile directory path(s) can be found by",
|
2018-05-28 00:32:30 +10:00
|
|
|
"navigating to 'about:support'.",
|
|
|
|
),
|
2018-05-24 12:56:06 +10:00
|
|
|
}
|
|
|
|
|
2018-05-28 00:32:30 +10:00
|
|
|
elif browser_cmd and not is_command_on_path(browser_cmd):
|
2018-05-24 12:56:06 +10:00
|
|
|
reply = {
|
2018-05-28 00:32:30 +10:00
|
|
|
"code": -1,
|
2018-05-24 12:56:06 +10:00
|
|
|
"cmd": "error",
|
2018-05-28 00:32:30 +10:00
|
|
|
"error": "%s %s %s"
|
|
|
|
% (
|
|
|
|
"'{0}' wasn't found on %PATH%.".format(browser_cmd),
|
|
|
|
"Please set valid browser by",
|
|
|
|
"'set browser [browser-command]'.",
|
|
|
|
),
|
2018-05-24 12:56:06 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
else:
|
|
|
|
# {{{
|
|
|
|
# Native messenger can't seem to create detached process on
|
|
|
|
# Windows while Firefox is quitting, which is essential to
|
|
|
|
# trigger restarting Firefox. So, below we are resorting to
|
|
|
|
# create a scheduled task with the task start-time set in
|
|
|
|
# the near future.
|
|
|
|
#
|
|
|
|
|
|
|
|
#
|
2018-05-24 14:30:53 +10:00
|
|
|
# subprocess.Popen(
|
2018-05-24 12:56:06 +10:00
|
|
|
# [ff_bin_path, "-profile", profile_dir],
|
|
|
|
# shell=False,
|
|
|
|
# creationflags=0x208 \
|
|
|
|
# | subprocess.CREATE_NEW_PROCESS_GROUP)
|
|
|
|
#
|
|
|
|
|
|
|
|
#
|
|
|
|
# 'schtasks.exe' is limited as in it doesn't support
|
|
|
|
# task-time with granularity in seconds. So, falling back
|
|
|
|
# to PowerShell as the last resort.
|
|
|
|
#
|
|
|
|
|
|
|
|
# out_str = ""
|
|
|
|
# task_time = time.strftime("%H:%M",
|
|
|
|
# time.localtime(
|
|
|
|
# time.time() + 60))
|
|
|
|
#
|
|
|
|
# out_str = subprocess.check_output(
|
|
|
|
# ["schtasks.exe",
|
|
|
|
# "/Create",
|
|
|
|
# "/F",
|
|
|
|
# "/SC",
|
|
|
|
# "ONCE",
|
|
|
|
# "/TN",
|
|
|
|
# "tridactyl",
|
|
|
|
# "/TR",
|
|
|
|
# "calc",
|
|
|
|
# "/IT",
|
|
|
|
# "/ST",
|
|
|
|
# task_time],
|
|
|
|
# shell=True)
|
|
|
|
# }}}
|
|
|
|
|
2018-05-28 00:32:30 +10:00
|
|
|
ff_lock_name = "parent.lock"
|
|
|
|
|
|
|
|
ff_bin_name = browser_cmd
|
|
|
|
ff_bin_path = '"%s"' % shutil.which(ff_bin_name)
|
|
|
|
|
|
|
|
ff_bin_dir = '"%s"' % str(
|
|
|
|
pathlib.WindowsPath(shutil.which(ff_bin_name)).parent
|
|
|
|
)
|
2018-05-24 12:56:06 +10:00
|
|
|
|
|
|
|
if profile_dir == "auto":
|
|
|
|
ff_lock_path = ff_bin_path
|
2018-05-28 00:32:30 +10:00
|
|
|
ff_args = '"%s"' % ("-foreground")
|
2018-05-24 12:56:06 +10:00
|
|
|
else:
|
2018-05-28 00:32:30 +10:00
|
|
|
ff_lock_path = '"%s/%s"' % (profile_dir, ff_lock_name)
|
|
|
|
ff_args = '"%s","%s","%s"' % (
|
|
|
|
"-foreground",
|
|
|
|
"-profile",
|
|
|
|
profile_dir,
|
|
|
|
)
|
2018-05-24 12:56:06 +10:00
|
|
|
|
|
|
|
try:
|
2018-05-28 00:32:30 +10:00
|
|
|
restart_ps1_content = """
|
|
|
|
$env:PATH=$env:PATH;{ff_bin_dir}
|
|
|
|
Set-Location -Path {ff_bin_dir}
|
|
|
|
$profileDir = "{profile_dir}"
|
|
|
|
if ($profileDir -ne "auto") {{
|
|
|
|
$lockFilePath = {ff_lock_path}
|
2018-05-24 12:56:06 +10:00
|
|
|
$locked = $true
|
|
|
|
$num_try = 10
|
2018-05-28 00:32:30 +10:00
|
|
|
}} else {{
|
2018-05-24 12:56:06 +10:00
|
|
|
$locked = $false
|
2018-05-28 00:32:30 +10:00
|
|
|
}}
|
|
|
|
while (($locked -eq $true) -and ($num_try -gt 0)) {{
|
|
|
|
try {{
|
2018-05-24 12:56:06 +10:00
|
|
|
[IO.File]::OpenWrite($lockFilePath).close()
|
2018-05-28 00:32:30 +10:00
|
|
|
$locked=$false
|
|
|
|
}} catch {{
|
2018-05-24 12:56:06 +10:00
|
|
|
$num_try-=1
|
|
|
|
Write-Host "[+] Trial: $num_try [lock == true]"
|
|
|
|
Start-Sleep -Seconds 1
|
2018-05-28 00:32:30 +10:00
|
|
|
}}
|
|
|
|
}}
|
|
|
|
if ($locked -eq $true) {{
|
2018-05-26 10:17:54 +10:00
|
|
|
$errorMsg = "Restarting Firefox failed. Please restart manually."
|
|
|
|
Write-Host "$errorMsg"
|
|
|
|
# Add-Type -AssemblyName System.Windows.Forms
|
|
|
|
# [System.Windows.MessageBox]::Show(
|
|
|
|
# $errorMsg,
|
|
|
|
# "Tridactyl")
|
2018-05-28 00:32:30 +10:00
|
|
|
}} else {{
|
2018-05-24 12:56:06 +10:00
|
|
|
Write-Host "[+] Restarting Firefox ..."
|
2018-05-28 00:32:30 +10:00
|
|
|
Start-Process `
|
|
|
|
-WorkingDirectory {ff_bin_dir} `
|
|
|
|
-FilePath {ff_bin_path} `
|
|
|
|
-ArgumentList {ff_args} `
|
|
|
|
-WindowStyle Normal
|
|
|
|
}}
|
|
|
|
""".format(
|
|
|
|
ff_bin_dir=ff_bin_dir,
|
|
|
|
profile_dir=profile_dir,
|
|
|
|
ff_lock_path=ff_lock_path,
|
|
|
|
ff_bin_path=ff_bin_path,
|
|
|
|
ff_args=ff_args,
|
|
|
|
)
|
2018-05-24 12:56:06 +10:00
|
|
|
|
2018-05-28 00:32:30 +10:00
|
|
|
delay_sec = 1.5
|
2018-05-24 12:56:06 +10:00
|
|
|
task_name = "firefox-restart"
|
|
|
|
native_messenger_dirname = ".tridactyl"
|
|
|
|
|
2018-05-26 10:17:54 +10:00
|
|
|
powershell_cmd = "powershell"
|
2018-05-28 00:32:30 +10:00
|
|
|
powershell_args = "%s %s" % (
|
|
|
|
"-NoProfile",
|
|
|
|
"-ExecutionPolicy Bypass",
|
|
|
|
)
|
2018-05-26 10:17:54 +10:00
|
|
|
|
2018-05-28 00:32:30 +10:00
|
|
|
restart_ps1_path = "%s\\%s\\%s" % (
|
|
|
|
os.path.expanduser("~"),
|
|
|
|
native_messenger_dirname,
|
|
|
|
"win_firefox_restart.ps1",
|
|
|
|
)
|
2018-05-26 10:17:54 +10:00
|
|
|
|
|
|
|
task_cmd = "cmd"
|
2018-05-28 00:32:30 +10:00
|
|
|
task_arg = '/c "%s %s -File %s"' % (
|
|
|
|
powershell_cmd,
|
|
|
|
powershell_args,
|
|
|
|
restart_ps1_path,
|
|
|
|
)
|
2018-05-24 12:56:06 +10:00
|
|
|
|
|
|
|
open(restart_ps1_path, "w+").write(restart_ps1_content)
|
|
|
|
|
|
|
|
startupinfo = subprocess.STARTUPINFO()
|
|
|
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
|
|
|
|
|
|
subprocess.check_output(
|
2018-05-28 00:32:30 +10:00
|
|
|
[
|
|
|
|
"powershell",
|
|
|
|
"-NonInteractive",
|
|
|
|
"-NoProfile",
|
|
|
|
"-WindowStyle",
|
|
|
|
"Minimized",
|
|
|
|
"-InputFormat",
|
|
|
|
"None",
|
|
|
|
"-ExecutionPolicy",
|
|
|
|
"Bypass",
|
|
|
|
"-Command",
|
|
|
|
"Register-ScheduledTask \
|
2018-05-24 12:56:06 +10:00
|
|
|
-TaskName '%s' \
|
|
|
|
-Force \
|
|
|
|
-Action (New-ScheduledTaskAction \
|
|
|
|
-Execute '%s' \
|
|
|
|
-Argument '%s') \
|
|
|
|
-Trigger (New-ScheduledTaskTrigger \
|
|
|
|
-Once \
|
|
|
|
-At \
|
2018-05-28 00:32:30 +10:00
|
|
|
(Get-Date).AddSeconds(%d).ToString('HH:mm:ss'))"
|
|
|
|
% (task_name, task_cmd, task_arg, delay_sec),
|
|
|
|
],
|
|
|
|
shell=False,
|
|
|
|
startupinfo=startupinfo,
|
|
|
|
)
|
2018-05-24 12:56:06 +10:00
|
|
|
|
|
|
|
reply = {
|
|
|
|
"code": 0,
|
2018-05-28 00:32:30 +10:00
|
|
|
"content": "Restarting in %d seconds..."
|
|
|
|
% delay_sec,
|
2018-05-24 12:56:06 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
except subprocess.CalledProcessError:
|
|
|
|
reply = {
|
|
|
|
"code": -1,
|
|
|
|
"cmd": "error",
|
2018-05-28 00:32:30 +10:00
|
|
|
"error": "error creating restart task.",
|
2018-05-24 12:56:06 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
return reply
|
|
|
|
|
2018-05-11 10:20:22 -04:00
|
|
|
|
2018-05-28 00:32:30 +10:00
|
|
|
def write_log(msg):
|
|
|
|
debug_log_dirname = ".tridactyl"
|
|
|
|
debug_log_filename = "native_main.log"
|
|
|
|
|
2018-07-18 01:03:12 -04:00
|
|
|
debug_log_path = os.path.join(
|
2018-05-28 00:32:30 +10:00
|
|
|
os.path.expanduser("~"),
|
|
|
|
debug_log_dirname,
|
|
|
|
debug_log_filename,
|
|
|
|
)
|
|
|
|
|
|
|
|
open(debug_log_path, "a+").write(msg)
|
|
|
|
|
|
|
|
|
2018-04-17 18:28:11 +01:00
|
|
|
def handleMessage(message):
|
|
|
|
""" Generate reply from incoming message. """
|
|
|
|
cmd = message["cmd"]
|
2018-05-28 00:32:30 +10:00
|
|
|
reply = {"cmd": cmd}
|
|
|
|
|
|
|
|
if DEBUG:
|
|
|
|
msg = "%s %s\n" % (
|
|
|
|
time.strftime("%H:%M:%S %p", time.localtime()),
|
|
|
|
str(message),
|
|
|
|
)
|
|
|
|
write_log(msg)
|
2018-04-17 18:28:11 +01:00
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
if cmd == "version":
|
|
|
|
reply = {"version": VERSION}
|
2018-04-17 18:28:11 +01:00
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
elif cmd == "getconfig":
|
2018-05-10 21:22:14 +01:00
|
|
|
file_content = getUserConfig()
|
|
|
|
if file_content:
|
2018-05-28 09:04:01 +10:00
|
|
|
reply["content"] = file_content
|
2018-05-10 21:22:14 +01:00
|
|
|
else:
|
2018-05-28 09:04:01 +10:00
|
|
|
reply["code"] = "File not found"
|
2018-05-10 21:22:14 +01:00
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
elif cmd == "run":
|
2018-04-26 12:33:01 +01:00
|
|
|
commands = message["command"]
|
native: feed stdin of subprocess for "run"
There's currently no way to tell the native messenger to
provide a particular stdin to a "run" command. This leads to
awkward uses of here-docs, which in turn require hacks like
the one in 4a5dcd75576cb218815bf75459b74de657f993ae.
Instead, let's take the "content" field from the message
body and feed that to the command's stdin, which gives us a
more robust channel for passing arbitrary bytes.
Note that we have to switch to using subprocess.Popen's
communicate() method, which will make sure we don't deadlock
if the input or output exceeds a pipe buffer.
Note also that when no stdin is provided by the caller
(i.e., all current cases), we'll pass an empty string. This
actually fixes a minor bug. Because we didn't override the
command's stdin argument, it's hooked to the messenger's
stdin, which is the pipe coming from firefox. If the command
tries to read, it (and the messenger) will hang forever,
since firefox is waiting for the messenger to respond before
writing anything else.
I bumped the native messenger version. This is mostly
backwards compatible (existing callers just don't send any
stdin content at all). But a caller that wants to send stdin
should check to make sure we have at least 0.1.7.
2018-07-18 01:08:35 -04:00
|
|
|
stdin = message.get("content", "").encode("utf-8")
|
2018-04-26 12:33:01 +01:00
|
|
|
|
native: feed stdin of subprocess for "run"
There's currently no way to tell the native messenger to
provide a particular stdin to a "run" command. This leads to
awkward uses of here-docs, which in turn require hacks like
the one in 4a5dcd75576cb218815bf75459b74de657f993ae.
Instead, let's take the "content" field from the message
body and feed that to the command's stdin, which gives us a
more robust channel for passing arbitrary bytes.
Note that we have to switch to using subprocess.Popen's
communicate() method, which will make sure we don't deadlock
if the input or output exceeds a pipe buffer.
Note also that when no stdin is provided by the caller
(i.e., all current cases), we'll pass an empty string. This
actually fixes a minor bug. Because we didn't override the
command's stdin argument, it's hooked to the messenger's
stdin, which is the pipe coming from firefox. If the command
tries to read, it (and the messenger) will hang forever,
since firefox is waiting for the messenger to respond before
writing anything else.
I bumped the native messenger version. This is mostly
backwards compatible (existing callers just don't send any
stdin content at all). But a caller that wants to send stdin
should check to make sure we have at least 0.1.7.
2018-07-18 01:08:35 -04:00
|
|
|
p = subprocess.Popen(commands, shell=True,
|
|
|
|
stdin=subprocess.PIPE,
|
|
|
|
stdout=subprocess.PIPE)
|
2018-04-26 12:33:01 +01:00
|
|
|
|
native: feed stdin of subprocess for "run"
There's currently no way to tell the native messenger to
provide a particular stdin to a "run" command. This leads to
awkward uses of here-docs, which in turn require hacks like
the one in 4a5dcd75576cb218815bf75459b74de657f993ae.
Instead, let's take the "content" field from the message
body and feed that to the command's stdin, which gives us a
more robust channel for passing arbitrary bytes.
Note that we have to switch to using subprocess.Popen's
communicate() method, which will make sure we don't deadlock
if the input or output exceeds a pipe buffer.
Note also that when no stdin is provided by the caller
(i.e., all current cases), we'll pass an empty string. This
actually fixes a minor bug. Because we didn't override the
command's stdin argument, it's hooked to the messenger's
stdin, which is the pipe coming from firefox. If the command
tries to read, it (and the messenger) will hang forever,
since firefox is waiting for the messenger to respond before
writing anything else.
I bumped the native messenger version. This is mostly
backwards compatible (existing callers just don't send any
stdin content at all). But a caller that wants to send stdin
should check to make sure we have at least 0.1.7.
2018-07-18 01:08:35 -04:00
|
|
|
reply["content"] = p.communicate(stdin)[0].decode("utf-8")
|
|
|
|
reply["code"] = p.returncode
|
2018-04-17 18:45:54 +01:00
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
elif cmd == "eval":
|
2018-04-18 21:49:33 +01:00
|
|
|
output = eval(message["command"])
|
2018-05-28 09:04:01 +10:00
|
|
|
reply["content"] = output
|
2018-04-18 21:49:33 +01:00
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
elif cmd == "read":
|
2018-04-28 00:08:39 +01:00
|
|
|
try:
|
2018-05-28 09:04:01 +10:00
|
|
|
with open(
|
|
|
|
os.path.expandvars(
|
|
|
|
os.path.expanduser(message["file"])
|
|
|
|
),
|
|
|
|
"r",
|
|
|
|
) as file:
|
|
|
|
reply["content"] = file.read()
|
|
|
|
reply["code"] = 0
|
2018-04-28 00:08:39 +01:00
|
|
|
except FileNotFoundError:
|
2018-05-28 09:04:01 +10:00
|
|
|
reply["content"] = ""
|
|
|
|
reply["code"] = 2
|
2018-04-28 00:08:39 +01:00
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
elif cmd == "mkdir":
|
2018-04-28 00:08:39 +01:00
|
|
|
os.makedirs(
|
2018-05-28 09:04:01 +10:00
|
|
|
os.path.relpath(message["dir"]),
|
|
|
|
exist_ok=message["exist_ok"],
|
2018-04-28 00:08:39 +01:00
|
|
|
)
|
2018-05-28 09:04:01 +10:00
|
|
|
reply["content"] = ""
|
|
|
|
reply["code"] = 0
|
2018-04-17 18:45:54 +01:00
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
elif cmd == "write":
|
2018-04-26 12:33:01 +01:00
|
|
|
with open(message["file"], "w") as file:
|
2018-04-17 18:45:54 +01:00
|
|
|
file.write(message["content"])
|
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
elif cmd == "temp":
|
|
|
|
prefix = message.get("prefix")
|
2018-05-17 17:53:33 +02:00
|
|
|
if prefix is None:
|
2018-05-28 09:04:01 +10:00
|
|
|
prefix = ""
|
|
|
|
prefix = "tmp_{}_".format(sanitizeFilename(prefix))
|
2018-05-11 10:20:22 -04:00
|
|
|
|
|
|
|
(handle, filepath) = tempfile.mkstemp(prefix=prefix)
|
2018-05-11 09:49:40 -04:00
|
|
|
with os.fdopen(handle, "w") as file:
|
2018-04-24 22:36:38 +01:00
|
|
|
file.write(message["content"])
|
2018-05-28 09:04:01 +10:00
|
|
|
reply["content"] = filepath
|
2018-04-24 22:36:38 +01:00
|
|
|
|
2018-05-28 09:04:01 +10:00
|
|
|
elif cmd == "env":
|
|
|
|
reply["content"] = getenv(message["var"], "")
|
2018-05-09 22:09:10 +02:00
|
|
|
|
2018-05-24 12:56:06 +10:00
|
|
|
elif cmd == "win_firefox_restart":
|
|
|
|
reply = win_firefox_restart(message)
|
2018-05-23 18:07:32 +10:00
|
|
|
|
2018-04-17 18:28:11 +01:00
|
|
|
else:
|
2018-05-28 09:04:01 +10:00
|
|
|
reply = {"cmd": "error", "error": "Unhandled message"}
|
|
|
|
eprint("Unhandled message: {}".format(message))
|
2018-04-17 18:28:11 +01:00
|
|
|
|
|
|
|
return reply
|
|
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
message = getMessage()
|
|
|
|
reply = handleMessage(message)
|
2018-04-17 18:45:54 +01:00
|
|
|
sendMessage(encodeMessage(reply))
|