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