From b1a456cfe38e83eedb778032ed6739e7e3cbe8a4 Mon Sep 17 00:00:00 2001
From: Matthew Holt <mholt@users.noreply.github.com>
Date: Thu, 12 Dec 2019 15:46:13 -0700
Subject: [PATCH] rewrite: strip_prefix, strip_suffix, and uri_replace dirs
 (closes #2906)

---
 caddyconfig/httpcaddyfile/directives.go |  3 +
 modules/caddyhttp/rewrite/caddyfile.go  | 95 ++++++++++++++++++++++++-
 2 files changed, 95 insertions(+), 3 deletions(-)

diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index 5ec499301..8f30db249 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -27,6 +27,9 @@ import (
 // to apply directives in HTTP routes.
 var defaultDirectiveOrder = []string{
 	"rewrite",
+	"strip_prefix",
+	"strip_suffix",
+	"uri_replace",
 	"try_files",
 	"basicauth",
 	"headers",
diff --git a/modules/caddyhttp/rewrite/caddyfile.go b/modules/caddyhttp/rewrite/caddyfile.go
index a1fc87400..667431363 100644
--- a/modules/caddyhttp/rewrite/caddyfile.go
+++ b/modules/caddyhttp/rewrite/caddyfile.go
@@ -15,20 +15,25 @@
 package rewrite
 
 import (
+	"strconv"
+
 	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
 	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
 )
 
 func init() {
-	httpcaddyfile.RegisterHandlerDirective("rewrite", parseCaddyfile)
+	httpcaddyfile.RegisterHandlerDirective("rewrite", parseCaddyfileRewrite)
+	httpcaddyfile.RegisterHandlerDirective("strip_prefix", parseCaddyfileStripPrefix)
+	httpcaddyfile.RegisterHandlerDirective("strip_suffix", parseCaddyfileStripSuffix)
+	httpcaddyfile.RegisterHandlerDirective("uri_replace", parseCaddyfileURIReplace)
 }
 
-// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+// parseCaddyfileRewrite sets up a basic rewrite handler from Caddyfile tokens. Syntax:
 //
 //     rewrite [<matcher>] <to>
 //
 // The <to> parameter becomes the new URI.
-func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+func parseCaddyfileRewrite(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
 	var rewr Rewrite
 	for h.Next() {
 		if !h.NextArg() {
@@ -42,3 +47,87 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
 	rewr.Rehandle = true
 	return rewr, nil
 }
+
+// parseCaddyfileStripPrefix sets up a handler from Caddyfile tokens. Syntax:
+//
+//     strip_prefix [<matcher>] <prefix>
+//
+// The request path will be stripped its prefix if it matches <prefix>.
+func parseCaddyfileStripPrefix(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+	var rewr Rewrite
+	for h.Next() {
+		if !h.NextArg() {
+			return nil, h.ArgErr()
+		}
+		rewr.StripPathPrefix = h.Val()
+		if h.NextArg() {
+			return nil, h.ArgErr()
+		}
+	}
+	return rewr, nil
+}
+
+// parseCaddyfileStripSuffix sets up a handler from Caddyfile tokens. Syntax:
+//
+//     strip_suffix [<matcher>] <suffix>
+//
+// The request path will be stripped its suffix if it matches <suffix>.
+func parseCaddyfileStripSuffix(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+	var rewr Rewrite
+	for h.Next() {
+		if !h.NextArg() {
+			return nil, h.ArgErr()
+		}
+		rewr.StripPathSuffix = h.Val()
+		if h.NextArg() {
+			return nil, h.ArgErr()
+		}
+	}
+	return rewr, nil
+}
+
+// parseCaddyfileURIReplace sets up a handler from Caddyfile tokens. Syntax:
+//
+//     uri_replace [<matcher>] <find> <replace> [<limit>]
+//
+// Substring replacements will be performed on the request URI up to the
+// number specified by limit, if any (default = 0, or no limit).
+func parseCaddyfileURIReplace(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+	var rewr Rewrite
+
+	var repls []replacer
+
+	for h.Next() {
+		args := h.RemainingArgs()
+		var find, replace, lim string
+		switch len(args) {
+		case 3:
+			lim = args[2]
+			fallthrough
+		case 2:
+			find = args[0]
+			replace = args[1]
+		default:
+			return nil, h.ArgErr()
+		}
+
+		var limInt int
+		if lim != "" {
+			var err error
+			limInt, err = strconv.Atoi(lim)
+			if err != nil {
+				return nil, h.Errf("limit must be an integer; invalid: %v", err)
+			}
+		}
+
+		repls = append(repls, replacer{
+			Find:    find,
+			Replace: replace,
+			Limit:   limInt,
+		})
+	}
+
+	rewr.URISubstring = repls
+
+	return rewr, nil
+}