@@ -265,150 +265,167 @@ impl ProxyApp {
265265 }
266266
267267 // Regex-based HTTP request line parser and rewriter
268- fn rewrite_http_request ( & self , buffer : & mut [ u8 ] , length : usize ) -> ( usize , bool ) {
269- // First convert the buffer to a string for processing
270- let request_str = match std:: str:: from_utf8 ( & buffer[ ..length] ) {
271- Ok ( s) => s,
272- Err ( _) => return ( length, false ) , // Not valid UTF-8, return unchanged
273- } ;
268+ fn rewrite_http_request ( & self , buffer : & mut [ u8 ] , length : usize ) -> ( usize , bool ) {
269+ // First convert the buffer to a string for processing
270+ let request_str = match std:: str:: from_utf8 ( & buffer[ ..length] ) {
271+ Ok ( s) => s,
272+ Err ( _) => return ( length, false ) , // Not valid UTF-8, return unchanged
273+ } ;
274+
275+ // Flag to track if this is a WebSocket upgrade request
276+ let is_websocket = request_str. contains ( "Upgrade: websocket" )
277+ || request_str. contains ( "Upgrade: WebSocket" ) ;
278+
279+ // Check if this looks like an HTTP request
280+ if !request_str. starts_with ( "GET " )
281+ && !request_str. starts_with ( "POST " )
282+ && !request_str. starts_with ( "PUT " )
283+ && !request_str. starts_with ( "DELETE " )
284+ && !request_str. starts_with ( "CONNECT " )
285+ && !request_str. starts_with ( "OPTIONS " )
286+ {
287+ return ( length, is_websocket) ;
288+ }
274289
275- // Flag to track if this is a WebSocket upgrade request
276- let is_websocket = request_str. contains ( "Upgrade: websocket" )
277- || request_str. contains ( "Upgrade: WebSocket" ) ;
278-
279- // Check if this looks like an HTTP request
280- if !request_str. starts_with ( "GET " )
281- && !request_str. starts_with ( "POST " )
282- && !request_str. starts_with ( "PUT " )
283- && !request_str. starts_with ( "DELETE " )
284- && !request_str. starts_with ( "CONNECT " )
285- && !request_str. starts_with ( "OPTIONS " )
286- {
287- return ( length, is_websocket) ;
290+ // First check for configuration changes at regular intervals
291+ self . check_and_reload_config_if_needed ( ) ;
292+
293+ // Check if we already have this request in the cache
294+ if let Some ( cached_request) = self . rewrite_cache . get ( & request_str. to_string ( ) ) {
295+ debug ! ( "Cache hit for request rewrite" ) ;
296+ let new_bytes = cached_request. as_bytes ( ) ;
297+ let new_len = new_bytes. len ( ) ;
298+
299+ // Make sure we don't overflow the buffer
300+ if new_len <= buffer. len ( ) {
301+ buffer[ ..new_len] . copy_from_slice ( new_bytes) ;
302+ return ( new_len, is_websocket) ;
303+ } else {
304+ debug ! ( "Cached rewritten request too large for buffer" ) ;
305+ return ( length, false ) ;
288306 }
307+ }
289308
290- // First check for configuration changes at regular intervals
291- self . check_and_reload_config_if_needed ( ) ;
309+ debug ! ( "Cache miss for request rewrite" ) ;
292310
293- // Check if we already have this request in the cache
294- if let Some ( cached_request ) = self . rewrite_cache . get ( & request_str. to_string ( ) ) {
295- debug ! ( "Cache hit for request rewrite" ) ;
296- let new_bytes = cached_request . as_bytes ( ) ;
297- let new_len = new_bytes . len ( ) ;
311+ // Find the first line of the request ( the request line)
312+ let line_end = match request_str. find ( " \r \n " ) {
313+ Some ( pos ) => pos ,
314+ None => return ( length , is_websocket ) , // Not a complete HTTP request line
315+ } ;
298316
299- // Make sure we don't overflow the buffer
300- if new_len <= buffer. len ( ) {
301- buffer[ ..new_len] . copy_from_slice ( new_bytes) ;
302- return ( new_len, is_websocket) ;
303- } else {
304- debug ! ( "Cached rewritten request too large for buffer" ) ;
305- return ( length, false ) ;
306- }
307- }
317+ let request_line = & request_str[ ..line_end] ;
318+ let rest_of_request = & request_str[ line_end..] ;
308319
309- debug ! ( "Cache miss for request rewrite" ) ;
320+ // Log the full request line for debugging
321+ debug ! ( "Received request line: '{}'" , request_line) ;
310322
311- // Find the first line of the request (the request line)
312- let line_end = match request_str. find ( "\r \n " ) {
313- Some ( pos) => pos,
314- None => return ( length, is_websocket) , // Not a complete HTTP request line
315- } ;
323+ // Parse the request line to extract Method, Path, and Protocol
324+ let parts: Vec < & str > = request_line. splitn ( 3 , ' ' ) . collect ( ) ;
325+ if parts. len ( ) < 3 {
326+ debug ! ( "Malformed request line: '{}'" , request_line) ;
327+ return ( length, is_websocket) ; // Malformed, return original
328+ }
329+ let method = parts[ 0 ] ;
330+ let request_path_with_query = parts[ 1 ] ;
331+ let protocol = parts[ 2 ] ;
332+
333+ // CRITICAL FIX: Separate path and query string
334+ let ( request_path, query_string) = match request_path_with_query. find ( '?' ) {
335+ Some ( pos) => {
336+ let ( path, query) = request_path_with_query. split_at ( pos) ;
337+ ( path, Some ( query) ) // query includes the '?' character
338+ }
339+ None => ( request_path_with_query, None )
340+ } ;
341+
342+ debug ! ( "Parsed request path: '{}', query: '{:?}'" , request_path, query_string) ;
343+
344+ // Try each rewrite rule
345+ let rules_guard = match self . path_rewrites . read ( ) {
346+ Ok ( guard) => guard,
347+ Err ( e) => {
348+ error ! ( "Failed to acquire read lock on path_rewrites: {}" , e) ;
349+ return ( length, is_websocket) ; // Return original length and websocket flag if lock acquisition fails
350+ }
351+ } ;
352+ for rule in rules_guard. iter ( ) {
353+ // Find all matches in the PATH ONLY (not including query)
354+ let mut matches = Vec :: new ( ) ;
355+ let mut captures = Vec :: new ( ) ;
356+
357+ log:: debug!( "Checking rewrite rule pattern for request_path: '{}'" , request_path) ;
358+
359+ // Use regex-automata to find matches in the path (excluding query)
360+ for mat in rule. pattern . find_iter ( request_path. as_bytes ( ) ) {
361+ matches. push ( ( mat. start ( ) , mat. end ( ) ) ) ;
362+
363+ // Extract capture groups
364+ // This is simplified since regex-automata's Match doesn't directly provide captures
365+ // In a real implementation, you'd need to extract captures based on the match bounds
366+ let matched_text = & request_path[ mat. start ( ) ..mat. end ( ) ] ;
367+ captures. push ( matched_text) ;
368+ }
316369
317- let request_line = & request_str[ ..line_end] ;
318- let rest_of_request = & request_str[ line_end..] ;
370+ // If we have a match, perform the rewrite
371+ if let Some ( ( start, end) ) = matches. first ( ) {
372+ debug ! ( "Matched regex pattern for rewrite" ) ;
319373
320- // Log the full request line for debugging
321- debug ! ( "Received request line: '{}'" , request_line) ;
374+ // Get parts of the *path* before and after the match
375+ let before = & request_path[ ..* start] ;
376+ let after = & request_path[ * end..] ;
322377
323- // Parse the request line to extract Method, Path, and Protocol
324- let parts: Vec < & str > = request_line. splitn ( 3 , ' ' ) . collect ( ) ;
325- if parts. len ( ) < 3 {
326- debug ! ( "Malformed request line: '{}'" , request_line) ;
327- return ( length, is_websocket) ; // Malformed, return original
328- }
329- let method = parts[ 0 ] ;
330- let request_path = parts[ 1 ] ;
331- let protocol = parts[ 2 ] ;
332- debug ! ( "Parsed request path: '{}'" , request_path) ;
333-
334- // Try each rewrite rule
335- let rules_guard = match self . path_rewrites . read ( ) {
336- Ok ( guard) => guard,
337- Err ( e) => {
338- error ! ( "Failed to acquire read lock on path_rewrites: {}" , e) ;
339- return ( length, is_websocket) ; // Return original length and websocket flag if lock acquisition fails
340- }
341- } ;
342- for rule in rules_guard. iter ( ) {
343- // Find all matches in the request line
344- let mut matches = Vec :: new ( ) ;
345- let mut captures = Vec :: new ( ) ;
346-
347- log:: debug!( "Checking rewrite rule pattern for request_path: '{}'" , request_path) ;
348-
349- // Use regex-automata to find matches in the path
350- for mat in rule. pattern . find_iter ( request_path. as_bytes ( ) ) {
351- matches. push ( ( mat. start ( ) , mat. end ( ) ) ) ;
352-
353- // Extract capture groups
354- // This is simplified since regex-automata's Match doesn't directly provide captures
355- // In a real implementation, you'd need to extract captures based on the match bounds
356- let matched_text = & request_path[ mat. start ( ) ..mat. end ( ) ] ;
357- captures. push ( matched_text) ;
378+ // Process replacement template with capture references
379+ let replacement = self . process_replacement (
380+ & captures. iter ( ) . map ( |s| * s) . collect :: < Vec < & str > > ( ) ,
381+ & rule. replacement ,
382+ ) ;
383+
384+ // Create the new *path* without query, then add query if present
385+ let new_request_path_only = format ! ( "{}{}{}" , before, replacement, after) ;
386+
387+ // Reconstruct the full path with query string if it was present
388+ let new_request_path = match query_string {
389+ Some ( q) => format ! ( "{}{}" , new_request_path_only, q) , // q already includes '?'
390+ None => new_request_path_only,
391+ } ;
392+
393+ let new_request_line = format ! ( "{} {} {}" , method, new_request_path, protocol) ;
394+ let new_request = format ! ( "{}{}" , new_request_line, rest_of_request) ;
395+
396+ // Log rewrite information with special note for WebSocket upgrades
397+ if is_websocket {
398+ debug ! ( "Rewrote WebSocket upgrade request: {} -> {}" , request_line, new_request_line) ;
399+ } else {
400+ debug ! ( "Rewrote request: {} -> {}" , request_line, new_request_line) ;
358401 }
359402
360- // If we have a match, perform the rewrite
361- if let Some ( ( start, end) ) = matches. first ( ) {
362- debug ! ( "Matched regex pattern for rewrite" ) ;
363-
364- // Get parts of the *path* before and after the match
365- let before = & request_path[ ..* start] ;
366- let after = & request_path[ * end..] ;
367-
368- // Process replacement template with capture references
369- let replacement = self . process_replacement (
370- & captures. iter ( ) . map ( |s| * s) . collect :: < Vec < & str > > ( ) ,
371- & rule. replacement ,
372- ) ;
373-
374- // Create the new *path* and then the new request line
375- let new_request_path = format ! ( "{}{}{}" , before, replacement, after) ;
376- let new_request_line = format ! ( "{} {} {}" , method, new_request_path, protocol) ;
377- let new_request = format ! ( "{}{}" , new_request_line, rest_of_request) ;
378-
379- // Log rewrite information with special note for WebSocket upgrades
380- if is_websocket {
381- debug ! ( "Rewrote WebSocket upgrade request: {} -> {}" , request_line, new_request_line) ;
382- } else {
383- debug ! ( "Rewrote request: {} -> {}" , request_line, new_request_line) ;
384- }
403+ // Store the rewritten request in the cache
404+ self . rewrite_cache . insert ( request_str. to_string ( ) , new_request. clone ( ) ) ;
405+ debug ! ( "Stored rewritten request in cache" ) ;
385406
386- // Store the rewritten request in the cache
387- self . rewrite_cache . insert ( request_str. to_string ( ) , new_request. clone ( ) ) ;
388- debug ! ( "Stored rewritten request in cache" ) ;
389-
390- // Convert back to bytes and copy to the buffer
391- let new_bytes = new_request. as_bytes ( ) ;
392- let new_len = new_bytes. len ( ) ;
393-
394- // Make sure we don't overflow the buffer
395- if new_len <= buffer. len ( ) {
396- // Copy the new request into the buffer
397- buffer[ ..new_len] . copy_from_slice ( new_bytes) ;
398- return ( new_len, is_websocket) ;
399- } else {
400- debug ! ( "Rewritten request too large for buffer" ) ;
401- return ( length, is_websocket) ; // Return original length if new request is too large
402- }
407+ // Convert back to bytes and copy to the buffer
408+ let new_bytes = new_request. as_bytes ( ) ;
409+ let new_len = new_bytes. len ( ) ;
410+
411+ // Make sure we don't overflow the buffer
412+ if new_len <= buffer. len ( ) {
413+ // Copy the new request into the buffer
414+ buffer[ ..new_len] . copy_from_slice ( new_bytes) ;
415+ return ( new_len, is_websocket) ;
416+ } else {
417+ debug ! ( "Rewritten request too large for buffer" ) ;
418+ return ( length, is_websocket) ; // Return original length if new request is too large
403419 }
404420 }
405-
406- // No rewrite performed
407- debug ! ( "No rewrite rule matched for request path: {}" , request_path) ;
408- // should close if no match
409- ( 0 , is_websocket)
410421 }
411422
423+ // No rewrite performed
424+ debug ! ( "No rewrite rule matched for request path: {}" , request_path) ;
425+ // should close if no match
426+ ( 0 , is_websocket)
427+ }
428+
412429 /// Checks if the configuration should be reloaded based on time interval.
413430 fn check_and_reload_config_if_needed ( & self ) {
414431 let now = std:: time:: Instant :: now ( ) ;
0 commit comments