From 82929b122ab1a2e98d17f403b0da26158c8aea72 Mon Sep 17 00:00:00 2001
From: Matthew Holt <mholt@users.noreply.github.com>
Date: Mon, 23 Jan 2017 22:06:29 -0700
Subject: [PATCH] Ensure active Caddyfile, if in site, is hidden no matter the
 cwd

---
 caddyhttp/browse/browse.go         |  9 +++++----
 caddyhttp/httpserver/plugin.go     | 32 +++++++++++++++++++++++++-----
 caddyhttp/httpserver/siteconfig.go |  3 +++
 caddyhttp/root/root.go             |  4 ++++
 caddyhttp/root/root_test.go        |  5 ++++-
 5 files changed, 43 insertions(+), 10 deletions(-)

diff --git a/caddyhttp/browse/browse.go b/caddyhttp/browse/browse.go
index ce5b58af0..36064c1d7 100644
--- a/caddyhttp/browse/browse.go
+++ b/caddyhttp/browse/browse.go
@@ -245,16 +245,17 @@ func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config
 // ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
 // If so, control is handed over to ServeListing.
 func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
-	var bc *Config
 	// See if there's a browse configuration to match the path
+	var bc *Config
 	for i := range b.Configs {
 		if httpserver.Path(r.URL.Path).Matches(b.Configs[i].PathScope) {
 			bc = &b.Configs[i]
-			goto inScope
+			break
 		}
 	}
-	return b.Next.ServeHTTP(w, r)
-inScope:
+	if bc == nil {
+		return b.Next.ServeHTTP(w, r)
+	}
 
 	// Browse works on existing directories; delegate everything else
 	requestedFilepath, err := bc.Fs.Root.Open(r.URL.Path)
diff --git a/caddyhttp/httpserver/plugin.go b/caddyhttp/httpserver/plugin.go
index b1fcb9773..d9ad8ab89 100644
--- a/caddyhttp/httpserver/plugin.go
+++ b/caddyhttp/httpserver/plugin.go
@@ -7,6 +7,7 @@ import (
 	"net"
 	"net/url"
 	"os"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -21,7 +22,7 @@ func init() {
 	flag.StringVar(&Host, "host", DefaultHost, "Default host")
 	flag.StringVar(&Port, "port", DefaultPort, "Default port")
 	flag.StringVar(&Root, "root", DefaultRoot, "Root path of default site")
-	flag.DurationVar(&GracefulTimeout, "grace", 5*time.Second, "Maximum duration of graceful shutdown") // TODO
+	flag.DurationVar(&GracefulTimeout, "grace", 5*time.Second, "Maximum duration of graceful shutdown")
 	flag.BoolVar(&HTTP2, "http2", true, "Use HTTP/2")
 	flag.BoolVar(&QUIC, "quic", false, "Use experimental QUIC")
 
@@ -44,10 +45,31 @@ func init() {
 		NewContext: newContext,
 	})
 	caddy.RegisterCaddyfileLoader("short", caddy.LoaderFunc(shortCaddyfileLoader))
+	caddy.RegisterParsingCallback(serverType, "root", hideCaddyfile)
 	caddy.RegisterParsingCallback(serverType, "tls", activateHTTPS)
 	caddytls.RegisterConfigGetter(serverType, func(c *caddy.Controller) *caddytls.Config { return GetConfig(c).TLS })
 }
 
+// hideCaddyfile hides the source/origin Caddyfile if it is within the
+// site root. This function should be run after parsing the root directive.
+func hideCaddyfile(cctx caddy.Context) error {
+	ctx := cctx.(*httpContext)
+	for _, cfg := range ctx.siteConfigs {
+		absRoot, err := filepath.Abs(cfg.Root)
+		if err != nil {
+			return err
+		}
+		absOriginCaddyfile, err := filepath.Abs(cfg.originCaddyfile)
+		if err != nil {
+			return err
+		}
+		if strings.HasPrefix(absOriginCaddyfile, absRoot) {
+			cfg.HiddenFiles = append(cfg.HiddenFiles, strings.TrimPrefix(absOriginCaddyfile, absRoot))
+		}
+	}
+	return nil
+}
+
 func newContext() caddy.Context {
 	return &httpContext{keysToSiteConfigs: make(map[string]*SiteConfig)}
 }
@@ -95,10 +117,10 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
 
 			// Save the config to our master list, and key it for lookups
 			cfg := &SiteConfig{
-				Addr:        addr,
-				Root:        Root,
-				TLS:         &caddytls.Config{Hostname: addr.Host},
-				HiddenFiles: []string{sourceFile},
+				Addr:            addr,
+				Root:            Root,
+				TLS:             &caddytls.Config{Hostname: addr.Host},
+				originCaddyfile: sourceFile,
 			}
 			h.saveConfig(key, cfg)
 		}
diff --git a/caddyhttp/httpserver/siteconfig.go b/caddyhttp/httpserver/siteconfig.go
index 2980d3770..e5e18b50b 100644
--- a/caddyhttp/httpserver/siteconfig.go
+++ b/caddyhttp/httpserver/siteconfig.go
@@ -33,6 +33,9 @@ type SiteConfig struct {
 
 	// Max amount of bytes a request can send on a given path
 	MaxRequestBodySizes []PathLimit
+
+	// The path to the Caddyfile used to generate this site config
+	originCaddyfile string
 }
 
 // PathLimit is a mapping from a site's path to its corresponding
diff --git a/caddyhttp/root/root.go b/caddyhttp/root/root.go
index 50ada1103..b3dded1c6 100644
--- a/caddyhttp/root/root.go
+++ b/caddyhttp/root/root.go
@@ -23,6 +23,10 @@ func setupRoot(c *caddy.Controller) error {
 			return c.ArgErr()
 		}
 		config.Root = c.Val()
+		if c.NextArg() {
+			// only one argument allowed
+			return c.ArgErr()
+		}
 	}
 
 	// Check if root path exists
diff --git a/caddyhttp/root/root_test.go b/caddyhttp/root/root_test.go
index 471d9a24a..daeb9eea0 100644
--- a/caddyhttp/root/root_test.go
+++ b/caddyhttp/root/root_test.go
@@ -52,6 +52,9 @@ func TestRoot(t *testing.T) {
 		{
 			`root `, true, "", parseErrContent,
 		},
+		{
+			`root /a /b`, true, "", parseErrContent,
+		},
 		{
 			fmt.Sprintf(`root %s`, inaccessiblePath), true, "", unableToAccessErrContent,
 		},
@@ -68,7 +71,7 @@ func TestRoot(t *testing.T) {
 		cfg := httpserver.GetConfig(c)
 
 		if test.shouldErr && err == nil {
-			t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
+			t.Errorf("Test %d: Expected error but got nil for input '%s'", i, test.input)
 		}
 
 		if err != nil {