@@ -11,6 +11,14 @@ class RequestInterface < Interface
1111 "HTTP_X_FORWARDED_FOR"
1212 ] . freeze
1313
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
21+
1422 # See Sentry server default limits at
1523 # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
1624 MAX_BODY_LIMIT = 4096 * 4
@@ -42,15 +50,6 @@ class RequestInterface < Interface
4250 # @see Configuration#send_default_pii
4351 # @see Configuration#rack_env_whitelist
4452 def initialize ( env :, send_default_pii :, rack_env_whitelist :)
45- env = env . dup
46-
47- unless send_default_pii
48- # need to completely wipe out ip addresses
49- RequestInterface ::IP_HEADERS . each do |header |
50- env . delete ( header )
51- end
52- end
53-
5453 request = ::Rack ::Request . new ( env )
5554
5655 if send_default_pii
@@ -63,7 +62,7 @@ def initialize(env:, send_default_pii:, rack_env_whitelist:)
6362 self . method = request . request_method
6463
6564 self . headers = filter_and_format_headers ( env , send_default_pii )
66- self . env = filter_and_format_env ( env , rack_env_whitelist )
65+ self . env = filter_and_format_env ( env , rack_env_whitelist , send_default_pii )
6766 end
6867
6968 private
@@ -91,12 +90,22 @@ def filter_and_format_headers(env, send_default_pii)
9190 next if is_server_protocol? ( key , value , env [ "SERVER_PROTOCOL" ] )
9291 next if is_skippable_header? ( key )
9392 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 )
9495
9596 # Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
96- key = key . sub ( /^HTTP_/ , "" )
97- key = key . split ( "_" ) . map ( &:capitalize ) . join ( "-" )
98-
99- 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
100109 rescue StandardError => e
101110 # Rails adds objects to the Rack env that can sometimes raise exceptions
102111 # when `to_s` is called.
@@ -107,8 +116,11 @@ def filter_and_format_headers(env, send_default_pii)
107116 end
108117 end
109118
119+ # Regex to detect lowercase chars — match? is allocation-free (no MatchData/String)
120+ LOWERCASE_PATTERN = /[a-z]/ . freeze
121+
110122 def is_skippable_header? ( key )
111- key . upcase != key || # lower-case envs aren't real http headers
123+ key . match? ( LOWERCASE_PATTERN ) || # lower-case envs aren't real http headers
112124 key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
113125 !( key . start_with? ( "HTTP_" ) || CONTENT_HEADERS . include? ( key ) )
114126 end
@@ -119,17 +131,25 @@ def is_skippable_header?(key)
119131 # if the request has legitimately sent a Version header themselves.
120132 # See: https://github.com/rack/rack/blob/028438f/lib/rack/handler/cgi.rb#L29
121133 def is_server_protocol? ( key , value , protocol_version )
122- rack_version = Gem ::Version . new ( ::Rack . release )
123- return false if rack_version >= Gem ::Version . new ( "3.0" )
134+ return false if self . class . rack_3_or_above?
124135
125136 key == "HTTP_VERSION" && value == protocol_version
126137 end
127138
128- def filter_and_format_env ( env , rack_env_whitelist )
139+ def self . rack_3_or_above?
140+ return @rack_3_or_above if defined? ( @rack_3_or_above )
141+
142+ @rack_3_or_above = defined? ( ::Rack ) &&
143+ Gem ::Version . new ( ::Rack . release ) >= Gem ::Version . new ( "3.0" )
144+ end
145+
146+ def filter_and_format_env ( env , rack_env_whitelist , send_default_pii )
129147 return env if rack_env_whitelist . empty?
130148
131149 env . select do |k , _v |
132- 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 )
133153 end
134154 end
135155 end
0 commit comments