167 lines
4.7 KiB
Go
167 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"git.julianfamily.org/keepassgo/internal/browserbridge"
|
|
"git.julianfamily.org/keepassgo/internal/grpcaddr"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
type bridgeConfig struct {
|
|
grpcAddr string
|
|
}
|
|
|
|
func main() {
|
|
cfg := bridgeConfig{
|
|
grpcAddr: resolveGlobalGRPCAddr(os.Args[1:]),
|
|
}
|
|
if len(os.Args) > 1 {
|
|
switch strings.TrimSpace(os.Args[1]) {
|
|
case "install-native-host":
|
|
if err := runInstallNativeHost(os.Args[2:]); err != nil {
|
|
fail(err)
|
|
}
|
|
return
|
|
case "status":
|
|
if err := runStatus(cfg, stripGlobalGRPCAddrFlags(os.Args[2:])); err != nil {
|
|
fail(err)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
if err := runNativeMessage(cfg); err != nil {
|
|
_ = browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()})
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func runInstallNativeHost(args []string) error {
|
|
fs := flag.NewFlagSet("install-native-host", flag.ContinueOnError)
|
|
browserName := fs.String("browser", string(browserbridge.BrowserFirefox), "target browser: firefox, chrome, chromium")
|
|
binaryPath := fs.String("binary", "", "path to keepassgo-browser-bridge binary")
|
|
extensionID := fs.String("extension-id", "", "browser extension id (required for chrome/chromium)")
|
|
extensionKey := fs.String("extension-key", "", "Chromium manifest public key used to derive a fixed extension id")
|
|
extensionKeyFile := fs.String("extension-key-file", "", "path to a Chromium manifest public key file")
|
|
outputPath := fs.String("output", "", "native host manifest output path")
|
|
if err := fs.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
path := strings.TrimSpace(*binaryPath)
|
|
if path == "" {
|
|
resolved, err := defaultBinaryPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path = resolved
|
|
}
|
|
resolvedExtensionID := strings.TrimSpace(*extensionID)
|
|
if resolvedExtensionID == "" {
|
|
keyValue := strings.TrimSpace(*extensionKey)
|
|
if keyValue == "" && strings.TrimSpace(*extensionKeyFile) != "" {
|
|
data, err := os.ReadFile(strings.TrimSpace(*extensionKeyFile))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyValue = string(data)
|
|
}
|
|
if keyValue != "" {
|
|
derivedID, err := browserbridge.ChromiumExtensionIDFromManifestKey(keyValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resolvedExtensionID = derivedID
|
|
}
|
|
}
|
|
installed, err := browserbridge.InstallManifest(browserbridge.Browser(strings.TrimSpace(*browserName)), path, resolvedExtensionID, strings.TrimSpace(*outputPath))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintln(os.Stdout, installed)
|
|
return nil
|
|
}
|
|
|
|
func runStatus(cfg bridgeConfig, args []string) error {
|
|
fs := flag.NewFlagSet("status", flag.ContinueOnError)
|
|
token := fs.String("token", "", "KeePassGO API bearer token")
|
|
if err := fs.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
req := browserbridge.Request{
|
|
Action: "status",
|
|
BearerToken: strings.TrimSpace(*token),
|
|
}
|
|
conn, client, ctx, err := dialBridge(context.Background(), cfg, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
resp := browserbridge.HandleRequest(ctx, req, cfg.grpcAddr, client)
|
|
enc := json.NewEncoder(os.Stdout)
|
|
enc.SetIndent("", " ")
|
|
return enc.Encode(resp)
|
|
}
|
|
|
|
func runNativeMessage(cfg bridgeConfig) error {
|
|
req, err := browserbridge.ReadRequest(os.Stdin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn, client, ctx, err := dialBridge(context.Background(), cfg, req)
|
|
if err != nil {
|
|
return browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()})
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
return browserbridge.WriteResponse(os.Stdout, browserbridge.HandleRequest(ctx, req, cfg.grpcAddr, client))
|
|
}
|
|
|
|
func dialBridge(ctx context.Context, cfg bridgeConfig, req browserbridge.Request) (*grpc.ClientConn, *browserbridge.GRPCClient, context.Context, error) {
|
|
return browserbridge.DialRequest(ctx, req, cfg.grpcAddr)
|
|
}
|
|
|
|
func defaultBinaryPath() (string, error) {
|
|
return browserbridge.ResolveBridgeBinaryPath("")
|
|
}
|
|
|
|
func resolveGlobalGRPCAddr(args []string) string {
|
|
addr := grpcaddr.Default(runtime.GOOS)
|
|
for i := 0; i < len(args); i++ {
|
|
arg := strings.TrimSpace(args[i])
|
|
switch {
|
|
case arg == "--grpc-addr" && i+1 < len(args):
|
|
return strings.TrimSpace(args[i+1])
|
|
case strings.HasPrefix(arg, "--grpc-addr="):
|
|
return strings.TrimSpace(strings.TrimPrefix(arg, "--grpc-addr="))
|
|
}
|
|
}
|
|
return addr
|
|
}
|
|
|
|
func stripGlobalGRPCAddrFlags(args []string) []string {
|
|
out := make([]string, 0, len(args))
|
|
for i := 0; i < len(args); i++ {
|
|
arg := strings.TrimSpace(args[i])
|
|
switch {
|
|
case arg == "--grpc-addr" && i+1 < len(args):
|
|
i++
|
|
continue
|
|
case strings.HasPrefix(arg, "--grpc-addr="):
|
|
continue
|
|
default:
|
|
out = append(out, args[i])
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func fail(err error) {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|