From 490cd02f826b00e52b6503b61436b8c7444fc30e Mon Sep 17 00:00:00 2001
From: Matthew Holt <mholt@users.noreply.github.com>
Date: Tue, 4 Feb 2020 13:04:34 -0700
Subject: [PATCH] httpcaddyfile: Make root directive mutually exclusive

See https://caddy.community/t/caddyfile-and-v2/6766/22?u=matt
---
 caddyconfig/httpcaddyfile/httptype.go | 74 +++++++++++++--------------
 1 file changed, 35 insertions(+), 39 deletions(-)

diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index 22050f1e4..88ca7e473 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -448,38 +448,13 @@ func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subro
 
 	subroute := new(caddyhttp.Subroute)
 
-	// get a group name for rewrite directives, if needed
-	var rewriteGroupName string
-	var rewriteCount int
-	for _, r := range routes {
-		if r.directive == "rewrite" {
-			rewriteCount++
-			if rewriteCount > 1 {
-				break
-			}
-		}
-	}
-	if rewriteCount > 1 {
-		rewriteGroupName = groupCounter.nextGroup()
-	}
-
-	// get a group name for handle blocks, if needed
-	var handleGroupName string
-	var handleCount int
-	for _, r := range routes {
-		if r.directive == "handle" {
-			handleCount++
-			if handleCount > 1 {
-				break
-			}
-		}
-	}
-	if handleCount > 1 {
-		handleGroupName = groupCounter.nextGroup()
-	}
-
-	// add all the routes piled in from directives
-	for _, r := range routes {
+	// some directives are mutually exclusive (only first matching
+	// instance should be evaluated); this is done by putting their
+	// routes in the same group
+	mutuallyExclusiveDirs := map[string]*struct {
+		count     int
+		groupName string
+	}{
 		// as a special case, group rewrite directives so that they are mutually exclusive;
 		// this means that only the first matching rewrite will be evaluated, and that's
 		// probably a good thing, since there should never be a need to do more than one
@@ -489,16 +464,37 @@ func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subro
 		// (We use this on the Caddy website, or at least we did once.) The first rewrite's
 		// result is also matched by the second rewrite, making the first rewrite pointless.
 		// See issue #2959.
-		if r.directive == "rewrite" {
-			route := r.Value.(caddyhttp.Route)
-			route.Group = rewriteGroupName
-			r.Value = route
-		}
+		"rewrite": {},
 
 		// handle blocks are also mutually exclusive by definition
-		if r.directive == "handle" {
+		"handle": {},
+
+		// root just sets a variable, so if it was not mutually exclusive, intersecting
+		// root directives would overwrite previously-matched ones; they should not cascade
+		"root": {},
+	}
+	for meDir, info := range mutuallyExclusiveDirs {
+		// see how many instances of the directive there are
+		for _, r := range routes {
+			if r.directive == meDir {
+				info.count++
+				if info.count > 1 {
+					break
+				}
+			}
+		}
+		// if there is more than one, put them in a group
+		if info.count > 1 {
+			info.groupName = groupCounter.nextGroup()
+		}
+	}
+
+	// add all the routes piled in from directives
+	for _, r := range routes {
+		// put this route into a group if it is mutually exclusive
+		if info, ok := mutuallyExclusiveDirs[r.directive]; ok {
 			route := r.Value.(caddyhttp.Route)
-			route.Group = handleGroupName
+			route.Group = info.groupName
 			r.Value = route
 		}