#!/usr/bin/env python3
"""
   Host application of the browser extension PassFF
   that wraps around the zx2c4 pass script.
"""

import json
import os
import re
import shlex
import struct
import subprocess
import sys

VERSION = "1.2.4"

###############################################################################
######################## Begin preferences section ############################
###############################################################################
COMMAND = "pass"
COMMAND_ARGS = []
COMMAND_ENV = {
   "TREE_CHARSET": "ISO-8859-1",
   "PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
}
CHARSET = "UTF-8"

###############################################################################
######################### End preferences section #############################
###############################################################################


def getMessage():
   """ Read a message from stdin and decode it. """
   rawLength = sys.stdin.buffer.read(4)
   if len(rawLength) == 0:
       sys.exit(0)
   messageLength = struct.unpack('@I', rawLength)[0]
   message = sys.stdin.buffer.read(messageLength).decode("utf-8")
   return json.loads(message)


def encodeMessage(messageContent):
   """ Encode a message for transmission, given its content. """
   encodedContent = json.dumps(messageContent)
   encodedLength = struct.pack('@I', len(encodedContent))
   return {'length': encodedLength, 'content': encodedContent}


def sendMessage(encodedMessage):
   """ Send an encoded message to stdout. """
   sys.stdout.buffer.write(encodedMessage['length'])
   sys.stdout.write(encodedMessage['content'])
   sys.stdout.flush()


def setPassGpgOpts(env, opts_dict):
   """ Add arguments to PASSWORD_STORE_GPG_OPTS. """
   opts = env.get('PASSWORD_STORE_GPG_OPTS', '')
   for opt, value in opts_dict.items():
       re_opt = new_opt = opt
       if value is not None:
           re_opt = rf"{opt}(?:=|\s+)\S*"
           new_opt = (
               f"{opt}={shlex.quote(value)}"
               if opt.startswith("--") else
               f"{opt} {shlex.quote(value)}"
           )
       # If the user's environment sets this opt, remove it.
       opts = re.sub(re_opt, '', opts)
       opts = f"{new_opt} {opts}"
   env['PASSWORD_STORE_GPG_OPTS'] = opts.strip()


if __name__ == "__main__":
   # Read message from standard input
   receivedMessage = getMessage()
   opt_args = []
   pos_args = []
   std_input = None

   if len(receivedMessage) == 0:
       opt_args = ["show"]
       pos_args = ["/"]
   elif receivedMessage[0] == "insert":
       opt_args = ["insert", "-m"]
       pos_args = [receivedMessage[1]]
       std_input = receivedMessage[2]
   elif receivedMessage[0] == "generate":
       opt_args = ["generate"]
       pos_args = [receivedMessage[1], receivedMessage[2]]
       if "-n" in receivedMessage[3:]:
           opt_args.append("-n")
   elif receivedMessage[0] == "grepMetaUrls" and len(receivedMessage) == 2:
       opt_args = ["grep", "-iE"]
       url_field_names = receivedMessage[1]
       pos_args = ["^({}):".format('|'.join(url_field_names))]
   elif receivedMessage[0] == "otp" and len(receivedMessage) == 2:
       opt_args = ["otp"]
       key = receivedMessage[1]
       key = "/" + (key[1:] if key[0] == "/" else key)
       pos_args = [key]
   else:
       opt_args = ["show"]
       key = receivedMessage[0]
       key = "/" + (key[1:] if key[0] == "/" else key)
       pos_args = [key]
   opt_args += COMMAND_ARGS

   # Set up (modified) command environment
   env = dict(os.environ)
   if "HOME" not in env:
       env["HOME"] = os.path.expanduser('~')
   for key, val in COMMAND_ENV.items():
       env[key] = val
   setPassGpgOpts(env, {'--status-fd': '2', '--debug': 'ipc'})

   # Set up subprocess params
   cmd = [COMMAND] + opt_args + ['--'] + pos_args
   proc_params = {
       'input': bytes(std_input, CHARSET) if std_input else None,
       'stdout': subprocess.PIPE,
       'stderr': subprocess.PIPE,
       'env': env
   }

   # Run and communicate with pass script
   proc = subprocess.run(cmd, **proc_params)

   # Send response
   sendMessage(
       encodeMessage({
           "exitCode": proc.returncode,
           "stdout": proc.stdout.decode(CHARSET),
           "stderr": proc.stderr.decode(CHARSET),
           "version": VERSION
       }))