From 75ffba64c5fe4d5e10685372040a2b408e0420e8 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 6 Jun 2026 22:55:47 +0500 Subject: [PATCH] engine: http: add support for redirects --- engine/common/http/net_http_xash.c | 109 +++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/engine/common/http/net_http_xash.c b/engine/common/http/net_http_xash.c index 5157dd8a..2304e541 100644 --- a/engine/common/http/net_http_xash.c +++ b/engine/common/http/net_http_xash.c @@ -69,6 +69,7 @@ typedef struct httpfile_s resource_t *resource; http_process_fn_t pfn_process; struct sockaddr_storage addr; + int redirects_followed; // in-memory response mode (set by HTTP_GetToMemory) qboolean to_memory; @@ -104,6 +105,7 @@ static CVAR_DEFINE_AUTO( http_autoremove, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, static CVAR_DEFINE_AUTO( http_timeout, "45", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "timeout for http downloader" ); static CVAR_DEFINE_AUTO( http_maxconnections, "2", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "maximum http connection number" ); static CVAR_DEFINE_AUTO( http_show_headers, "0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "show HTTP headers (request and response)" ); +static CVAR_DEFINE_AUTO( http_max_redirects, "5", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "maximum HTTP redirects to follow per request" ); static int HTTP_FileFree( httpfile_t *file ); static int HTTP_FileConnect( httpfile_t *file ); @@ -114,6 +116,7 @@ static int HTTP_FileResolveNS( httpfile_t *file ); static int HTTP_FileSendRequest( httpfile_t *file ); static int HTTP_FileDecompress( httpfile_t *file ); static httpserver_t *HTTP_ParseURLEx( const char *url_, qboolean full_path ); +static qboolean HTTP_FileRedirect( httpfile_t *file, const char *location ); static const char *HTTP_DownloadPath( char *buf, size_t buflen, const char *path, qboolean incomplete ) { @@ -816,10 +819,6 @@ static int HTTP_FileProcessStream( httpfile_t *curfile ) { int num = -1; - char *p = Q_strchr( curfile->buf, '\r' ); - if( !p ) p = Q_strchr( curfile->buf, '\n' ); - if( p ) *p = 0; - // extract the error code, don't assume the response is valid HTTP if( !Q_strncmp( curfile->buf, "HTTP/1.", 7 )) { @@ -830,9 +829,31 @@ static int HTTP_FileProcessStream( httpfile_t *curfile ) num = Q_atoi( tmp ); } + if( num == 301 || num == 302 || num == 303 || num == 307 || num == 308 ) + { + char *loc = Q_stristr( curfile->buf, "Location:" ); + + if( loc ) + { + loc += sizeof( "Location:" ) - 1; + while( *loc == ' ' || *loc == '\t' ) + loc++; + + char *eol = Q_strchr( loc, '\r' ); + if( !eol ) eol = Q_strchr( loc, '\n' ); + if( eol ) *eol = 0; + + if( HTTP_FileRedirect( curfile, loc )) + return 1; + } + } + + char *p = Q_strchr( curfile->buf, '\r' ); + if( !p ) p = Q_strchr( curfile->buf, '\n' ); + if( p ) *p = 0; + switch( num ) { - // TODO: handle redirects case 404: Con_Printf( S_ERROR "%s: file not found\n", curfile->path ); break; @@ -1200,6 +1221,83 @@ static httpserver_t *HTTP_ParseURL( const char *url_ ) return HTTP_ParseURLEx( url_, false ); } +static qboolean HTTP_FileRedirect( httpfile_t *file, const char *location ) +{ + if( !location || !*location ) + return false; + + if( file->redirects_followed >= http_max_redirects.value ) + { + Con_Printf( S_ERROR "too many redirects for %s\n", file->to_memory ? file->url : file->path ); + return false; + } + + // silent http -> https upgrade is OK; reject downgrade + qboolean target_secure = !Q_strnicmp( location, "https://", 8 ); + qboolean target_plain = !Q_strnicmp( location, "http://", 7 ); + + if( !target_secure && !target_plain ) + { + Con_Printf( S_ERROR "redirect to non-absolute URL not supported: %s\n", location ); + return false; + } + + if( file->server->secure && !target_secure ) + { + Con_Printf( S_ERROR "refusing https -> http redirect: %s\n", location ); + return false; + } + + httpserver_t *newserver = HTTP_ParseURLEx( location, true ); + if( !newserver ) + { + Con_Printf( S_ERROR "redirect target %s is not a valid URL\n", location ); + return false; + } + + Con_Reportf( "HTTP: redirect %s -> %s\n", file->to_memory ? file->url : file->path, location ); + + // tear down current connection but keep file/mem buffers + if( file->socket != -1 ) + { + closesocket( file->socket ); + http.active_count--; + file->socket = -1; + } + + if( file->own_server && file->server ) + Mem_Free( file->server ); + file->server = newserver; + file->own_server = true; + + // truncate the partial download; we'll restart from the new server + if( file->file ) + { + g_fsapi.Seek( file->file, 0, SEEK_SET ); + } + file->mem_size = 0; + file->downloaded = 0; + file->lastchecksize = 0; + file->header_size = 0; + file->bytes_sent = 0; + file->got_response = false; + file->compressed = false; + file->chunked = false; + file->chunksize = 0; + file->size = file->reported_size; + + // redirect Location: gave us a full URL, so per-file suffix is empty now + file->path[0] = 0; + if( file->to_memory ) + Q_strncpy( file->url, location, sizeof( file->url )); + + file->redirects_followed++; + file->blocktime = 0; + file->pfn_process = HTTP_FileResolveNS; + + return true; +} + /* ======================= HTTP_AddCustomServer @@ -1364,6 +1462,7 @@ void HTTP_Init( void ) Cvar_RegisterVariable( &http_timeout ); Cvar_RegisterVariable( &http_maxconnections ); Cvar_RegisterVariable( &http_show_headers ); + Cvar_RegisterVariable( &http_max_redirects ); } /*