#!/usr/bin/env python3

# Requires python-gpg library
import os
import sys
import json
import gnupg
import struct
import difflib
from urllib.parse import urlparse
from collections import OrderedDict

if sys.platform == "win32":
  # Interacts with windows registry to register this app
  import winreg
  # On Windows, the default I/O mode is O_TEXT. Set this to O_BINARY
  # to avoid unwanted modifications of the input/output streams.
  import msvcrt
  msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
  msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)


EXTENSION_NAME = "com.piaotech.chrome.extension.pass"
EXTENSION_ID = "oblajhnjmknenodebpekmkliopipoolo"

def get_store_path():
  if 'PASSWORD_STORE_DIR' in os.environ:
    dir = os.environ['PASSWORD_STORE_DIR']
  else:
    dir = os.path.expanduser('~') + "/.password-store"
  return dir

# Returns a similarity score between two strings.
def similarity(pattern1, pattern2):
  return difflib.SequenceMatcher(a=pattern1.lower(), b=pattern2.lower()).ratio()

# Returns the similarity score of a credential to a pattern. This method is used
# as sort key for sorting the credential list based on similarity to the search
# pattern.
def sort_key(credential):
  score = similarity(credential[1], credential[3])
  return score

# Returns a list of credentials ordered by similarity to the pattern argument.
# The list is a list of lists with the format:
# 
#  [ 
#    [ "store path", "url", "username", "pattern" ],
#    [ "store path", "url", "username", "pattern" ],
#    [ "store path", "url", "username", "pattern" ],
#    .....
#  ]
def get_list(pattern):
  credentials = []
  for root, dirs, files in os.walk(get_store_path(), followlinks=True):
    if len(files) > 0:
      for f in files:
        if f.endswith(".gpg"):
          cred_path = root.replace(get_store_path(), "")
          cred_url = os.path.basename(os.path.normpath(cred_path))
          cred_user = f.replace(".gpg", "")
          credentials.append([cred_path, cred_url, cred_user, pattern])

  return sorted(credentials, key = sort_key, reverse = True)

# Returns the pass password for the specified path. The path must corresponde to
# a unique existing gpg file inside the password store.
def get_pass(path):
  password = ""
  gpg = gnupg.GPG()
  txt = open(get_store_path() + "/" + path + ".gpg", "rb")
  data = gpg.decrypt_file(txt)
  if data.status == "decryption ok":
    password = data.data.decode('utf-8').split("\n")[0]

  return password

# Sends the response message with the format that chrome HostNativeApplications expect.
def send_message(message):
  response = json.dumps(message).encode('utf-8')
  sys.stdout.buffer.write(struct.pack('I', len(response)))
  sys.stdout.buffer.write(response)
  sys.stdout.buffer.flush()

# Method that implements Chrome Native App protocol for messaging.
def process_native():
  size = sys.stdin.buffer.read(4)

  if not size:
    send_message({ "action": "error", "msg": "no data" })
    exit

  try:
    length = struct.unpack('I', size)[0]
    data = sys.stdin.buffer.read(length)
    request = json.loads(data.decode('utf-8'))
    action = request["action"]
    if action == "get-creds":
      pattern = urlparse(request["url"]).netloc
      send_message({ "action": "fill-creds", "credentials": get_list(pattern), "url": pattern })
    elif action == "get-pass":
      user = request["user"]
      path = request["path"]
      send_message({ "action": "fill-pass", "user": user, "pass": get_pass(path) })
  except Exception:
    send_message({ "action": "error", "msg": sys.exc_info()[0] })

# Method prints to stdout the list of credentials ordered by a similarity to
# pattern.
def print_creds(pattern):
 for credential in get_list(pattern):
   score = similarity(credential[1], pattern)
   print("compare %s and %s score %.4f" % (credential[1], credential[3], score))

# Determines the path were the native app manifest should be installed.
def nativePathChrome():
  if sys.platform == "darwin":
    return os.path.expanduser('~') + "/Library/Application Support/Google/Chrome/NativeMessagingHosts/"
  elif sys.platform == "linux" or sys.platform == "linux2":
    return os.path.expanduser('~') + "/.config/google-chrome/NativeMessagingHosts/"
  elif sys.platform == "win32":
    return os.path.join("Google", "Chrome", "NativeMessagingHosts",
                              EXTENSION_NAME)
  else:
    sys.stderr.write("Only linux, OSX or Windows are supported")
    sys.exit(1)

def nativePathChromium():
  if sys.platform == "darwin":
    return os.path.expanduser('~') + "/Library/Application Support/Chromium/NativeMessagingHosts/"
  elif sys.platform == "linux" or sys.platform == "linux2":
    return os.path.expanduser('~') + "/.config/chromium/NativeMessagingHosts/"
  elif sys.platform == "win32":
    return os.path.expanduser('~') + "/AppData/Roaming/Chromium/NativeMessagingHosts/"
  else:
    sys.stderr.write("Only linux or OSX are supported")
    sys.exit(1)

# Installs the Native Host Application manifest for this script into Chrome.
def install(nativePath, extension_id):
  if sys.platform == "win32":
    # Appends APPDATA to nativePath and set this path as a registry value
    reg_key = os.path.join("Software", nativePath)
    nativePath = os.path.join(os.environ['APPDATA'], nativePath)
    outfile = os.path.join(nativePath, EXTENSION_NAME + '.json')
    winreg.SetValue(winreg.HKEY_CURRENT_USER, reg_key, winreg.REG_SZ, outfile)

  if not os.path.exists(nativePath):
    os.makedirs(nativePath)

  if sys.platform == "win32":
    batch = "python \"{}\" %*".format(os.path.realpath(__file__))
    nativeApp = EXTENSION_NAME + '.bat'
    outfile = os.path.join(nativePath, nativeApp)
    with open(outfile, 'w') as file:
      file.write("@echo off\n\n")
      file.write(batch)
  else:
    nativeApp = os.path.realpath(__file__)

  manifest = OrderedDict()
  manifest['name'] = EXTENSION_NAME
  manifest["description"] = "Chrome native host application for pass - the standard Unix password manager."
  manifest["path"] = nativeApp
  manifest["type"] = "stdio"
  manifest["allowed_origins"] = ["chrome-extension://" + extension_id + "/"]

  outfile = os.path.join(nativePath, manifest['name'] + '.json')

  with open(outfile, 'w') as file:
    json.dump(manifest, file, indent='\t')


if len(sys.argv) > 1:
  if sys.argv[1].startswith('chrome-extension://'):
    process_native()
  elif sys.argv[1] == "install":
    if len(sys.argv) > 2:
      install(nativePathChrome(), sys.argv[2])
      install(nativePathChromium(), sys.argv[2])
    else:
      install(nativePathChrome(), EXTENSION_ID)
      install(nativePathChromium(), EXTENSION_ID)
  else:
    print_creds(sys.argv[1])

