Skip to content

Commit ef2fcc6

Browse files
authored
Merge pull request #370 from hashicorp/jbardin/protocol-check
Check detectors and relative paths before proto from X-Terraform-Get
2 parents c5e9d08 + cd6aaf8 commit ef2fcc6

File tree

2 files changed

+131
-32
lines changed

2 files changed

+131
-32
lines changed

get_http.go

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/url"
1111
"os"
1212
"path/filepath"
13+
"regexp"
1314
"strings"
1415
"time"
1516

@@ -293,50 +294,79 @@ func (g *HttpGetter) Get(dst string, u *url.URL) error {
293294
// If there is a subdir component, then we download the root separately
294295
// into a temporary directory, then copy over the proper subdir.
295296
source, subDir := SourceDirSubdir(source)
296-
if subDir == "" {
297-
var opts []ClientOption
298297

299-
// Check if the protocol was switched to one which was not configured.
300-
//
298+
var opts []ClientOption
299+
300+
// Check if the protocol was switched to one which was not configured.
301+
if g.client != nil && g.client.Getters != nil {
302+
// We must first use the Detectors provided, because `X-Terraform-Get does
303+
// not necessarily return a valid URL. We can replace the source string
304+
// here, since the detectors would have been called immediately during the
305+
// next Get anyway.
306+
source, err = Detect(source, g.client.Pwd, g.client.Detectors)
307+
if err != nil {
308+
return err
309+
}
310+
311+
protocol := ""
312+
// X-Terraform-Get allows paths relative to the previous request too,
313+
// which won't have a protocol.
314+
if !relativeGet(source) {
315+
protocol = strings.Split(source, ":")[0]
316+
}
317+
301318
// Otherwise, all default getters are allowed.
302-
if g.client != nil && g.client.Getters != nil {
303-
protocol := strings.Split(source, ":")[0]
319+
if protocol != "" {
304320
_, allowed := g.client.Getters[protocol]
305321
if !allowed {
306322
return fmt.Errorf("no getter available for X-Terraform-Get source protocol: %q", protocol)
307323
}
308324
}
325+
}
309326

310-
// Add any getter client options.
311-
if g.client != nil {
312-
opts = g.client.Options
313-
}
327+
// Add any getter client options.
328+
if g.client != nil {
329+
opts = g.client.Options
330+
}
314331

315-
// If the client is nil, we know we're using the HttpGetter directly. In this case,
316-
// we don't know exactly which protocols are configued, but we can make a good guess.
317-
//
318-
// This prevents all default getters from being allowed when only using the
319-
// HttpGetter directly. To enable protocol switching, a client "wrapper" must
320-
// be used.
321-
if g.client == nil {
332+
// If the client is nil, we know we're using the HttpGetter directly. In
333+
// this case, we don't know exactly which protocols are configured, but we
334+
// can make a good guess.
335+
//
336+
// This prevents all default getters from being allowed when only using the
337+
// HttpGetter directly. To enable protocol switching, a client "wrapper" must
338+
// be used.
339+
if g.client == nil {
340+
switch {
341+
case subDir != "":
342+
// If there's a subdirectory, we will also need a file getter to
343+
// unpack it.
344+
opts = append(opts, WithGetters(map[string]Getter{
345+
"file": new(FileGetter),
346+
"http": g,
347+
"https": g,
348+
}))
349+
default:
322350
opts = append(opts, WithGetters(map[string]Getter{
323351
"http": g,
324352
"https": g,
325353
}))
326354
}
355+
}
327356

328-
// Ensure we pass along the context we constructed in this function.
329-
//
330-
// This is especially important to enforce a limit on X-Terraform-Get redirects
331-
// which could be setup, if configured, at the top of this function.
332-
opts = append(opts, WithContext(ctx))
357+
// Ensure we pass along the context we constructed in this function.
358+
//
359+
// This is especially important to enforce a limit on X-Terraform-Get redirects
360+
// which could be setup, if configured, at the top of this function.
361+
opts = append(opts, WithContext(ctx))
333362

334-
// Note: this allows the protocol to be switched to another configured getters.
335-
return Get(dst, source, opts...)
363+
if subDir != "" {
364+
// We have a subdir, time to jump some hoops
365+
return g.getSubdir(ctx, dst, source, subDir, opts...)
336366
}
337367

338-
// We have a subdir, time to jump some hoops
339-
return g.getSubdir(ctx, dst, source, subDir)
368+
// Note: this allows the protocol to be switched to another configured getters.
369+
return Get(dst, source, opts...)
340370
}
341371

342372
// GetFile fetches the file from src and stores it at dst.
@@ -478,7 +508,7 @@ func (g *HttpGetter) GetFile(dst string, src *url.URL) error {
478508

479509
// getSubdir downloads the source into the destination, but with
480510
// the proper subdir.
481-
func (g *HttpGetter) getSubdir(ctx context.Context, dst, source, subDir string) error {
511+
func (g *HttpGetter) getSubdir(ctx context.Context, dst, source, subDir string, opts ...ClientOption) error {
482512
// Create a temporary directory to store the full source. This has to be
483513
// a non-existent directory.
484514
td, tdcloser, err := safetemp.Dir("", "getter")
@@ -487,10 +517,6 @@ func (g *HttpGetter) getSubdir(ctx context.Context, dst, source, subDir string)
487517
}
488518
defer tdcloser.Close()
489519

490-
var opts []ClientOption
491-
if g.client != nil {
492-
opts = g.client.Options
493-
}
494520
// Download that into the given directory
495521
if err := Get(td, source, opts...); err != nil {
496522
return err
@@ -566,6 +592,9 @@ func (g *HttpGetter) parseMeta(ctx context.Context, r io.Reader) (string, error)
566592
}
567593
}
568594

595+
// X-Terraform-Get allows paths relative to the previous request
596+
var relativeGet = regexp.MustCompile(`^\.{0,2}/`).MatchString
597+
569598
// attrValue returns the attribute value for the case-insensitive key
570599
// `name', or the empty string if nothing is found.
571600
func attrValue(attrs []xml.Attr, name string) string {

get_http_test.go

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,47 @@ func TestHttpGetter__XTerraformGetDisabled(t *testing.T) {
584584
}
585585
}
586586

587+
type testCustomDetector struct{}
588+
589+
func (testCustomDetector) Detect(src, _ string) (string, bool, error) {
590+
if strings.HasPrefix(src, "custom|") {
591+
return "http://" + src[7:], true, nil
592+
}
593+
return "", false, nil
594+
}
595+
596+
// test a source url with no protocol
597+
func TestHttpGetter__XTerraformGetDetected(t *testing.T) {
598+
ctx, cancel := context.WithCancel(context.Background())
599+
defer cancel()
600+
601+
ln := testHttpServerWithXTerraformGetDetected(t)
602+
603+
var u url.URL
604+
u.Scheme = "http"
605+
u.Host = ln.Addr().String()
606+
u.Path = "/first"
607+
dst := tempDir(t)
608+
609+
c := &Client{
610+
Ctx: ctx,
611+
Src: u.String(),
612+
Dst: dst,
613+
Mode: ClientModeDir,
614+
Options: []ClientOption{
615+
func(c *Client) error {
616+
c.Detectors = append(c.Detectors, testCustomDetector{})
617+
return nil
618+
},
619+
},
620+
}
621+
622+
err := c.Get()
623+
if err != nil {
624+
t.Fatal(err)
625+
}
626+
}
627+
587628
func TestHttpGetter__XTerraformGetProxyBypass(t *testing.T) {
588629
ctx, cancel := context.WithCancel(context.Background())
589630
defer cancel()
@@ -696,7 +737,6 @@ func TestHttpGetter__endless_body(t *testing.T) {
696737
client := &Client{
697738
Ctx: ctx,
698739
Mode: ClientModeFile,
699-
// Mode: ClientModeDir,
700740
Getters: map[string]Getter{
701741
"http": httpGetter,
702742
},
@@ -770,6 +810,36 @@ func testHttpServerWithXTerraformGetLoop(t *testing.T) net.Listener {
770810
return ln
771811
}
772812

813+
func testHttpServerWithXTerraformGetDetected(t *testing.T) net.Listener {
814+
t.Helper()
815+
816+
ln, err := net.Listen("tcp", "127.0.0.1:0")
817+
if err != nil {
818+
t.Fatalf("err: %s", err)
819+
}
820+
821+
// This location requires a custom detector to work.
822+
first := fmt.Sprintf("custom|%s/archive.tar.gz", ln.Addr())
823+
824+
mux := http.NewServeMux()
825+
mux.HandleFunc("/first", func(w http.ResponseWriter, r *http.Request) {
826+
w.Header().Set("X-Terraform-Get", first)
827+
})
828+
mux.HandleFunc("/archive.tar.gz", func(w http.ResponseWriter, r *http.Request) {
829+
f, err := ioutil.ReadFile("testdata/archive.tar.gz")
830+
if err != nil {
831+
t.Fatal(err)
832+
}
833+
w.Write(f)
834+
})
835+
836+
var server http.Server
837+
server.Handler = mux
838+
go server.Serve(ln)
839+
840+
return ln
841+
}
842+
773843
func testHttpServerWithXTerraformGetProxyBypass(t *testing.T) net.Listener {
774844
t.Helper()
775845

0 commit comments

Comments
 (0)