/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/kbnm/handler.go
225 строк
5 KB
chrisnojima-zoom
Version 670 - clean2 (#29122)
08 июн 2026, 19:31
Не верифицирован
08 июн 2026, 19:31
1943197
Код
Авторство
О чём код?
package main import ( "bufio" "bytes" "errors" "fmt" "io" "os" "os/exec" "regexp" "strings" ) var errInvalidMethod = errors.New("invalid method") var errInvalidInput = errors.New("invalid input") var errMissingField = errors.New("missing field") var errUserNotFound = errors.New("user not found") var errKeybaseNotRunning = errors.New("keybase is not running") var errKeybaseNotLoggedIn = errors.New("keybase is not logged in") type errUnexpected struct { value string } func (err *errUnexpected) Error() string { return fmt.Sprintf("unexpected error: %s", err.value) } func execRunner(cmd *exec.Cmd) error { return cmd.Run() } // reUsernameQuery matches valid username queries var reUsernameQuery = regexp.MustCompile(`^[a-zA-Z0-9_\-.:@]{1,256}$`) // checkUsernameQuery returns the query if it's valid to use // We return the valid string so that it can be used as a separate // pre-validated variable which makes it less likely that the validation will // be accidentally removed in the future, as opposed to if we just continued // using the input variable after validating it. func checkUsernameQuery(s string) (string, error) { if s == "" { return "", errMissingField } if !reUsernameQuery.MatchString(s) { return "", errInvalidInput } return s, nil } // newHandler returns a request handler. func newHandler() *handler { return &handler{ Run: execRunner, FindKeybaseBinary: func() (string, error) { return findKeybaseBinary(keybaseBinary) }, } } type handler struct { // Run wraps the equivalent of cmd.Run(), allowing for mocking Run func(cmd *exec.Cmd) error // FindCmd returns the path of the keybase binary if it can find it FindKeybaseBinary func() (string, error) } // Handle accepts a request, handles it, and returns an optional result if there was no error func (h *handler) Handle(req *Request) (any, error) { switch req.Method { case "chat": return nil, h.handleChat(req) case "query": return h.handleQuery(req) } return nil, errInvalidMethod } // handleChat sends a chat message to a user. func (h *handler) handleChat(req *Request) error { if req.Body == "" { return errMissingField } idQuery, err := checkUsernameQuery(req.To) if err != nil { return err } binPath, err := h.FindKeybaseBinary() if err != nil { return err } var out bytes.Buffer cmd := exec.Command(binPath, "chat", "send", "--private", idQuery) cmd.Env = append(os.Environ(), "KEYBASE_LOG_FORMAT=plain") cmd.Stdin = strings.NewReader(req.Body) cmd.Stdout = &out cmd.Stderr = &out if err := h.Run(cmd); err != nil { return parseError(&out, err) } return nil } type resultQuery struct { Username string `json:"username"` } // parseQuery reads the stderr from a keybase query command and returns a result func parseQuery(r io.Reader) (*resultQuery, error) { scanner := bufio.NewScanner(r) var lastErrLine string for scanner.Scan() { // Find a line that looks like... "[INFO] 001 Identifying someuser" line := strings.TrimSpace(scanner.Text()) parts := strings.Split(line, " ") if len(parts) < 4 { continue } // Short circuit errors if parts[0] == "[ERRO]" { lastErrLine = strings.Join(parts[2:], " ") if lastErrLine == "Not found" { return nil, errUserNotFound } continue } if parts[2] != "Identifying" { continue } resp := &resultQuery{ Username: parts[3], } return resp, nil } if err := scanner.Err(); err != nil { return nil, scanner.Err() } // This could happen if the keybase service is broken return nil, &errUnexpected{lastErrLine} } // parseError reads stderr output and returns an error made from it. If it // fails to parse an error, it returns the fallback error. func parseError(r io.Reader, fallback error) error { scanner := bufio.NewScanner(r) // Find the final error var lastErr error for scanner.Scan() { // Should be of the form "[ERRO] 001 Not found" or "...: No resolution found" line := strings.TrimSpace(scanner.Text()) if line == "" { continue } // Check some error states we know about: if strings.Contains(line, "Keybase isn't running.") { return errKeybaseNotRunning } if strings.Contains(line, "You are not logged into Keybase.") { return errKeybaseNotLoggedIn } parts := strings.SplitN(line, " ", 3) if len(parts) < 3 { continue } if parts[0] != "[ERRO]" { continue } if strings.HasSuffix(parts[2], "No resolution found") { return errUserNotFound } if strings.HasPrefix(parts[2], "Not found") { return errUserNotFound } lastErr = fmt.Errorf("%s", parts[2]) } if lastErr != nil { return lastErr } return fallback } // handleQuery searches whether a user is present in Keybase. func (h *handler) handleQuery(req *Request) (*resultQuery, error) { idQuery, err := checkUsernameQuery(req.To) if err != nil { return nil, err } binPath, err := h.FindKeybaseBinary() if err != nil { return nil, err } // Unfortunately `keybase id ...` does not support JSON output, so we parse the output var out bytes.Buffer cmd := exec.Command(binPath, "id", idQuery) cmd.Env = append(os.Environ(), "KEYBASE_LOG_FORMAT=plain") cmd.Stdout = &out cmd.Stderr = &out if err := h.Run(cmd); err != nil { return nil, parseError(&out, err) } return parseQuery(&out) }