Skip to content

Commit

Permalink
Added serverside logging for stream URLs, added hook 3DStreamRadio_Ur…
Browse files Browse the repository at this point in the history
…lIsAllowed.
  • Loading branch information
Grocel committed Nov 18, 2024
1 parent 4b03ffe commit 08a75b5
Show file tree
Hide file tree
Showing 16 changed files with 388 additions and 94 deletions.
4 changes: 2 additions & 2 deletions data_static/streamradio/version.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
446
1725659185
447
1731972171
19 changes: 16 additions & 3 deletions lua/entities/base_streamradio.lua
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,12 @@ function ENT:GetOrCreateStream()
return call("StreamOnSearch", ...)
end

stream.CanIgnoreWhitelist = function( ... )
return call("StreamCanIgnoreWhitelist", ...)
stream.CanSkipUrlChecks = function( ... )
return call("StreamCanSkipUrlChecks", ...)
end

stream.CanBypassUrlBlock = function( ... )
return call("StreamCanBypassUrlBlock", ...)
end

stream.OnMute = function( ... )
Expand Down Expand Up @@ -202,7 +206,16 @@ function ENT:StreamOnSearch()
return true
end

function ENT:StreamCanIgnoreWhitelist()
function ENT:StreamCanSkipUrlChecks()
return false
end

function ENT:StreamCanBypassUrlBlock(blockedByHook)
if blockedByHook then
-- was blocked by external code
return false
end

if not StreamRadioLib.IsUrlWhitelistAdminRadioTrusted() then
return false
end
Expand Down
4 changes: 1 addition & 3 deletions lua/streamradio_core/_include.lua
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ StreamRadioLib.Url.Load()
StreamRadioLib.Interface.Load()
StreamRadioLib.Filesystem.Load()

if SERVER then
StreamRadioLib.Whitelist.Load()
end
StreamRadioLib.Whitelist.Load()

StreamRadioLib.Cfchttp.Load()
StreamRadioLib.Cache.Load()
Expand Down
4 changes: 2 additions & 2 deletions lua/streamradio_core/_load.lua
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,13 @@ local function loadAddon()
-- Sometimes the version is not known, yet.

if CLIENT then
local NEED_VERSION = 240730
local NEED_VERSION = 241029

if VERSION < NEED_VERSION then
versionError = string.format("Your GMod-Client (version: %s) is too old!\nPlease update the GMod-Client to version %s or newer!", VERSION, NEED_VERSION)
end
else
local NEED_VERSION = 240730
local NEED_VERSION = 241029

if VERSION < NEED_VERSION then
versionError = string.format("The GMod-Server (version: %s) is too old!\nPlease update the GMod-Server to version %s or newer!\nTell an Admin!", VERSION, NEED_VERSION)
Expand Down
10 changes: 9 additions & 1 deletion lua/streamradio_core/classes/rendertarget.lua
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,15 @@ function CLASS:CreateRendertarget()

-- No ENUMS for thise values are available in the game.
-- https://wiki.facepunch.com/gmod/Enums/TEXTUREFLAGS
local textureFlags = bit.bor(4, 8, 16, 32, 512, 2048, 8192, 32768)
local textureFlags = bit.bor(
4, -- TEXTUREFLAGS_CLAMPS
8, -- TEXTUREFLAGS_CLAMPT
16, -- TEXTUREFLAGS_ANISOTROPIC
32, -- TEXTUREFLAGS_HINT_DXT5
512, -- TEXTUREFLAGS_NOLOD
8192, -- TEXTUREFLAGS_EIGHTBITALPHA
32768 -- TEXTUREFLAGS_RENDERTARGET
)

local tex = GetRenderTargetEx(
name, w, h,
Expand Down
38 changes: 28 additions & 10 deletions lua/streamradio_core/classes/stream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ local retry_errors_block = {

local retry_errors_urlblocked = {
[LIBError.STREAM_ERROR_URL_NOT_WHITELISTED] = true,
[LIBError.STREAM_ERROR_URL_BLOCKED] = true,
}

local function loadLibs()
Expand Down Expand Up @@ -757,19 +758,31 @@ function CLASS:IsAllowedInternalUrl(url, callback, logFailure)
end

function CLASS:IsAllowedExternalUrl(url, callback)
if self:CallHook("CanIgnoreWhitelist", url) then
-- Sometimes we don't want/need to check the addon's whitelist
-- E.g. when the owner of the radio entity is an admin.

if self:CallHook("CanSkipUrlChecks", url) then
-- Sometimes we want to ignore the addon's whitelist
callback(self, true, nil)
return
end

StreamRadioLib.Whitelist.IsAllowedAsync(url, function(allowed)
local ent = self:GetEntity()
local context = StreamRadioLib.Whitelist.BuildContext(ent)

StreamRadioLib.Whitelist.IsAllowedAsync(url, context, function(allowed, blockedByHook)
if not IsValid(self) then return end

if not allowed then
callback(self, allowed, LIBError.STREAM_ERROR_URL_NOT_WHITELISTED)
if self:CallHook("CanBypassUrlBlock", url, blockedByHook) then
-- Sometimes we want to ignore the block, but still to perform the checks.
callback(self, true, nil)
return
end

if blockedByHook then
callback(self, allowed, LIBError.STREAM_ERROR_URL_BLOCKED)
return
end

callback(self, false, LIBError.STREAM_ERROR_URL_NOT_WHITELISTED)
return
end

Expand All @@ -791,7 +804,7 @@ function CLASS:DoUrlBackgroundCheck()
return
end

self.nextUrlBackgroundCheck = now + 1 + math.random() * 2
self.nextUrlBackgroundCheck = now + 1 + math.random() * 9

if self:GetMuted() then
return
Expand Down Expand Up @@ -823,7 +836,7 @@ function CLASS:DoUrlBackgroundCheck()
self.urlBackgroundCheckRuns = true

self:IsAllowedUrlPair(externalUrl, internalUrl, function(this, isAllowed)
self.nextUrlBackgroundCheck = RealTime() + 1 + math.random() * 2
self.nextUrlBackgroundCheck = RealTime() + 1 + math.random() * 9
self.urlBackgroundCheckRuns = nil

if self:GetMuted() then
Expand All @@ -846,7 +859,7 @@ function CLASS:DoUrlBackgroundCheck()

if not isAllowed then
if not isWhitelistError then
-- Attempt to reconnect respecting the changed rules. It will likely fail and run its relatively complex error handling.
-- Attempt to reconnect respecting the changed rules. It will likely fail and run its complex error handling.
self:Reconnect()
end
else
Expand Down Expand Up @@ -2862,11 +2875,16 @@ function CLASS:OnSearch(url)
return true -- Allow url to be played
end

function CLASS:CanIgnoreWhitelist(url)
function CLASS:CanSkipUrlChecks(url)
-- override
return false -- Ignore the build-in whitelist?
end

function CLASS:CanBypassUrlBlock(url, blockedByHook)
-- override
return false -- Bypass the URL block?
end

function CLASS:OnClose()
-- override
end
Expand Down
5 changes: 3 additions & 2 deletions lua/streamradio_core/client/cl_vgui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,13 @@ function PANEL:GetOrCreateStream()
return true
end

stream.CanIgnoreWhitelist = function( thisStream )
stream.CanSkipUrlChecks = function( thisStream )
if not IsValid( self ) then
return false
end

-- This stream is for the local client only and safe to use. No whitelist is needed here. Avoids UX problems also.
-- This stream is for the local client only and safe to use.
-- No whitelist is needed here. Avoids UX problems also.
return true
end

Expand Down
126 changes: 102 additions & 24 deletions lua/streamradio_core/client/cl_whitelist.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ table.Empty(LIB)
local LIBUtil = StreamRadioLib.Util
local LIBUrl = StreamRadioLib.Url
local LIBNet = StreamRadioLib.Net
local LIBHook = StreamRadioLib.Hook

local g_whitelistCache = LIBUtil.CreateCacheArray(2048)
local g_whitelistCallbacks = {}
Expand All @@ -19,31 +20,41 @@ end)

local g_emptyFunction = function() end

local function callCallbacks(result, url)
local callbacks = g_whitelistCallbacks[url]
local function callCallbacks(url, ...)
local callbacksList = g_whitelistCallbacks[url]
g_whitelistCallbacks[url] = nil

if not callbacks then
if not callbacksList then
return
end

for _, callback in ipairs(callbacks) do
callback(result)
for _, callbacks in pairs(callbacksList) do
for _, callback in ipairs(callbacks) do
callback(...)
end
end
end

LIBNet.Receive("whitelist_check_url_result", function()
local url = net.ReadString()
local result = net.ReadBool()
local blockedByHook = net.ReadBool()

url = LIBUrl.SanitizeUrl(url)
if url == "" then
return
end

g_whitelistCache:Set(url, result)
local now = CurTime()
local lifetime = blockedByHook and 600 or 3600
local expires = now + lifetime

g_whitelistCache:Set(url, {
result = result,
blockedByHook = blockedByHook,
}, expires)

callCallbacks(result, url)
callCallbacks(url, result, blockedByHook)
end)

LIBNet.Receive("whitelist_clear_cache", function()
Expand Down Expand Up @@ -72,59 +83,122 @@ function LIB.AddCheckFunction(name, func)
g_whitelistFunction[name] = func
end

function LIB.IsAllowedSync(url)
function LIB.BuildContext(ent, ply)
context = context or {}

if not IsValid(ent) or not isentity(ent) then
ent = nil
end

if ent and ent.__IsRadio and not IsValid(ply) then
ply = ent:GetRealRadioOwner()
end

if not IsValid(ply) or not ply:IsPlayer() then
ply = nil
end

context.entity = ent
context.player = ply

return context
end

function LIB.SanitizeContext(context)
context = context or {}

local ent = context.entity
local ply = context.player

if not IsValid(ply) or not ply:IsPlayer() then
context.player = LocalPlayer()
end

if not IsValid(ent) or not isentity(ent) then
context.entity = nil
end

return context
end

function LIB.IsAllowedSync(url, context)
url = tostring(url or "")

if url == "" then
return false
return false, false
end

if LIBUrl.IsOfflineURL(url) then
return true
return true, false
end

url = LIBUrl.SanitizeOnlineUrl(url)
if url == "" then
return false
return false, false
end

context = LIB.SanitizeContext(context)

local now = CurTime()

local cacheItem = g_whitelistCache:Get(url, now)
if cacheItem then
-- Use cached result instead of asking the server again

local result = cacheItem.result or false
local blockedByHook = cacheItem.blockedByHook or false

return result, blockedByHook
end

local ply = context.player
local ent = context.entity

local isAllowed = LIBHook.RunCustom("UrlIsAllowed", url, ply, ent)

if isAllowed == false then
return false, true
end

if not StreamRadioLib.IsUrlWhitelistEnabled() then
-- allow all URLs if the whitelist is disabled
return true
return nil, false
end

if callCheckFunctions(url) then
return true
return true, false
end

if g_whitelistCache:Has(url) then
local result = g_whitelistCache:Get(url)
return result
end

return nil
return nil, nil
end

function LIB.IsAllowedAsync(url, callback)
function LIB.IsAllowedAsync(url, context, callback)
url = tostring(url or "")
callback = callback or g_emptyFunction

local result = LIB.IsAllowedSync(url)
context = LIB.SanitizeContext(context)
local ent = context.entity or NULL

local result, blockedByHook = LIB.IsAllowedSync(url, context)

if result ~= nil then
callback(result)
callback(result, blockedByHook or false)
return
end

local callbacks = g_whitelistCallbacks[url] or {}
g_whitelistCallbacks[url] = callbacks
local callbacksList = g_whitelistCallbacks[url] or {}
g_whitelistCallbacks[url] = callbacksList

local callbacks = callbacksList[ent] or {}
callbacksList[ent] = callbacks

local hasSend = #callbacks > 0
table.insert(callbacks, callback)

if not hasSend then
LIBNet.Start("whitelist_check_url")
net.WriteString(url)
net.WriteEntity(ent)
net.SendToServer()
end
end
Expand Down Expand Up @@ -185,5 +259,9 @@ function LIB.QuickWhitelistRemove(url)
g_whitelistCache:Remove(url)
end

function LIB.Load()
LIB.InvalidateCache()
end

return true

Loading

0 comments on commit 08a75b5

Please sign in to comment.