From 6c847d07237032972dfd9a10e31fd5a733bbc789 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gustavo=20Cha=C3=ADn?= <gchain@gmail.com>
Date: Tue, 7 Jun 2016 19:06:24 +0200
Subject: [PATCH] New {request} placeholder to log entire requests (sans body)
 (#871)

Add a {request} placeholder to the replacer.

Closes #858.
---
 caddyhttp/httpserver/replacer.go      | 17 +++++++++++++++++
 caddyhttp/httpserver/replacer_test.go |  3 +++
 2 files changed, 20 insertions(+)

diff --git a/caddyhttp/httpserver/replacer.go b/caddyhttp/httpserver/replacer.go
index e0299b8bf..cae5870f5 100644
--- a/caddyhttp/httpserver/replacer.go
+++ b/caddyhttp/httpserver/replacer.go
@@ -3,6 +3,7 @@ package httpserver
 import (
 	"net"
 	"net/http"
+	"net/http/httputil"
 	"net/url"
 	"os"
 	"path"
@@ -11,6 +12,14 @@ import (
 	"time"
 )
 
+// requestReplacer is a strings.Replacer which is used to
+// encode literal \r and \n characters and keep everything
+// on one line
+var requestReplacer = strings.NewReplacer(
+	"\r", "\\r",
+	"\n", "\\n",
+)
+
 // Replacer is a type which can replace placeholder
 // substrings in a string with actual values from a
 // http.Request and ResponseRecorder. Always use
@@ -95,6 +104,14 @@ func NewReplacer(r *http.Request, rr *ResponseRecorder, emptyValue string) Repla
 				dir, _ := path.Split(r.URL.Path)
 				return dir
 			}(),
+			"{request}": func() string {
+				dump, err := httputil.DumpRequest(r, false)
+				if err != nil {
+					return ""
+				}
+
+				return requestReplacer.Replace(string(dump))
+			}(),
 		},
 		emptyValue: emptyValue,
 	}
diff --git a/caddyhttp/httpserver/replacer_test.go b/caddyhttp/httpserver/replacer_test.go
index 466e06239..b6f2f458a 100644
--- a/caddyhttp/httpserver/replacer_test.go
+++ b/caddyhttp/httpserver/replacer_test.go
@@ -74,6 +74,9 @@ func TestReplace(t *testing.T) {
 	if expected, actual := "The Custom header is foobarbaz.", repl.Replace("The Custom header is {>Custom}."); expected != actual {
 		t.Errorf("{>Custom} replacement: expected '%s', got '%s'", expected, actual)
 	}
+	if expected, actual := "The request is POST / HTTP/1.1\\r\\nHost: localhost\\r\\nCustom: foobarbaz\\r\\nShorterval: 1\\r\\n\\r\\n.", repl.Replace("The request is {request}."); expected != actual {
+		t.Errorf("{request} replacement: expected '%s', got '%s'", expected, actual)
+	}
 
 	// Test header case-insensitivity
 	if expected, actual := "The cUsToM header is foobarbaz...", repl.Replace("The cUsToM header is {>cUsToM}..."); expected != actual {