@@ -11,8 +11,13 @@ class RequestInterface < Interface
1111 "HTTP_X_FORWARDED_FOR"
1212 ] . freeze
1313
14- # Regex to detect lowercase chars — match? is allocation-free (no MatchData/String)
15- LOWERCASE_PATTERN = /[a-z]/ . freeze
14+ # Cache for Rack env key → HTTP header name transformations
15+ # e.g. "HTTP_ACCEPT_LANGUAGE" → "Accept-Language", "CONTENT_TYPE" → "Content-Type"
16+ @header_name_cache = { }
17+
18+ class << self
19+ attr_reader :header_name_cache
20+ end
1621
1722 # See Sentry server default limits at
1823 # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
@@ -45,15 +50,6 @@ class RequestInterface < Interface
4550 # @see Configuration#send_default_pii
4651 # @see Configuration#rack_env_whitelist
4752 def initialize ( env :, send_default_pii :, rack_env_whitelist :)
48- env = env . dup
49-
50- unless send_default_pii
51- # need to completely wipe out ip addresses
52- RequestInterface ::IP_HEADERS . each do |header |
53- env . delete ( header )
54- end
55- end
56-
5753 request = ::Rack ::Request . new ( env )
5854
5955 if send_default_pii
@@ -66,7 +62,7 @@ def initialize(env:, send_default_pii:, rack_env_whitelist:)
6662 self . method = request . request_method
6763
6864 self . headers = filter_and_format_headers ( env , send_default_pii )
69- self . env = filter_and_format_env ( env , rack_env_whitelist )
65+ self . env = filter_and_format_env ( env , rack_env_whitelist , send_default_pii )
7066 end
7167
7268 private
@@ -94,12 +90,22 @@ def filter_and_format_headers(env, send_default_pii)
9490 next if is_server_protocol? ( key , value , env [ "SERVER_PROTOCOL" ] )
9591 next if is_skippable_header? ( key )
9692 next if key == "HTTP_AUTHORIZATION" && !send_default_pii
93+ # Filter IP headers inline instead of env.dup + delete
94+ next if !send_default_pii && IP_HEADERS . include? ( key )
9795
9896 # Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
99- key = key . delete_prefix ( "HTTP_" )
100- key = key . split ( "_" ) . map ( &:capitalize ) . join ( "-" )
101-
102- memo [ key ] = Utils ::EncodingHelper . encode_to_utf_8 ( value . to_s )
97+ key = self . class . header_name_cache [ key ] ||= begin
98+ k = key . delete_prefix ( "HTTP_" )
99+ k . split ( "_" ) . map ( &:capitalize ) . join ( "-" ) . freeze
100+ end
101+
102+ # Fast path: ASCII strings are valid UTF-8, skip dup+force_encoding
103+ str = value . to_s
104+ memo [ key ] = if str . ascii_only?
105+ str
106+ else
107+ Utils ::EncodingHelper . encode_to_utf_8 ( str )
108+ end
103109 rescue StandardError => e
104110 # Rails adds objects to the Rack env that can sometimes raise exceptions
105111 # when `to_s` is called.
@@ -110,6 +116,9 @@ def filter_and_format_headers(env, send_default_pii)
110116 end
111117 end
112118
119+ # Regex to detect lowercase chars — match? is allocation-free (no MatchData/String)
120+ LOWERCASE_PATTERN = /[a-z]/ . freeze
121+
113122 def is_skippable_header? ( key )
114123 key . match? ( LOWERCASE_PATTERN ) || # lower-case envs aren't real http headers
115124 key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
@@ -134,11 +143,13 @@ def self.rack_3_or_above?
134143 Gem ::Version . new ( ::Rack . release ) >= Gem ::Version . new ( "3.0" )
135144 end
136145
137- def filter_and_format_env ( env , rack_env_whitelist )
146+ def filter_and_format_env ( env , rack_env_whitelist , send_default_pii )
138147 return env if rack_env_whitelist . empty?
139148
140149 env . select do |k , _v |
141- rack_env_whitelist . include? k . to_s
150+ key = k . to_s
151+ next false if !send_default_pii && IP_HEADERS . include? ( key )
152+ rack_env_whitelist . include? ( key )
142153 end
143154 end
144155 end
0 commit comments