1
2
3
4
5
6 package auth
7
8 import (
9 "cmd/internal/quoted"
10 "fmt"
11 "maps"
12 "net/http"
13 "net/url"
14 "os/exec"
15 "strings"
16 )
17
18
19
20
21
22
23 func runAuthCommand(command string, url string, res *http.Response) (map[string]http.Header, error) {
24 if command == "" {
25 panic("GOAUTH invoked an empty authenticator command:" + command)
26 }
27 cmd, err := buildCommand(command)
28 if err != nil {
29 return nil, err
30 }
31 if url != "" {
32 cmd.Args = append(cmd.Args, url)
33 }
34 cmd.Stderr = new(strings.Builder)
35 if res != nil && writeResponseToStdin(cmd, res) != nil {
36 return nil, fmt.Errorf("could not run command %s: %v\n%s", command, err, cmd.Stderr)
37 }
38 out, err := cmd.Output()
39 if err != nil {
40 return nil, fmt.Errorf("could not run command %s: %v\n%s", command, err, cmd.Stderr)
41 }
42 credentials, err := parseUserAuth(string(out))
43 if err != nil {
44 return nil, fmt.Errorf("cannot parse output of GOAUTH command %s: %v", command, err)
45 }
46 return credentials, nil
47 }
48
49
50
51
52
53
54 func parseUserAuth(data string) (map[string]http.Header, error) {
55 credentials := make(map[string]http.Header)
56 for data != "" {
57 var line string
58 var ok bool
59 var urls []string
60
61 for {
62 line, data, ok = strings.Cut(data, "\n")
63 if !ok {
64 return nil, fmt.Errorf("invalid format: missing empty line after URLs")
65 }
66 if line == "" {
67 break
68 }
69 u, err := url.ParseRequestURI(line)
70 if err != nil {
71 return nil, fmt.Errorf("could not parse URL %s: %v", line, err)
72 }
73 urls = append(urls, u.String())
74 }
75
76 header := make(http.Header)
77 for {
78 line, data, ok = strings.Cut(data, "\n")
79 if !ok {
80 return nil, fmt.Errorf("invalid format: missing empty line after headers")
81 }
82 if line == "" {
83 break
84 }
85 name, value, ok := strings.Cut(line, ": ")
86 value = strings.TrimSpace(value)
87 if !ok || !validHeaderFieldName(name) || !validHeaderFieldValue(value) {
88 return nil, fmt.Errorf("invalid format: invalid header line")
89 }
90 header.Add(name, value)
91 }
92 maps.Copy(credentials, mapHeadersToPrefixes(urls, header))
93 }
94 return credentials, nil
95 }
96
97
98
99 func mapHeadersToPrefixes(prefixes []string, header http.Header) map[string]http.Header {
100 prefixToHeaders := make(map[string]http.Header, len(prefixes))
101 for _, p := range prefixes {
102 p = strings.TrimPrefix(p, "https://")
103 prefixToHeaders[p] = header.Clone()
104 }
105 return prefixToHeaders
106 }
107
108 func buildCommand(command string) (*exec.Cmd, error) {
109 words, err := quoted.Split(command)
110 if err != nil {
111 return nil, fmt.Errorf("cannot parse GOAUTH command %s: %v", command, err)
112 }
113 cmd := exec.Command(words[0], words[1:]...)
114 return cmd, nil
115 }
116
117
118 func writeResponseToStdin(cmd *exec.Cmd, res *http.Response) error {
119 var output strings.Builder
120 output.WriteString(res.Proto + " " + res.Status + "\n")
121 for k, v := range res.Header {
122 output.WriteString(k + ": " + strings.Join(v, ", ") + "\n")
123 }
124 output.WriteString("\n")
125 cmd.Stdin = strings.NewReader(output.String())
126 return nil
127 }
128
View as plain text