From 0d7fe36007d5fbadaa6057b6543500d63147e6c0 Mon Sep 17 00:00:00 2001
From: Francis Lavoie <lavofr@gmail.com>
Date: Fri, 12 Mar 2021 15:25:49 -0500
Subject: [PATCH] httpcaddyfile: Add `error` directive for the existing handler
 (#4034)

* httpcaddyfile: Add `error` directive for the existing handler

* httpcaddyfile: Move `error` to the end of the order
---
 caddyconfig/httpcaddyfile/builtins.go   | 11 ++++++
 caddyconfig/httpcaddyfile/directives.go |  1 +
 modules/caddyhttp/staticerror.go        | 50 ++++++++++++++++++++++++-
 3 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 8830f5210..32f9da711 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -42,6 +42,7 @@ func init() {
 	RegisterHandlerDirective("redir", parseRedir)
 	RegisterHandlerDirective("respond", parseRespond)
 	RegisterHandlerDirective("abort", parseAbort)
+	RegisterHandlerDirective("error", parseError)
 	RegisterHandlerDirective("route", parseRoute)
 	RegisterHandlerDirective("handle", parseHandle)
 	RegisterDirective("handle_errors", parseHandleErrors)
@@ -566,6 +567,16 @@ func parseAbort(h Helper) (caddyhttp.MiddlewareHandler, error) {
 	return &caddyhttp.StaticResponse{Abort: true}, nil
 }
 
+// parseError parses the error directive.
+func parseError(h Helper) (caddyhttp.MiddlewareHandler, error) {
+	se := new(caddyhttp.StaticError)
+	err := se.UnmarshalCaddyfile(h.Dispenser)
+	if err != nil {
+		return nil, err
+	}
+	return se, nil
+}
+
 // parseRoute parses the route directive.
 func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
 	sr := new(caddyhttp.Subroute)
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index 7f77f49f2..b4a8407d6 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -70,6 +70,7 @@ var directiveOrder = []string{
 	"file_server",
 	"acme_server",
 	"abort",
+	"error",
 }
 
 // directiveIsOrdered returns true if dir is
diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go
index d2d255e78..914e6c148 100644
--- a/modules/caddyhttp/staticerror.go
+++ b/modules/caddyhttp/staticerror.go
@@ -20,6 +20,7 @@ import (
 	"strconv"
 
 	"github.com/caddyserver/caddy/v2"
+	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 )
 
 func init() {
@@ -50,6 +51,50 @@ func (StaticError) CaddyModule() caddy.ModuleInfo {
 	}
 }
 
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+//     error [<matcher>] <status>|<message> [<status>] {
+//         message <text>
+//     }
+//
+// If there is just one argument (other than the matcher), it is considered
+// to be a status code if it's a valid positive integer of 3 digits.
+func (e *StaticError) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+	for d.Next() {
+		args := d.RemainingArgs()
+		switch len(args) {
+		case 1:
+			if len(args[0]) == 3 {
+				if num, err := strconv.Atoi(args[0]); err == nil && num > 0 {
+					e.StatusCode = WeakString(args[0])
+					break
+				}
+			}
+			e.Error = args[0]
+		case 2:
+			e.Error = args[0]
+			e.StatusCode = WeakString(args[1])
+		default:
+			return d.ArgErr()
+		}
+
+		for d.NextBlock(0) {
+			switch d.Val() {
+			case "message":
+				if e.Error != "" {
+					return d.Err("message already specified")
+				}
+				if !d.AllArgs(&e.Error) {
+					return d.ArgErr()
+				}
+			default:
+				return d.Errf("unrecognized subdirective '%s'", d.Val())
+			}
+		}
+	}
+	return nil
+}
+
 func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
 
@@ -66,4 +111,7 @@ func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler
 }
 
 // Interface guard
-var _ MiddlewareHandler = (*StaticError)(nil)
+var (
+	_ MiddlewareHandler     = (*StaticError)(nil)
+	_ caddyfile.Unmarshaler = (*StaticError)(nil)
+)