<|start_filename|>util/scripts.go<|end_filename|>
// Copyright (c) 2018 <NAME> <<EMAIL>>
// MIT Licence - http://opensource.org/licenses/MIT

package util

import (
	"bytes"
	"encoding/json"
	"errors"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// ErrUnknownFileType is returned by Run for files it can't identify.
var ErrUnknownFileType = errors.New("unknown filetype")

// Default Runners used by Run to determine how to execute a file.
var (
	Executable Runner // run executable files directly
	Script     Runner // run script files with commands from Interpreters

	// DefaultInterpreters maps script file extensions to interpreters.
	// Used by the Script Runner (and by extension Run()) to determine
	// how to run files that aren't executable.
	DefaultInterpreters = map[string][]string{
		".py":          {"/usr/bin/python"},
		".rb":          {"/usr/bin/ruby"},
		".sh":          {"/bin/bash"},
		".zsh":         {"/bin/zsh"},
		".scpt":        {"/usr/bin/osascript"},
		".scptd":       {"/usr/bin/osascript"},
		".applescript": {"/usr/bin/osascript"},
		".js":          {"/usr/bin/osascript", "-l", "JavaScript"},
	}

	// Available runners in order they should be tried.
	// Executable and Script are added by init.
	runners Runners
)

func init() {
	// Default runners
	Executable = &ExecRunner{}
	Script = NewScriptRunner(DefaultInterpreters)

	runners = Runners{
		Executable,
		Script,
	}
}

// Runner knows how to execute a file passed to it.
// It is used by Run to determine how to run a file.
//
// When Run is passed a filepath, it asks each registered Runner
// in turn whether it can handle the file.
type Runner interface {
	// Can Runner execute this (type of) file?
	CanRun(filename string) bool
	// Cmd that executes file (via Runner's execution mechanism).
	Cmd(filename string, args ...string) *exec.Cmd
}

// Runners implements Runner over a sequence of Runner objects.
type Runners []Runner

// CanRun returns true if one of the runners can run this file.
func (rs Runners) CanRun(filename string) bool {
	for _, r := range rs {
		if r.CanRun(filename) {
			return true
		}
	}
	return false
}

// Cmd returns a command to run the (script) file.
func (rs Runners) Cmd(filename string, args ...string) *exec.Cmd {
	for _, r := range rs {
		if r.CanRun(filename) {
			return r.Cmd(filename, args...)
		}
	}

	return nil
}

// Run runs the executable or script at path and returns the output.
// If it can't figure out how to run the file (see Runner), it
// returns ErrUnknownFileType.
func (rs Runners) Run(filename string, args ...string) ([]byte, error) {
	fi, err := os.Stat(filename)
	if err != nil {
		return nil, err
	}
	if fi.IsDir() {
		return nil, ErrUnknownFileType
	}

	// See if a runner will accept file
	for _, r := range rs {
		if r.CanRun(filename) {
			cmd := r.Cmd(filename, args...)
			return RunCmd(cmd)
		}
	}

	return nil, ErrUnknownFileType
}

// Run runs the executable or script at path and returns the output.
// If it can't figure out how to run the file (see Runner), it
// returns ErrUnknownFileType.
func Run(filename string, args ...string) ([]byte, error) {
	return runners.Run(filename, args...)
}

// RunAS executes AppleScript and returns the output.
func RunAS(script string, args ...string) (string, error) {
	return runOsaScript(script, "AppleScript", args...)
}

// RunJS executes JavaScript (JXA) and returns the output.
func RunJS(script string, args ...string) (string, error) {
	return runOsaScript(script, "JavaScript", args...)
}

// runOsaScript executes a script with /usr/bin/osascript.
// It returns the output from STDOUT.
func runOsaScript(script, lang string, args ...string) (string, error) {
	argv := []string{"-l", lang, "-e", script}
	argv = append(argv, args...)

	cmd := exec.Command("/usr/bin/osascript", argv...)
	data, err := RunCmd(cmd)
	if err != nil {
		return "", err
	}

	// Remove trailing newline added by osascript
	s := strings.TrimSuffix(string(data), "\n")

	return s, nil
}

// RunCmd executes a command and returns its output.
//
// The main difference to exec.Cmd.Output() is that RunCmd writes all
// STDERR output to the log if a command fails.
func RunCmd(cmd *exec.Cmd) ([]byte, error) {
	var stdout, stderr bytes.Buffer

	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		log.Printf("------------- %v ---------------", cmd.Args)
		log.Println(stderr.String())
		log.Println("----------------------------------------------")
		return nil, err
	}

	return stdout.Bytes(), nil
}

// QuoteAS converts string to an AppleScript string literal for insertion into AppleScript code.
// It wraps the value in quotation marks, so don't insert additional ones.
func QuoteAS(s string) string {
	if s == "" {
		return `""`
	}

	if s == `"` {
		return "quote"
	}

	chars := []string{}
	for i, c := range s {
		if c == '"' {
			switch i {
			case 0:
				chars = append(chars, `quote & "`)
			case len(s) - 1:
				chars = append(chars, `" & quote`)
			default:
				chars = append(chars, `" & quote & "`)
			}
			continue
		}
		if i == 0 {
			chars = append(chars, `"`)
		}
		chars = append(chars, string(c))
		if i == len(s)-1 {
			chars = append(chars, `"`)
		}
	}

	return strings.Join(chars, "")
}

// QuoteJS converts a value into JavaScript source code.
// It calls json.Marshal(v), and returns an empty string if an error occurs.
func QuoteJS(v interface{}) string {
	data, err := json.Marshal(v)
	if err != nil {
		log.Printf("couldn't convert %#v to JS: %v", v, err)
		return ""
	}

	return string(data)
}

// ExecRunner implements Runner for executable files.
type ExecRunner struct{}

// CanRun returns true if file exists and is executable.
func (r ExecRunner) CanRun(filename string) bool {
	fi, err := os.Stat(filename)
	if err != nil || fi.IsDir() {
		return false
	}

	perms := uint32(fi.Mode().Perm())
	return perms&0111 != 0
}

// Cmd returns a Cmd to run executable with args.
func (r ExecRunner) Cmd(executable string, args ...string) *exec.Cmd {
	executable, err := filepath.Abs(executable)
	if err != nil {
		panic(err)
	}

	return exec.Command(executable, args...)
}

// ScriptRunner implements Runner for the specified file extensions.
// It calls the given script with the interpreter command from Interpreters.
//
// A ScriptRunner (combined with Runners, which implements Run) is a useful
// base for adding support for running scripts to your own program.
type ScriptRunner struct {
	// Interpreters is an "extension: command" mapping of file extensions
	// to commands to invoke interpreters that can run the files.
	//
	//     Interpreters = map[string][]string{
	//         ".py": []string{"/usr/bin/python"},
	//         ".rb": []string{"/usr/bin/ruby"},
	//     }
	//
	Interpreters map[string][]string
}

// NewScriptRunner creates a new ScriptRunner for interpreters.
func NewScriptRunner(interpreters map[string][]string) *ScriptRunner {
	if interpreters == nil {
		interpreters = map[string][]string{}
	}

	r := &ScriptRunner{
		Interpreters: make(map[string][]string, len(interpreters)),
	}

	// Copy over defaults
	for k, v := range interpreters {
		r.Interpreters[k] = v
	}

	return r
}

// CanRun returns true if file exists and its extension is in Interpreters.
func (r ScriptRunner) CanRun(filename string) bool {
	if fi, err := os.Stat(filename); err != nil || fi.IsDir() {
		return false
	}
	ext := strings.ToLower(filepath.Ext(filename))

	_, ok := r.Interpreters[ext]
	return ok
}

// Cmd returns a Cmd to run filename with its interpreter.
func (r ScriptRunner) Cmd(filename string, args ...string) *exec.Cmd {
	var (
		argv    []string
		command string
	)

	ext := strings.ToLower(filepath.Ext(filename))
	interpreter := DefaultInterpreters[ext]

	command = interpreter[0]

	argv = append(argv, interpreter[1:]...) // any remainder of interpreter command
	argv = append(argv, filename)           // path to script file
	argv = append(argv, args...)            // arguments to script

	return exec.Command(command, argv...)
}


<|start_filename|>keychain/keychain_test.go<|end_filename|>
// Copyright (c) 2019 <NAME> <<EMAIL>>
// MIT Licence applies http://opensource.org/licenses/MIT

package keychain

import (
	"os/exec"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestKeychain verifies reading from and writing to Keychain.
func TestKeychain(t *testing.T) {
	t.Parallel()

	var (
		service   = "net.deanishe.awgo"
		name      = "test_password"
		password  = "<PASSWORD>"
		password2 = "<PASSWORD>"
		kc        = New(service)
	)

	t.Run("sanity check", func(t *testing.T) {
		// ensure test account doesn't exist
		cmd := exec.Command("/usr/bin/security", "delete-generic-password", "-s", service, "-a", name)
		if err := cmd.Run(); err != nil {
			require.Equal(t, 44, cmd.ProcessState.ExitCode(), "unexpected exit code")
		}
	})

	t.Run("missing items", func(t *testing.T) {
		assert.Equal(t, ErrNotFound, kc.Delete(name), "delete missing item did not fail")
		_, err := kc.Get(name)
		assert.Equal(t, ErrNotFound, err, "retrieve missing item did not fail")
	})

	t.Run("set password", func(t *testing.T) {
		assert.Nil(t, kc.Set(name, password), "set password failed")
		v, err := kc.Get(name)
		assert.Nil(t, err, "get password failed")
		assert.Equal(t, password, v, "unexpected password")
	})

	t.Run("change password", func(t *testing.T) {
		assert.Nil(t, kc.Set(name, password2), "change password failed")
		v, err := kc.Get(name)
		assert.Nil(t, err, "get changed password failed")
		assert.Equal(t, password2, v, "unexpected password")
	})

	t.Run("delete password", func(t *testing.T) {
		assert.Nil(t, kc.Delete(name), "delete failed")
	})
}

// TestParse verifies parsing of sometimes-encoded Keychain passwords.
func TestParse(t *testing.T) {
	t.Parallel()

	data := []struct {
		in    string
		pw    string
		valid bool
	}{
		// ascii passwords
		{`password: "<PASSWORD>"`, "<PASSWORD>", true},
		{`password: "<PASSWORD>"`, "<PASSWORD>", true},
		// unicode passwords
		{`password: <PASSWORD>  "<PASSWORD>5<PASSWORD>_s\303\251cr\303\251t"`, "<PASSWORD>", true},
		{`password: <PASSWORD>  "<PASSWORD>"`, "<PASSWORD>", true},

		// invalid
		{``, "", false},
		{`password: `, "", false},
		{`password: 0x"`, "", false},
		{`password: <PASSWORD>`, "", false},
	}

	for _, td := range data {
		td := td
		t.Run(td.in, func(t *testing.T) {
			t.Parallel()
			pw, err := parseKeychainPassword(td.in)
			if td.valid {
				assert.Equal(t, td.pw, pw, "unexpected password")
			} else {
				assert.NotNil(t, err, "invalid password accepted")
			}
		})
	}
}
