...
1
2 package ssh
3
4 import (
5 "bufio"
6 "net/url"
7 "os/exec"
8 "strings"
9 "sync"
10
11 "github.com/cli/safeexec"
12 )
13
14 type Translator struct {
15 cacheMap map[string]string
16 cacheMu sync.RWMutex
17 sshPath string
18 sshPathErr error
19 sshPathMu sync.Mutex
20
21 lookPath func(string) (string, error)
22 newCommand func(string, ...string) *exec.Cmd
23 }
24
25
26 func NewTranslator() *Translator {
27 return &Translator{}
28 }
29
30
31 func (t *Translator) Translate(u *url.URL) *url.URL {
32 if u.Scheme != "ssh" {
33 return u
34 }
35 resolvedHost, err := t.resolve(u.Hostname())
36 if err != nil {
37 return u
38 }
39 if strings.EqualFold(resolvedHost, "ssh.github.com") {
40 resolvedHost = "github.com"
41 }
42 newURL, _ := url.Parse(u.String())
43 newURL.Host = resolvedHost
44 return newURL
45 }
46
47 func (t *Translator) resolve(hostname string) (string, error) {
48 t.cacheMu.RLock()
49 cached, cacheFound := t.cacheMap[strings.ToLower(hostname)]
50 t.cacheMu.RUnlock()
51 if cacheFound {
52 return cached, nil
53 }
54
55 var sshPath string
56 t.sshPathMu.Lock()
57 if t.sshPath == "" && t.sshPathErr == nil {
58 lookPath := t.lookPath
59 if lookPath == nil {
60 lookPath = safeexec.LookPath
61 }
62 t.sshPath, t.sshPathErr = lookPath("ssh")
63 }
64 if t.sshPathErr != nil {
65 defer t.sshPathMu.Unlock()
66 return t.sshPath, t.sshPathErr
67 }
68 sshPath = t.sshPath
69 t.sshPathMu.Unlock()
70
71 t.cacheMu.Lock()
72 defer t.cacheMu.Unlock()
73
74 newCommand := t.newCommand
75 if newCommand == nil {
76 newCommand = exec.Command
77 }
78 sshCmd := newCommand(sshPath, "-G", hostname)
79 stdout, err := sshCmd.StdoutPipe()
80 if err != nil {
81 return "", err
82 }
83
84 if err := sshCmd.Start(); err != nil {
85 return "", err
86 }
87
88 var resolvedHost string
89 s := bufio.NewScanner(stdout)
90 for s.Scan() {
91 line := s.Text()
92 parts := strings.SplitN(line, " ", 2)
93 if len(parts) == 2 && parts[0] == "hostname" {
94 resolvedHost = parts[1]
95 }
96 }
97
98 err = sshCmd.Wait()
99 if err != nil || resolvedHost == "" {
100
101 resolvedHost = hostname
102 }
103
104 if t.cacheMap == nil {
105 t.cacheMap = map[string]string{}
106 }
107 t.cacheMap[strings.ToLower(hostname)] = resolvedHost
108 return resolvedHost, nil
109 }
110
View as plain text