From 2ec459b31292617bd685d72f1941fff04dfbe3c0 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@jeremyevans.net>
Date: Tue, 11 Feb 2025 19:10:05 -0800
Subject: Escape non-printable characters when logging.

---
 lib/rack/common_logger.rb  | 5 +++--
 test/spec_common_logger.rb | 7 ++++++-
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/lib/rack/common_logger.rb b/lib/rack/common_logger.rb
index 539da419..f9de1d70 100644
--- a/lib/rack/common_logger.rb
+++ b/lib/rack/common_logger.rb
@@ -23,7 +23,7 @@ module Rack
     #   lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
     #
     #   %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
-    FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
+    FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f }
 
     def initialize(app, logger = nil)
       @app = app
@@ -55,7 +55,8 @@ module Rack
         length,
         Utils.clock_time - began_at ]
 
-      msg.gsub!(/[^[:print:]\n]/) { |c| "\\x#{c.ord}" }
+      msg.gsub!(/[^[:print:]]/) { |c| sprintf("\\x%x", c.ord) }
+      msg[-1] = "\n"
 
       logger = @logger || env[RACK_ERRORS]
 
diff --git a/test/spec_common_logger.rb b/test/spec_common_logger.rb
index 21d321a6..2af90070 100644
--- a/test/spec_common_logger.rb
+++ b/test/spec_common_logger.rb
@@ -91,12 +91,17 @@ describe Rack::CommonLogger do
     (0..1).must_include duration.to_f
   end
 
-  it "escapes non printable characters except newline" do
+  it "escapes non printable characters including newline" do
     logdev = StringIO.new
     log = Logger.new(logdev)
     Rack::MockRequest.new(Rack::CommonLogger.new(app_without_lint, log)).request("GET\b", "/hello")
 
     logdev.string.must_match(/GET\\x8 \/hello/)
+
+    Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/", 'REMOTE_USER' => "foo\nbar", "QUERY_STRING" => "bar\nbaz")
+    logdev.string[-1].must_equal "\n"
+    logdev.string.must_include("foo\\xabar")
+    logdev.string.must_include("bar\\xabaz")
   end
 
   def length
-- 
2.30.2

