22#include "buffer_pool.h"
33#include "configuration.h"
44#include "connection.h"
5- #include "http.h"
5+ #include "http.h" /* For http_url_encode */
66#include "multicast.h"
77#include "status.h"
88#include "utils.h"
@@ -571,12 +571,22 @@ static int http_proxy_try_receive_response(http_proxy_session_t *session) {
571571 return bytes_forwarded ;
572572}
573573
574+ /* Check if status code is a redirect that may have Location header */
575+ static int http_proxy_is_redirect_status (int status_code ) {
576+ return (status_code == 301 || status_code == 302 || status_code == 303 ||
577+ status_code == 307 || status_code == 308 );
578+ }
579+
574580static int http_proxy_parse_response_headers (http_proxy_session_t * session ) {
575581 char * header_end ;
576582 char * line ;
577583 char * body_start ;
578584 size_t header_len ;
579585 char headers_copy [HTTP_PROXY_RESPONSE_BUFFER_SIZE ];
586+ char location_header [HTTP_PROXY_PATH_SIZE ];
587+ int has_location = 0 ;
588+
589+ location_header [0 ] = '\0' ;
580590
581591 /* Look for end of headers (double CRLF) */
582592 session -> response_buffer [session -> response_buffer_pos ] = '\0' ;
@@ -631,6 +641,15 @@ static int http_proxy_parse_response_headers(http_proxy_session_t *session) {
631641 1 ] = '\0' ;
632642 logger (LOG_DEBUG , "HTTP Proxy: Content-Type: %s" ,
633643 session -> response_content_type );
644+ } else if (strncasecmp (line , "Location:" , 9 ) == 0 ) {
645+ /* Extract Location header value for potential rewriting */
646+ char * value = line + 9 ;
647+ while (* value == ' ' )
648+ value ++ ;
649+ strncpy (location_header , value , sizeof (location_header ) - 1 );
650+ location_header [sizeof (location_header ) - 1 ] = '\0' ;
651+ has_location = 1 ;
652+ logger (LOG_DEBUG , "HTTP Proxy: Location: %s" , location_header );
634653 }
635654 /* Note: Transfer-Encoding is passed through, not parsed */
636655 }
@@ -640,44 +659,142 @@ static int http_proxy_parse_response_headers(http_proxy_session_t *session) {
640659 /* Forward response headers to client - flush immediately */
641660 if (!session -> headers_forwarded && session -> conn ) {
642661 /*
643- * We need to inject Set-Cookie header if should_set_r2h_cookie is set.
644- * Strategy: send headers without final \r\n\r\n, optionally inject
645- * Set-Cookie, then send \r\n\r\n.
662+ * For redirect responses (30x), we need to rewrite the Location header
663+ * to point back through the proxy. We rebuild headers line by line.
664+ *
665+ * For non-redirect responses, we need to inject Set-Cookie header if
666+ * should_set_r2h_cookie is set.
646667 */
647- size_t headers_without_crlf = header_len - 2 ; /* Exclude final \r\n */
648-
649- /* Send headers up to (but not including) final \r\n\r\n */
650- if (connection_queue_output (session -> conn , session -> response_buffer ,
651- headers_without_crlf ) < 0 ) {
652- logger (LOG_ERROR , "HTTP Proxy: Failed to forward headers to client" );
653- return -1 ;
668+ int is_redirect = http_proxy_is_redirect_status (session -> response_status_code );
669+ char rewritten_location [HTTP_PROXY_PATH_SIZE ];
670+ int location_rewritten = 0 ;
671+
672+ /* Rewrite Location header for redirects */
673+ if (is_redirect && has_location ) {
674+ if (http_proxy_build_url (location_header , "/" , rewritten_location ,
675+ sizeof (rewritten_location )) == 0 ) {
676+ location_rewritten = 1 ;
677+ logger (LOG_DEBUG , "HTTP Proxy: Rewritten Location: %s -> %s" ,
678+ location_header , rewritten_location );
679+ }
654680 }
655681
656- /* Inject Set-Cookie header if needed */
657- if (session -> conn -> should_set_r2h_cookie && config .r2h_token &&
658- config .r2h_token [0 ] != '\0' ) {
659- char set_cookie_header [512 ];
660- int cookie_len =
661- snprintf (set_cookie_header , sizeof (set_cookie_header ),
662- "Set-Cookie: r2h-token=%s; Path=/; HttpOnly; "
663- "SameSite=Strict\r\n" ,
664- config .r2h_token );
665- if (cookie_len > 0 && cookie_len < (int )sizeof (set_cookie_header )) {
666- if (connection_queue_output (session -> conn ,
667- (const uint8_t * )set_cookie_header ,
668- cookie_len ) < 0 ) {
669- logger (LOG_ERROR , "HTTP Proxy: Failed to send Set-Cookie header" );
682+ if (location_rewritten ) {
683+ /*
684+ * Need to rebuild headers with modified Location.
685+ * Parse original headers again and rebuild with new Location value.
686+ */
687+ char rebuilt_headers [HTTP_PROXY_RESPONSE_BUFFER_SIZE ];
688+ char * rebuild_ptr = rebuilt_headers ;
689+ size_t rebuild_remaining = sizeof (rebuilt_headers );
690+ char * orig_line ;
691+ char orig_headers [HTTP_PROXY_RESPONSE_BUFFER_SIZE ];
692+ int first_line = 1 ;
693+
694+ /* Copy headers again for parsing */
695+ memcpy (orig_headers , session -> response_buffer , header_len - 2 );
696+ orig_headers [header_len - 2 ] = '\0' ;
697+
698+ /* Rebuild headers line by line */
699+ orig_line = strtok (orig_headers , "\r\n" );
700+ while (orig_line != NULL ) {
701+ int written ;
702+
703+ if (strncasecmp (orig_line , "Location:" , 9 ) == 0 ) {
704+ /* Replace Location header with rewritten value */
705+ written = snprintf (rebuild_ptr , rebuild_remaining , "Location: %s\r\n" ,
706+ rewritten_location );
707+ } else {
708+ /* Copy other headers as-is */
709+ written = snprintf (rebuild_ptr , rebuild_remaining , "%s\r\n" , orig_line );
710+ }
711+
712+ if (written < 0 || (size_t )written >= rebuild_remaining ) {
713+ logger (LOG_ERROR , "HTTP Proxy: Rebuilt headers too large" );
670714 return -1 ;
671715 }
672- logger (LOG_DEBUG , "HTTP Proxy: Injected Set-Cookie header for r2h-token" );
716+
717+ rebuild_ptr += written ;
718+ rebuild_remaining -= written ;
719+ first_line = 0 ;
720+ orig_line = strtok (NULL , "\r\n" );
673721 }
674- session -> conn -> should_set_r2h_cookie = 0 ; /* Only set once */
675- }
676722
677- /* Send final \r\n to end headers */
678- if (connection_queue_output (session -> conn , (const uint8_t * )"\r\n" , 2 ) < 0 ) {
679- logger (LOG_ERROR , "HTTP Proxy: Failed to send header terminator" );
680- return -1 ;
723+ (void )first_line ; /* Suppress unused warning */
724+
725+ /* Send rebuilt headers */
726+ size_t rebuilt_len = rebuild_ptr - rebuilt_headers ;
727+ if (connection_queue_output (session -> conn , (const uint8_t * )rebuilt_headers ,
728+ rebuilt_len ) < 0 ) {
729+ logger (LOG_ERROR , "HTTP Proxy: Failed to forward rebuilt headers" );
730+ return -1 ;
731+ }
732+
733+ /* Inject Set-Cookie header if needed */
734+ if (session -> conn -> should_set_r2h_cookie && config .r2h_token &&
735+ config .r2h_token [0 ] != '\0' ) {
736+ char set_cookie_header [512 ];
737+ int cookie_len =
738+ snprintf (set_cookie_header , sizeof (set_cookie_header ),
739+ "Set-Cookie: r2h-token=%s; Path=/; HttpOnly; "
740+ "SameSite=Strict\r\n" ,
741+ config .r2h_token );
742+ if (cookie_len > 0 && cookie_len < (int )sizeof (set_cookie_header )) {
743+ if (connection_queue_output (session -> conn ,
744+ (const uint8_t * )set_cookie_header ,
745+ cookie_len ) < 0 ) {
746+ logger (LOG_ERROR , "HTTP Proxy: Failed to send Set-Cookie header" );
747+ return -1 ;
748+ }
749+ logger (LOG_DEBUG ,
750+ "HTTP Proxy: Injected Set-Cookie header for r2h-token" );
751+ }
752+ session -> conn -> should_set_r2h_cookie = 0 ;
753+ }
754+
755+ /* Send final CRLF to end headers */
756+ if (connection_queue_output (session -> conn , (const uint8_t * )"\r\n" , 2 ) < 0 ) {
757+ logger (LOG_ERROR , "HTTP Proxy: Failed to send header terminator" );
758+ return -1 ;
759+ }
760+ } else {
761+ /* No Location rewriting needed - use original logic */
762+ size_t headers_without_crlf = header_len - 2 ; /* Exclude final \r\n */
763+
764+ /* Send headers up to (but not including) final \r\n\r\n */
765+ if (connection_queue_output (session -> conn , session -> response_buffer ,
766+ headers_without_crlf ) < 0 ) {
767+ logger (LOG_ERROR , "HTTP Proxy: Failed to forward headers to client" );
768+ return -1 ;
769+ }
770+
771+ /* Inject Set-Cookie header if needed */
772+ if (session -> conn -> should_set_r2h_cookie && config .r2h_token &&
773+ config .r2h_token [0 ] != '\0' ) {
774+ char set_cookie_header [512 ];
775+ int cookie_len =
776+ snprintf (set_cookie_header , sizeof (set_cookie_header ),
777+ "Set-Cookie: r2h-token=%s; Path=/; HttpOnly; "
778+ "SameSite=Strict\r\n" ,
779+ config .r2h_token );
780+ if (cookie_len > 0 && cookie_len < (int )sizeof (set_cookie_header )) {
781+ if (connection_queue_output (session -> conn ,
782+ (const uint8_t * )set_cookie_header ,
783+ cookie_len ) < 0 ) {
784+ logger (LOG_ERROR , "HTTP Proxy: Failed to send Set-Cookie header" );
785+ return -1 ;
786+ }
787+ logger (LOG_DEBUG ,
788+ "HTTP Proxy: Injected Set-Cookie header for r2h-token" );
789+ }
790+ session -> conn -> should_set_r2h_cookie = 0 ; /* Only set once */
791+ }
792+
793+ /* Send final \r\n to end headers */
794+ if (connection_queue_output (session -> conn , (const uint8_t * )"\r\n" , 2 ) < 0 ) {
795+ logger (LOG_ERROR , "HTTP Proxy: Failed to send header terminator" );
796+ return -1 ;
797+ }
681798 }
682799
683800 session -> headers_forwarded = 1 ;
@@ -871,3 +988,77 @@ int http_proxy_session_cleanup(http_proxy_session_t *session) {
871988
872989 return 0 ;
873990}
991+
992+ int http_proxy_is_proxy_url (const char * url ) {
993+ /* Only support http:// URLs for proxying (not https://) */
994+ if (strncasecmp (url , "http://" , 7 ) == 0 ) {
995+ /* Make sure it's not already a wrapped URL (http://host/rtp/...) */
996+ const char * path_start = strchr (url + 7 , '/' );
997+ if (path_start ) {
998+ /* Check if path starts with /rtp/, /udp/, /rtsp/, or /http/ */
999+ if (strncmp (path_start , "/rtp/" , 5 ) == 0 ||
1000+ strncmp (path_start , "/udp/" , 5 ) == 0 ||
1001+ strncmp (path_start , "/rtsp/" , 6 ) == 0 ||
1002+ strncmp (path_start , "/http/" , 6 ) == 0 ) {
1003+ return 0 ; /* Already wrapped, don't treat as plain HTTP */
1004+ }
1005+ }
1006+ return 1 ;
1007+ }
1008+ return 0 ;
1009+ }
1010+
1011+ int http_proxy_build_url (const char * http_url , const char * base_url_placeholder ,
1012+ char * output , size_t output_size ) {
1013+ const char * host_start ;
1014+ char * encoded_token = NULL ;
1015+ int result ;
1016+ int has_r2h_token = (config .r2h_token && config .r2h_token [0 ] != '\0' );
1017+
1018+ /* Skip http:// prefix */
1019+ if (strncasecmp (http_url , "http://" , 7 ) != 0 ) {
1020+ logger (LOG_ERROR , "http_proxy_build_url: URL must start with http://" );
1021+ return -1 ;
1022+ }
1023+ host_start = http_url + 7 ; /* Points to host:port/path */
1024+
1025+ /* URL encode r2h-token if configured */
1026+ if (has_r2h_token ) {
1027+ encoded_token = http_url_encode (config .r2h_token );
1028+ if (!encoded_token ) {
1029+ logger (LOG_ERROR , "Failed to URL encode r2h-token" );
1030+ return -1 ;
1031+ }
1032+ }
1033+
1034+ /* Build proxy URL: {BASE_URL}http/host:port/path[?r2h-token=xxx] */
1035+ /* Check if original URL has query parameters */
1036+ const char * query_start = strchr (host_start , '?' );
1037+
1038+ if (has_r2h_token && encoded_token ) {
1039+ if (query_start ) {
1040+ /* Original URL has query params, append r2h-token with & */
1041+ result = snprintf (output , output_size , "%shttp/%s&r2h-token=%s" ,
1042+ base_url_placeholder , host_start , encoded_token );
1043+ } else {
1044+ /* No query params, add r2h-token with ? */
1045+ result = snprintf (output , output_size , "%shttp/%s?r2h-token=%s" ,
1046+ base_url_placeholder , host_start , encoded_token );
1047+ }
1048+ } else {
1049+ /* No r2h-token, just transform the URL */
1050+ result =
1051+ snprintf (output , output_size , "%shttp/%s" , base_url_placeholder , host_start );
1052+ }
1053+
1054+ if (encoded_token )
1055+ free (encoded_token );
1056+
1057+ if (result >= (int )output_size ) {
1058+ logger (LOG_ERROR , "HTTP proxy URL too long" );
1059+ return -1 ;
1060+ }
1061+
1062+ return 0 ;
1063+ }
1064+
0 commit comments