From 7949388da8afed082064df539076c7043acd2993 Mon Sep 17 00:00:00 2001
From: Abiola Ibrahim <abiola89@gmail.com>
Date: Mon, 16 Nov 2015 17:22:06 +0100
Subject: [PATCH] Proxy: Allow ignored subpaths.

---
 middleware/proxy/proxy.go         |  4 +++-
 middleware/proxy/proxy_test.go    |  4 ++++
 middleware/proxy/upstream.go      | 18 ++++++++++++++++++
 middleware/proxy/upstream_test.go | 30 ++++++++++++++++++++++++++++++
 4 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go
index 9b8fed21f..fcb00abea 100644
--- a/middleware/proxy/proxy.go
+++ b/middleware/proxy/proxy.go
@@ -26,6 +26,8 @@ type Upstream interface {
 	From() string
 	// Selects an upstream host to be routed to.
 	Select() *UpstreamHost
+	// Checks if subpath is not an ignored path
+	IsAllowedPath(string) bool
 }
 
 // UpstreamHostDownFunc can be used to customize how Down behaves.
@@ -59,7 +61,7 @@ func (uh *UpstreamHost) Down() bool {
 func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 
 	for _, upstream := range p.Upstreams {
-		if middleware.Path(r.URL.Path).Matches(upstream.From()) {
+		if middleware.Path(r.URL.Path).Matches(upstream.From()) && upstream.IsAllowedPath(r.URL.Path) {
 			var replacer middleware.Replacer
 			start := time.Now()
 			requestHost := r.Host
diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go
index 6fb6a899b..aab836bcc 100644
--- a/middleware/proxy/proxy_test.go
+++ b/middleware/proxy/proxy_test.go
@@ -125,6 +125,10 @@ func (u *fakeUpstream) Select() *UpstreamHost {
 	}
 }
 
+func (u *fakeUpstream) IsAllowedPath(requestPath string) bool {
+	return true
+}
+
 // recorderHijacker is a ResponseRecorder that can
 // be hijacked.
 type recorderHijacker struct {
diff --git a/middleware/proxy/upstream.go b/middleware/proxy/upstream.go
index f068907ef..7916b3b6c 100644
--- a/middleware/proxy/upstream.go
+++ b/middleware/proxy/upstream.go
@@ -5,11 +5,13 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/url"
+	"path"
 	"strconv"
 	"strings"
 	"time"
 
 	"github.com/mholt/caddy/caddy/parse"
+	"github.com/mholt/caddy/middleware"
 )
 
 var (
@@ -29,6 +31,7 @@ type staticUpstream struct {
 		Interval time.Duration
 	}
 	WithoutPathPrefix string
+	IgnoredSubPaths   []string
 }
 
 // NewStaticUpstreams parses the configuration input and sets up
@@ -165,6 +168,12 @@ func parseBlock(c *parse.Dispenser, u *staticUpstream) error {
 			return c.ArgErr()
 		}
 		u.WithoutPathPrefix = c.Val()
+	case "except":
+		ignoredPaths := c.RemainingArgs()
+		if len(ignoredPaths) == 0 {
+			return c.ArgErr()
+		}
+		u.IgnoredSubPaths = ignoredPaths
 	default:
 		return c.Errf("unknown property '%s'", c.Val())
 	}
@@ -223,3 +232,12 @@ func (u *staticUpstream) Select() *UpstreamHost {
 	}
 	return u.Policy.Select(pool)
 }
+
+func (u *staticUpstream) IsAllowedPath(requestPath string) bool {
+	for _, ignoredSubPath := range u.IgnoredSubPaths {
+		if middleware.Path(path.Clean(requestPath)).Matches(path.Join(u.From(), ignoredSubPath)) {
+			return false
+		}
+	}
+	return true
+}
diff --git a/middleware/proxy/upstream_test.go b/middleware/proxy/upstream_test.go
index f3df16136..5b2fdb1da 100644
--- a/middleware/proxy/upstream_test.go
+++ b/middleware/proxy/upstream_test.go
@@ -51,3 +51,33 @@ func TestRegisterPolicy(t *testing.T) {
 	}
 
 }
+
+func TestAllowedPaths(t *testing.T) {
+	upstream := &staticUpstream{
+		from:            "/proxy",
+		IgnoredSubPaths: []string{"/download", "/static"},
+	}
+	tests := []struct {
+		url      string
+		expected bool
+	}{
+		{"/proxy", true},
+		{"/proxy/dl", true},
+		{"/proxy/download", false},
+		{"/proxy/download/static", false},
+		{"/proxy/static", false},
+		{"/proxy/static/download", false},
+		{"/proxy/something/download", true},
+		{"/proxy/something/static", true},
+		{"/proxy//static", false},
+		{"/proxy//static//download", false},
+		{"/proxy//download", false},
+	}
+
+	for i, test := range tests {
+		isAllowed := upstream.IsAllowedPath(test.url)
+		if test.expected != isAllowed {
+			t.Errorf("Test %d: expected %v found %v", i+1, test.expected, isAllowed)
+		}
+	}
+}