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) }