]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/go-getter/get_http.go
deps: github.com/hashicorp/terraform@sdk-v0.11-with-go-modules
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / go-getter / get_http.go
1 package getter
2
3 import (
4 "encoding/xml"
5 "fmt"
6 "io"
7 "net/http"
8 "net/url"
9 "os"
10 "path/filepath"
11 "strings"
12
13 "github.com/hashicorp/go-safetemp"
14 )
15
16 // HttpGetter is a Getter implementation that will download from an HTTP
17 // endpoint.
18 //
19 // For file downloads, HTTP is used directly.
20 //
21 // The protocol for downloading a directory from an HTTP endpoing is as follows:
22 //
23 // An HTTP GET request is made to the URL with the additional GET parameter
24 // "terraform-get=1". This lets you handle that scenario specially if you
25 // wish. The response must be a 2xx.
26 //
27 // First, a header is looked for "X-Terraform-Get" which should contain
28 // a source URL to download.
29 //
30 // If the header is not present, then a meta tag is searched for named
31 // "terraform-get" and the content should be a source URL.
32 //
33 // The source URL, whether from the header or meta tag, must be a fully
34 // formed URL. The shorthand syntax of "github.com/foo/bar" or relative
35 // paths are not allowed.
36 type HttpGetter struct {
37 // Netrc, if true, will lookup and use auth information found
38 // in the user's netrc file if available.
39 Netrc bool
40
41 // Client is the http.Client to use for Get requests.
42 // This defaults to a cleanhttp.DefaultClient if left unset.
43 Client *http.Client
44 }
45
46 func (g *HttpGetter) ClientMode(u *url.URL) (ClientMode, error) {
47 if strings.HasSuffix(u.Path, "/") {
48 return ClientModeDir, nil
49 }
50 return ClientModeFile, nil
51 }
52
53 func (g *HttpGetter) Get(dst string, u *url.URL) error {
54 // Copy the URL so we can modify it
55 var newU url.URL = *u
56 u = &newU
57
58 if g.Netrc {
59 // Add auth from netrc if we can
60 if err := addAuthFromNetrc(u); err != nil {
61 return err
62 }
63 }
64
65 if g.Client == nil {
66 g.Client = httpClient
67 }
68
69 // Add terraform-get to the parameter.
70 q := u.Query()
71 q.Add("terraform-get", "1")
72 u.RawQuery = q.Encode()
73
74 // Get the URL
75 resp, err := g.Client.Get(u.String())
76 if err != nil {
77 return err
78 }
79 defer resp.Body.Close()
80 if resp.StatusCode < 200 || resp.StatusCode >= 300 {
81 return fmt.Errorf("bad response code: %d", resp.StatusCode)
82 }
83
84 // Extract the source URL
85 var source string
86 if v := resp.Header.Get("X-Terraform-Get"); v != "" {
87 source = v
88 } else {
89 source, err = g.parseMeta(resp.Body)
90 if err != nil {
91 return err
92 }
93 }
94 if source == "" {
95 return fmt.Errorf("no source URL was returned")
96 }
97
98 // If there is a subdir component, then we download the root separately
99 // into a temporary directory, then copy over the proper subdir.
100 source, subDir := SourceDirSubdir(source)
101 if subDir == "" {
102 return Get(dst, source)
103 }
104
105 // We have a subdir, time to jump some hoops
106 return g.getSubdir(dst, source, subDir)
107 }
108
109 func (g *HttpGetter) GetFile(dst string, u *url.URL) error {
110 if g.Netrc {
111 // Add auth from netrc if we can
112 if err := addAuthFromNetrc(u); err != nil {
113 return err
114 }
115 }
116
117 if g.Client == nil {
118 g.Client = httpClient
119 }
120
121 resp, err := g.Client.Get(u.String())
122 if err != nil {
123 return err
124 }
125 defer resp.Body.Close()
126 if resp.StatusCode != 200 {
127 return fmt.Errorf("bad response code: %d", resp.StatusCode)
128 }
129
130 // Create all the parent directories
131 if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
132 return err
133 }
134
135 f, err := os.Create(dst)
136 if err != nil {
137 return err
138 }
139
140 n, err := io.Copy(f, resp.Body)
141 if err == nil && n < resp.ContentLength {
142 err = io.ErrShortWrite
143 }
144 if err1 := f.Close(); err == nil {
145 err = err1
146 }
147 return err
148 }
149
150 // getSubdir downloads the source into the destination, but with
151 // the proper subdir.
152 func (g *HttpGetter) getSubdir(dst, source, subDir string) error {
153 // Create a temporary directory to store the full source. This has to be
154 // a non-existent directory.
155 td, tdcloser, err := safetemp.Dir("", "getter")
156 if err != nil {
157 return err
158 }
159 defer tdcloser.Close()
160
161 // Download that into the given directory
162 if err := Get(td, source); err != nil {
163 return err
164 }
165
166 // Process any globbing
167 sourcePath, err := SubdirGlob(td, subDir)
168 if err != nil {
169 return err
170 }
171
172 // Make sure the subdir path actually exists
173 if _, err := os.Stat(sourcePath); err != nil {
174 return fmt.Errorf(
175 "Error downloading %s: %s", source, err)
176 }
177
178 // Copy the subdirectory into our actual destination.
179 if err := os.RemoveAll(dst); err != nil {
180 return err
181 }
182
183 // Make the final destination
184 if err := os.MkdirAll(dst, 0755); err != nil {
185 return err
186 }
187
188 return copyDir(dst, sourcePath, false)
189 }
190
191 // parseMeta looks for the first meta tag in the given reader that
192 // will give us the source URL.
193 func (g *HttpGetter) parseMeta(r io.Reader) (string, error) {
194 d := xml.NewDecoder(r)
195 d.CharsetReader = charsetReader
196 d.Strict = false
197 var err error
198 var t xml.Token
199 for {
200 t, err = d.Token()
201 if err != nil {
202 if err == io.EOF {
203 err = nil
204 }
205 return "", err
206 }
207 if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
208 return "", nil
209 }
210 if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
211 return "", nil
212 }
213 e, ok := t.(xml.StartElement)
214 if !ok || !strings.EqualFold(e.Name.Local, "meta") {
215 continue
216 }
217 if attrValue(e.Attr, "name") != "terraform-get" {
218 continue
219 }
220 if f := attrValue(e.Attr, "content"); f != "" {
221 return f, nil
222 }
223 }
224 }
225
226 // attrValue returns the attribute value for the case-insensitive key
227 // `name', or the empty string if nothing is found.
228 func attrValue(attrs []xml.Attr, name string) string {
229 for _, a := range attrs {
230 if strings.EqualFold(a.Name.Local, name) {
231 return a.Value
232 }
233 }
234 return ""
235 }
236
237 // charsetReader returns a reader for the given charset. Currently
238 // it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
239 // error which is printed by go get, so the user can find why the package
240 // wasn't downloaded if the encoding is not supported. Note that, in
241 // order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
242 // greater than 0x7f are not rejected).
243 func charsetReader(charset string, input io.Reader) (io.Reader, error) {
244 switch strings.ToLower(charset) {
245 case "ascii":
246 return input, nil
247 default:
248 return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
249 }
250 }