diff --git a/lua/autorun/cfc_http_whitelist_init.lua b/lua/autorun/cfc_http_whitelist_init.lua index 474f2e9..e21c1bc 100644 --- a/lua/autorun/cfc_http_whitelist_init.lua +++ b/lua/autorun/cfc_http_whitelist_init.lua @@ -24,4 +24,10 @@ includeShared( "cfc_http_restrictions/shared/url.lua" ) includeClient( "cfc_http_restrictions/client/list_view.lua" ) includeClient( "cfc_http_restrictions/client/wrap_functions.lua" ) + +AddCSLuaFile( "cfc_http_restrictions/wraps/http.lua" ) +AddCSLuaFile( "cfc_http_restrictions/wraps/html.lua" ) +AddCSLuaFile( "cfc_http_restrictions/wraps/playURL.lua" ) + includeClient( "cfc_http_restrictions/client/integrations.lua") + diff --git a/lua/cfc_http_restrictions/client/wrap_functions.lua b/lua/cfc_http_restrictions/client/wrap_functions.lua index 65acc57..f6ec24b 100644 --- a/lua/cfc_http_restrictions/client/wrap_functions.lua +++ b/lua/cfc_http_restrictions/client/wrap_functions.lua @@ -1,241 +1,5 @@ -local function wrapHTTP() - _HTTP = _HTTP or HTTP - print( "HTTP wrapped, original function at '_G._HTTP'" ) - - HTTP = function( req ) - local options = CFCHTTP.GetOptionsForURL( req.url ) - local isAllowed = options and options.allowed - local noisy = options and options.noisy - - local stack = string.Split( debug.traceback(), "\n" ) - CFCHTTP.LogRequest( { - noisy = noisy, - method = req.method, - fileLocation = stack[3], - urls = { { url = req.url, status = isAllowed and "allowed" or "blocked" } }, - } ) - - local onFailure = req.failed - if not isAllowed then - if onFailure then onFailure( "URL is not whitelisted" ) end - return - end - _HTTP( req ) - end -end - -local function wrapFetch() - _http_Fetch = _http_Fetch or http.Fetch - print( "http.Fetch wrapped, original function at '_http_Fetch'" ) - - ---@diagnostic disable-next-line: duplicate-set-field - http.Fetch = function( url, onSuccess, onFailure, headers ) - local options = CFCHTTP.GetOptionsForURL( url ) - local isAllowed = options and options.allowed - local noisy = options and options.noisy - - local stack = string.Split( debug.traceback(), "\n" ) - CFCHTTP.LogRequest( { - noisy = noisy, - method = "GET", - fileLocation = stack[3], - urls = { { url = url, status = isAllowed and "allowed" or "blocked" } }, - } ) - - if not isAllowed then - if onFailure then onFailure( "URL is not whitelisted" ) end - return - end - - _http_Fetch( url, onSuccess, onFailure, headers ) - end -end - -local function wrapPost() - _http_Post = _http_Post or http.Post - print( "http.Post wrapped, original function at '_http_Post'" ) - - ---@diagnostic disable-next-line: duplicate-set-field - http.Post = function( url, params, onSuccess, onFailure, headers ) - local options = CFCHTTP.GetOptionsForURL( url ) - local isAllowed = options and options.allowed - local noisy = options and options.noisy - - local stack = string.Split( debug.traceback(), "\n" ) - CFCHTTP.LogRequest( { - noisy = noisy, - method = "POST", - fileLocation = stack[3], - urls = { { url = url, status = isAllowed and "allowed" or "blocked" } }, - } ) - - if not isAllowed then - if onFailure then onFailure( "URL is not whitelisted" ) end - return - end - - _http_Post( url, params, onSuccess, onFailure, headers ) - end -end - --- the URI was blocked because it was not in the whitelist -CFCHTTP.BASS_ERROR_BLOCKED_URI = 11001 --- unused: the request was blocked after inspecting the content --- this is likely because the content could result in playing blocked URIs -CFCHTTP.BASS_ERROR_BLOCKED_CONTENT = 11002 - -local function wrapPlayURL() - _sound_PlayURL = _sound_PlayURL or sound.PlayURL - print( "sound.PlayURL wrapped, original function at _sound_PlayUrl" ) - - ---@diagnostic disable-next-line: duplicate-set-field - sound.PlayURL = function( url, flags, callback ) - local stack = string.Split( debug.traceback(), "\n" ) - - local options = CFCHTTP.GetOptionsForURL( url ) - local isAllowed = options and options.allowed - local noisy = options and options.noisy - - local logData = { - noisy = noisy, - method = "GET", - fileLocation = stack[3], - urls = { { url = url, status = isAllowed and "allowed" or "blocked" } }, - } - - if not isAllowed then - CFCHTTP.LogRequest( logData ) - if callback then callback( nil, CFCHTTP.BASS_ERROR_BLOCKED_URI, "BASS_ERROR_BLOCKED_URI" ) end - return - end - - CFCHTTP.GetFileDataURLS( url, function( uris, err ) - if err ~= nil then - print( "Error getting URLs: " .. err ) - if callback then callback( nil, CFCHTTP.BASS_ERROR_BLOCKED_CONTENT, "BASS_ERROR_BLOCKED_CONTENT" ) end - return - end - - if #uris == 0 then - CFCHTTP.LogRequest( logData ) - _sound_PlayURL( url, flags, callback ) - return - end - - local multiOptions = CFCHTTP.GetOptionsForURLs( uris ) - isAllowed = multiOptions.combined.allowed - - CFCHTTP.LogRequest( logData ) - if not isAllowed then - if callback then callback( nil, CFCHTTP.BASS_ERROR_BLOCKED_CONTENT, "BASS_ERROR_BLOCKED_CONTENT" ) end - return - end - - _sound_PlayURL( url, flags, callback ) - end ) - end -end - - -local function wrapHTMLPanel( panelName ) - print( "Wrapping SetHTML and OpenURL for " .. panelName ) - local funcName = function( functionName ) - return "_" .. panelName .. "_" .. functionName - end - - local controlTable = vgui.GetControlTable( panelName ) - - local setHTML = funcName( "SetHTML" ) - local openURL = funcName( "OpenURL" ) - local runJavascript = funcName( "RunJavascript" ) - - _G[setHTML] = _G[setHTML] or controlTable.SetHTML - _G[openURL] = _G[openURL] or controlTable.OpenURL - _G[runJavascript] = _G[runJavascript] or controlTable.RunJavascript - - controlTable.SetHTML = function( self, html, ... ) - local stack = string.Split( debug.traceback(), "\n" ) - - local logUrls = {} - html = CFCHTTP.ReplaceURLs( html, function( url ) - local options = CFCHTTP.GetOptionsForURL( url ) - local isAllowed = options and options.allowed - - local logUrl = { url = url, status = isAllowed and "allowed" or "replaced" } - table.insert( logUrls, logUrl ) - - if not isAllowed then - return CFCHTTP.GetRedirectURL( url ) - end - - return url - end ) - CFCHTTP.LogRequest( { - noisy = true, - method = "GET", - fileLocation = stack[3], - urls = logUrls, - } ) - - return _G[setHTML]( self, html, ... ) - end - - controlTable.RunJavascript = function( self, js ) - local stack = string.Split( debug.traceback(), "\n" ) - local logUrls = {} - js = CFCHTTP.ReplaceURLs( js, function( url ) - local options = CFCHTTP.GetOptionsForURL( url ) - local isAllowed = options and options.allowed - - local logUrl = { url = url, status = isAllowed and "allowed" or "replaced" } - table.insert( logUrls, logUrl ) - if not isAllowed then - return CFCHTTP.GetRedirectURL( url ) - end - - return url - end ) - - CFCHTTP.LogRequest( { - noisy = true, - method = "GET", - fileLocation = stack[3], - urls = logUrls, - } ) - - return _G[runJavascript]( self, js ) - end - - controlTable.OpenURL = function( self, url, ... ) - local options = CFCHTTP.GetOptionsForURL( url ) - local isAllowed = options and options.allowed - local noisy = options and options.noisy - - local stack = string.Split( debug.traceback(), "\n" ) - CFCHTTP.LogRequest( { - noisy = noisy, - method = "GET", - fileLocation = stack[3], - urls = { { url = url, status = isAllowed and "allowed" or "blocked" } }, - } ) - - if not isAllowed then - url = CFCHTTP.GetRedirectURL( url ) - end - - return _G[openURL]( self, url, ... ) - end -end - hook.Add( "Initialize", "CFC_HttpWhitelist_WrapHTML", function() - if CFCHTTP.config.wrapHTMLPanels then - wrapHTMLPanel( "DHTML" ) - wrapHTMLPanel( "DPanel" ) - wrapHTMLPanel( "DMediaPlayerHTML" ) - end + ProtectedCall( function() + include( "cfc_http_restrictions/wraps/html.lua" ) + end ) end ) - -wrapHTTP() -wrapFetch() -wrapPost() -wrapPlayURL() diff --git a/lua/cfc_http_restrictions/shared/filetypes.lua b/lua/cfc_http_restrictions/shared/filetypes.lua index 783c245..03af135 100644 --- a/lua/cfc_http_restrictions/shared/filetypes.lua +++ b/lua/cfc_http_restrictions/shared/filetypes.lua @@ -29,17 +29,10 @@ function CFCHTTP.GetFileDataURLS( url, callback ) local filetype = CFCHTTP.getFileType( body ) if filetype then local urls, err = filetype.GetURLSFromData( body ) - if err then - callback( {}, err ) - else - if #urls == 0 then - callback( {}, "No URLs found in file" ) - return - end - callback( urls, nil ) - end + callback( urls, err ) else - callback( {}, nil ) + local urls = CFCHTTP.FindURLs( body ) + callback( urls, nil ) end end ) end diff --git a/lua/cfc_http_restrictions/shared/filetypes/m3u.lua b/lua/cfc_http_restrictions/shared/filetypes/m3u.lua index 8bf2a95..2d2c1f9 100644 --- a/lua/cfc_http_restrictions/shared/filetypes/m3u.lua +++ b/lua/cfc_http_restrictions/shared/filetypes/m3u.lua @@ -26,7 +26,7 @@ end ---@return string[] urls ---@return string|nil error function M3U.GetURLSFromData( _ ) - return {}, "m3u parsing not implemented" + return {}, "m3u files are not allowed" end return M3U diff --git a/lua/cfc_http_restrictions/shared/filetypes/pls.lua b/lua/cfc_http_restrictions/shared/filetypes/pls.lua index 96c3a42..5303552 100644 --- a/lua/cfc_http_restrictions/shared/filetypes/pls.lua +++ b/lua/cfc_http_restrictions/shared/filetypes/pls.lua @@ -6,36 +6,6 @@ CFCHTTP.FileTypes.PLS = { } local PLS = CFCHTTP.FileTypes.PLS - ----@param body string ----@return table data -local function loadPLSFile( body ) - body = string.Replace( body, "\r\n", "\n" ) - body = string.Replace( body, "\r", "\n" ) - - local lines = string.Split( body, "\n" ) - local section; - local data = {} - - for _, line in ipairs( lines ) do - local tempSection = line:match( "^%[([^%[%]]+)%]$" ); - if tempSection then - section = tonumber( tempSection ) and tonumber( tempSection ) or tempSection; - data[section] = data[section] or {}; - end - - local param, value = line:match( "^([%w|_]+)%s-=%s-(.+)$" ); - if param and value ~= nil then - if tonumber( value ) then - value = tonumber( value ); - end - data[section][param] = value; - end - end - - return data; -end - ---@param body string ---@return boolean function PLS.IsFileData( body ) @@ -50,28 +20,12 @@ function PLS.IsFileURL( url ) return false end ----@param body string +---@param _body string ---@return string[] urls ---@return string|nil error -function PLS.GetURLSFromData( body ) - if not PLS.allowed then return {}, "pls files are not allowed" end - - if #body > PLS.maxFileSize then return {}, "body too large" end - local urls = CFCHTTP.FindURLs( body ) - - local plsData = loadPLSFile( body ) - if not plsData.playlist then - return urls, "no playlist section" - end - - for i = 1, 150 do - local f = plsData.playlist["File" .. i] - if not f then - return urls, nil - end - table.insert( urls, f ) - end - return urls, "too many files" +---@diagnostic disable-next-line: unused-local +function PLS.GetURLSFromData( _body ) + return {}, "pls files are not allowed" end return PLS diff --git a/lua/cfc_http_restrictions/shared/list_manager.lua b/lua/cfc_http_restrictions/shared/list_manager.lua index 7f1ccf1..03164a6 100644 --- a/lua/cfc_http_restrictions/shared/list_manager.lua +++ b/lua/cfc_http_restrictions/shared/list_manager.lua @@ -42,32 +42,6 @@ function CFCHTTP.GetOptionsForURL( url ) return CFCHTTP.config.defaultOptions end ---- Returns the options for a list of URLs ----@param urls string[] ----@return {options: table, combined: table|nil, combinedUri: string|nil} -function CFCHTTP.GetOptionsForURLs( urls ) - local out = { - combined = nil, - options = {}, - } - for _, url in pairs( urls ) do - local options = CFCHTTP.GetOptionsForURL( url ) - out.options[url] = options - if options and not options.allowed then - out.combined = options - out.combinedUri = url - elseif not out.combined then - out.combined = options - out.combinedUri = url - end - end - if out.combined == nil then - out.combined = CFCHTTP.config.defaultOptions - end - - return out -end - -- file based config functions function CFCHTTP.allowAddress( addr ) if CFCHTTP.config.addresses[addr] ~= nil and CFCHTTP.config.addresses[addr].permanent then diff --git a/lua/cfc_http_restrictions/shared/logging.lua b/lua/cfc_http_restrictions/shared/logging.lua index 369195f..0efff3a 100644 --- a/lua/cfc_http_restrictions/shared/logging.lua +++ b/lua/cfc_http_restrictions/shared/logging.lua @@ -2,6 +2,7 @@ local COLORS = { RED = Color( 255, 0, 0 ), GREEN = Color( 0, 255, 0 ), GREY = Color( 136, 151, 158 ), + WHITE = Color( 255, 255, 255 ), ORANGE = Color( 255, 165, 0 ), YELLOW = Color( 235, 226, 52 ) } @@ -19,7 +20,7 @@ local statusColors = { ---@class CFCHTTPLogRequestInput ---@field method string ----@field urls { url: string, status: string }[] +---@field urls { url: string, status: string, reason: string }[] ---@field fileLocation string ---@field noisy boolean|nil @@ -36,6 +37,9 @@ function CFCHTTP.LogRequest( input ) if #input.urls == 1 then local url = input.urls[1].url + + local reason = input.urls[1].reason + local requestStatus = string.upper( input.urls[1].status ) or "UNKNOWN" local requestColor = statusColors[requestStatus] or COLORS.GREY if not shouldLogAllows:GetBool() and requestStatus == "ALLOWED" then return end @@ -44,23 +48,29 @@ function CFCHTTP.LogRequest( input ) MsgC( requestColor, requestStatus, COLORS.GREY, ": ", - COLORS.YELLOW, input.method, + COLORS.YELLOW, string.upper( input.method ), COLORS.GREY, " - ", - COLORS.YELLOW, url, "\n" + COLORS.YELLOW, url ) + if reason then + MsgC( ": ", COLORS.WHITE, reason ) + end + MsgC( "\n" ) if verboseLogging:GetBool() then - MsgC( COLORS.YELLOW, " ", input.fileLocation, "\n" ) + MsgC( COLORS.YELLOW, " ", input.fileLocation, "\n" ) end return end - local msg = { COLORS.YELLOW, tostring( #input.urls ), " urs filtered:\n", COLORS.YELLOW, " ", input.fileLocation, "\n" } + local msg = { COLORS.YELLOW, tostring( #input.urls ), " urls filtered:\n", COLORS.YELLOW, " ", input.fileLocation, "\n" } for _, v in pairs( input.urls or {} ) do local url = v.url + local reason = v.reason or "" local requestStatus = string.upper( v.status ) or "UNKNOWN" local requestColor = statusColors[requestStatus] or COLORS.GREY - table.Add( msg, { requestColor, "\t", requestStatus, COLORS.GREY, ": ", COLORS.YELLOW, input.method, COLORS.GREY, " - ", COLORS.YELLOW, url, "\n" } ) + table.Add( msg, + { requestColor, "\t", requestStatus, COLORS.GREY, ": ", COLORS.YELLOW, string.upper( input.method ), COLORS.GREY, " - ", COLORS.YELLOW, url, " : ", COLORS.WHITE, reason, "\n" } ) end MsgC( unpack( msg ) ) diff --git a/lua/cfc_http_restrictions/wraps/html.lua b/lua/cfc_http_restrictions/wraps/html.lua new file mode 100644 index 0000000..d999a2b --- /dev/null +++ b/lua/cfc_http_restrictions/wraps/html.lua @@ -0,0 +1,93 @@ +local function wrapHTMLPanel( panelName ) + print( "Wrapping SetHTML and OpenURL for " .. panelName ) + local funcName = function( functionName ) + return "_" .. panelName .. "_" .. functionName + end + + local controlTable = vgui.GetControlTable( panelName ) + + local setHTML = funcName( "SetHTML" ) + local openURL = funcName( "OpenURL" ) + local runJavascript = funcName( "RunJavascript" ) + + _G[setHTML] = _G[setHTML] or controlTable.SetHTML + _G[openURL] = _G[openURL] or controlTable.OpenURL + _G[runJavascript] = _G[runJavascript] or controlTable.RunJavascript + + controlTable.SetHTML = function( self, html, ... ) + local stack = string.Split( debug.traceback(), "\n" ) + + local logUrls = {} + html = CFCHTTP.ReplaceURLs( html, function( url ) + local options = CFCHTTP.GetOptionsForURL( url ) + local isAllowed = options and options.allowed + + local logUrl = { url = url, status = isAllowed and "allowed" or "replaced" } + table.insert( logUrls, logUrl ) + + if not isAllowed then + return CFCHTTP.GetRedirectURL( url ) + end + + return url + end ) + CFCHTTP.LogRequest( { + noisy = true, + method = "GET", + fileLocation = stack[3], + urls = logUrls, + } ) + + return _G[setHTML]( self, html, ... ) + end + + controlTable.RunJavascript = function( self, js ) + local stack = string.Split( debug.traceback(), "\n" ) + local logUrls = {} + js = CFCHTTP.ReplaceURLs( js, function( url ) + local options = CFCHTTP.GetOptionsForURL( url ) + local isAllowed = options and options.allowed + + local logUrl = { url = url, status = isAllowed and "allowed" or "replaced" } + table.insert( logUrls, logUrl ) + if not isAllowed then + return CFCHTTP.GetRedirectURL( url ) + end + + return url + end ) + + CFCHTTP.LogRequest( { + noisy = true, + method = "GET", + fileLocation = stack[3], + urls = logUrls, + } ) + + return _G[runJavascript]( self, js ) + end + + controlTable.OpenURL = function( self, url, ... ) + local options = CFCHTTP.GetOptionsForURL( url ) + local isAllowed = options and options.allowed + local noisy = options and options.noisy + + local stack = string.Split( debug.traceback(), "\n" ) + CFCHTTP.LogRequest( { + noisy = noisy, + method = "GET", + fileLocation = stack[3], + urls = { { url = url, status = isAllowed and "allowed" or "blocked" } }, + } ) + + if not isAllowed then + url = CFCHTTP.GetRedirectURL( url ) + end + + return _G[openURL]( self, url, ... ) + end +end + +wrapHTMLPanel( "DHTML" ) +wrapHTMLPanel( "DPanel" ) +wrapHTMLPanel( "DMediaPlayerHTML" ) diff --git a/lua/cfc_http_restrictions/wraps/http.lua b/lua/cfc_http_restrictions/wraps/http.lua new file mode 100644 index 0000000..bd3ac09 --- /dev/null +++ b/lua/cfc_http_restrictions/wraps/http.lua @@ -0,0 +1,26 @@ +local function wrapHTTP() + _HTTP = _HTTP or HTTP + print( "HTTP wrapped, original function at '_G._HTTP'" ) + + HTTP = function( req ) + local options = CFCHTTP.GetOptionsForURL( req.url ) + local isAllowed = options and options.allowed + local noisy = options and options.noisy + + local stack = string.Split( debug.traceback(), "\n" ) + CFCHTTP.LogRequest( { + noisy = noisy, + method = req.method, + fileLocation = stack[3], + urls = { { url = req.url, status = isAllowed and "allowed" or "blocked" } }, + } ) + + local onFailure = req.failed + if not isAllowed then + if onFailure then onFailure( "URL is not whitelisted" ) end + return + end + _HTTP( req ) + end +end +wrapHTTP() diff --git a/lua/cfc_http_restrictions/wraps/playURL.lua b/lua/cfc_http_restrictions/wraps/playURL.lua new file mode 100644 index 0000000..85d33d1 --- /dev/null +++ b/lua/cfc_http_restrictions/wraps/playURL.lua @@ -0,0 +1,58 @@ +CFCHTTP = CFCHTTP or {} + +-- the URI was blocked because it was not in the whitelist +CFCHTTP.BASS_ERROR_BLOCKED_URI = 11001 +-- unused: the request was blocked after inspecting the content +-- this is likely because the content could result in playing blocked URIs +CFCHTTP.BASS_ERROR_BLOCKED_CONTENT = 11002 + +local function wrapPlayURL() + _sound_PlayURL = _sound_PlayURL or sound.PlayURL + print( "sound.PlayURL wrapped, original function at _sound_PlayUrl" ) + + ---@diagnostic disable-next-line: duplicate-set-field + sound.PlayURL = function( url, flags, callback ) + local stack = string.Split( debug.traceback(), "\n" ) + + local options = CFCHTTP.GetOptionsForURL( url ) + local isAllowed = options and options.allowed + local noisy = options and options.noisy + + local logData = { + noisy = noisy, + method = "GET", + fileLocation = stack[3], + urls = { { url = url, status = isAllowed and "allowed" or "blocked" } }, + } + + if not isAllowed then + CFCHTTP.LogRequest( logData ) + if callback then callback( nil, CFCHTTP.BASS_ERROR_BLOCKED_URI, "BASS_ERROR_BLOCKED_URI" ) end + return + end + + CFCHTTP.GetFileDataURLS( url, function( uris, err ) + if err ~= nil then + logData.urls[1].status = "blocked" + logData.urls[1].reason = err + + CFCHTTP.LogRequest( logData ) + if callback then callback( nil, CFCHTTP.BASS_ERROR_BLOCKED_CONTENT, "BASS_ERROR_BLOCKED_CONTENT" ) end + return + end + if #uris == 0 then + CFCHTTP.LogRequest( logData ) + _sound_PlayURL( url, flags, callback ) + return + end + + isAllowed = false + logData.urls[1].status = "blocked" + logData.urls[1].reason = "cant load files containing urls" + + CFCHTTP.LogRequest( logData ) + if callback then callback( nil, CFCHTTP.BASS_ERROR_BLOCKED_CONTENT, "BASS_ERROR_BLOCKED_CONTENT" ) end + end ) + end +end +wrapPlayURL() diff --git a/lua/includes/modules/http.lua b/lua/includes/modules/http.lua new file mode 100644 index 0000000..187acf0 --- /dev/null +++ b/lua/includes/modules/http.lua @@ -0,0 +1,20 @@ +AddCSLuaFile() + +-- file.Read is not available yet +local originalFile = file.Open( "lua/includes/modules/http.lua", "rb", "MOD" ) +local code = originalFile:Read( originalFile:Size() ) +originalFile:Close() + +local f = CompileString( code, "lua/includes/modules/http.lua", false ) + +if CLIENT then + ProtectedCall( function() + include( "cfc_http_restrictions/wraps/http.lua" ) + end ) + ProtectedCall( function() + include( "cfc_http_restrictions/wraps/playURL.lua" ) + end ) +end + +print( "Running wrapped http.lua" ) +f()