From b1cd0bfeffe0c585557fd1e0c26c0f092396723e Mon Sep 17 00:00:00 2001
From: Abiola Ibrahim <abiola89@gmail.com>
Date: Wed, 29 Jun 2016 13:41:52 +0100
Subject: [PATCH] Support for placeholders in fastcgi env vars.

---
 caddyhttp/fastcgi/fastcgi.go          |  4 +-
 caddyhttp/fastcgi/fastcgi_test.go     | 77 ++++++++++++++++++++-------
 caddyhttp/httpserver/replacer.go      |  5 ++
 caddyhttp/httpserver/replacer_test.go |  2 +-
 4 files changed, 67 insertions(+), 21 deletions(-)

diff --git a/caddyhttp/fastcgi/fastcgi.go b/caddyhttp/fastcgi/fastcgi.go
index 0ac886b6d..30426b33b 100644
--- a/caddyhttp/fastcgi/fastcgi.go
+++ b/caddyhttp/fastcgi/fastcgi.go
@@ -254,9 +254,11 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]
 		env["HTTPS"] = "on"
 	}
 
+	replacer := httpserver.NewReplacer(r, nil, "")
 	// Add env variables from config
 	for _, envVar := range rule.EnvVars {
-		env[envVar[0]] = envVar[1]
+		// replace request placeholders in environment variables
+		env[envVar[0]] = replacer.Replace(envVar[1])
 	}
 
 	// Add all HTTP headers to env variables
diff --git a/caddyhttp/fastcgi/fastcgi_test.go b/caddyhttp/fastcgi/fastcgi_test.go
index e1e394919..8b813c246 100644
--- a/caddyhttp/fastcgi/fastcgi_test.go
+++ b/caddyhttp/fastcgi/fastcgi_test.go
@@ -123,38 +123,77 @@ func TestBuildEnv(t *testing.T) {
 		t.Error("Unexpected error:", err.Error())
 	}
 
-	r := http.Request{
-		Method:     "GET",
-		URL:        url,
-		Proto:      "HTTP/1.1",
-		ProtoMajor: 1,
-		ProtoMinor: 1,
-		Host:       "localhost:2015",
-		RemoteAddr: "[2b02:1810:4f2d:9400:70ab:f822:be8a:9093]:51688",
-		RequestURI: "/fgci_test.php",
+	var newReq = func() *http.Request {
+		return &http.Request{
+			Method:     "GET",
+			URL:        url,
+			Proto:      "HTTP/1.1",
+			ProtoMajor: 1,
+			ProtoMinor: 1,
+			Host:       "localhost:2015",
+			RemoteAddr: "[2b02:1810:4f2d:9400:70ab:f822:be8a:9093]:51688",
+			RequestURI: "/fgci_test.php",
+		}
 	}
 
 	fpath := "/fgci_test.php"
 
-	var envExpected = map[string]string{
-		"REMOTE_ADDR":     "2b02:1810:4f2d:9400:70ab:f822:be8a:9093",
-		"REMOTE_PORT":     "51688",
-		"SERVER_PROTOCOL": "HTTP/1.1",
-		"QUERY_STRING":    "test=blabla",
-		"REQUEST_METHOD":  "GET",
-		"HTTP_HOST":       "localhost:2015",
+	var newEnv = func() map[string]string {
+		return map[string]string{
+			"REMOTE_ADDR":     "2b02:1810:4f2d:9400:70ab:f822:be8a:9093",
+			"REMOTE_PORT":     "51688",
+			"SERVER_PROTOCOL": "HTTP/1.1",
+			"QUERY_STRING":    "test=blabla",
+			"REQUEST_METHOD":  "GET",
+			"HTTP_HOST":       "localhost:2015",
+		}
 	}
 
+	// request
+	var r *http.Request
+
+	// expected environment variables
+	var envExpected map[string]string
+
 	// 1. Test for full canonical IPv6 address
-	testBuildEnv(&r, rule, fpath, envExpected)
+	r = newReq()
+	testBuildEnv(r, rule, fpath, envExpected)
 
 	// 2. Test for shorthand notation of IPv6 address
+	r = newReq()
 	r.RemoteAddr = "[::1]:51688"
+	envExpected = newEnv()
 	envExpected["REMOTE_ADDR"] = "::1"
-	testBuildEnv(&r, rule, fpath, envExpected)
+	testBuildEnv(r, rule, fpath, envExpected)
 
 	// 3. Test for IPv4 address
+	r = newReq()
 	r.RemoteAddr = "192.168.0.10:51688"
+	envExpected = newEnv()
 	envExpected["REMOTE_ADDR"] = "192.168.0.10"
-	testBuildEnv(&r, rule, fpath, envExpected)
+	testBuildEnv(r, rule, fpath, envExpected)
+
+	// 4. Test for environment variable
+	r = newReq()
+	rule.EnvVars = [][2]string{
+		{"HTTP_HOST", "localhost:2016"},
+		{"REQUEST_METHOD", "POST"},
+	}
+	envExpected = newEnv()
+	envExpected["HTTP_HOST"] = "localhost:2016"
+	envExpected["REQUEST_METHOD"] = "POST"
+	testBuildEnv(r, rule, fpath, envExpected)
+
+	// 5. Test for environment variable placeholders
+	r = newReq()
+	rule.EnvVars = [][2]string{
+		{"HTTP_HOST", "{host}"},
+		{"CUSTOM_URI", "custom_uri{uri}"},
+		{"CUSTOM_QUERY", "custom=true&{query}"},
+	}
+	envExpected = newEnv()
+	envExpected["HTTP_HOST"] = "localhost:2015"
+	envExpected["CUSTOM_URI"] = "custom_uri/fgci_test.php?test=blabla"
+	envExpected["CUSTOM_QUERY"] = "custom=true&test=blabla"
+	testBuildEnv(r, rule, fpath, envExpected)
 }
diff --git a/caddyhttp/httpserver/replacer.go b/caddyhttp/httpserver/replacer.go
index cae5870f5..64cd18013 100644
--- a/caddyhttp/httpserver/replacer.go
+++ b/caddyhttp/httpserver/replacer.go
@@ -127,6 +127,11 @@ func NewReplacer(r *http.Request, rr *ResponseRecorder, emptyValue string) Repla
 // Replace performs a replacement of values on s and returns
 // the string with the replaced values.
 func (r *replacer) Replace(s string) string {
+	// Do not attempt replacements if no placeholder is found.
+	if !strings.ContainsAny(s, "{}") {
+		return s
+	}
+
 	// Make response placeholders now
 	if r.responseRecorder != nil {
 		r.replacements["{status}"] = strconv.Itoa(r.responseRecorder.status)
diff --git a/caddyhttp/httpserver/replacer_test.go b/caddyhttp/httpserver/replacer_test.go
index 3b80af437..5768c42fb 100644
--- a/caddyhttp/httpserver/replacer_test.go
+++ b/caddyhttp/httpserver/replacer_test.go
@@ -32,7 +32,7 @@ func TestNewReplacer(t *testing.T) {
 		if got, want := v.replacements["{status}"], ""; got != want {
 			t.Errorf("Expected status to NOT be set before Replace() is called; was: %s", got)
 		}
-		rep.Replace("foobar")
+		rep.Replace("{foobar}")
 		if got, want := v.replacements["{status}"], "200"; got != want {
 			t.Errorf("Expected status to be %s, was: %s", want, got)
 		}