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