overwrite http.lua and call http wraps before loading (#38)

* add better log messages for multi url checks

* update log function to use table.Add

* overwrite http.lua and call http wraps before loading

* remove useless elseif

* use ProtectedCall for loading wraps

* use ProtectedCall for loading html wraps

* include reason in logs, dont allow any urls in file data

* remove unused function

* fix typo
This commit is contained in:
Pierce Lally 2023-09-12 18:17:42 -04:00 committed by GitHub
parent 5bc16c0745
commit 855cfc13c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 230 additions and 332 deletions

View File

@ -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")

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<string, 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

View File

@ -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 ) )

View File

@ -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" )

View File

@ -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()

View File

@ -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()

View File

@ -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()