Skip to content

Commit 08a75b5

Browse files
committed
Added serverside logging for stream URLs, added hook 3DStreamRadio_UrlIsAllowed.
1 parent 4b03ffe commit 08a75b5

File tree

16 files changed

+388
-94
lines changed

16 files changed

+388
-94
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
446
2-
1725659185
1+
447
2+
1731972171

lua/entities/base_streamradio.lua

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,12 @@ function ENT:GetOrCreateStream()
162162
return call("StreamOnSearch", ...)
163163
end
164164

165-
stream.CanIgnoreWhitelist = function( ... )
166-
return call("StreamCanIgnoreWhitelist", ...)
165+
stream.CanSkipUrlChecks = function( ... )
166+
return call("StreamCanSkipUrlChecks", ...)
167+
end
168+
169+
stream.CanBypassUrlBlock = function( ... )
170+
return call("StreamCanBypassUrlBlock", ...)
167171
end
168172

169173
stream.OnMute = function( ... )
@@ -202,7 +206,16 @@ function ENT:StreamOnSearch()
202206
return true
203207
end
204208

205-
function ENT:StreamCanIgnoreWhitelist()
209+
function ENT:StreamCanSkipUrlChecks()
210+
return false
211+
end
212+
213+
function ENT:StreamCanBypassUrlBlock(blockedByHook)
214+
if blockedByHook then
215+
-- was blocked by external code
216+
return false
217+
end
218+
206219
if not StreamRadioLib.IsUrlWhitelistAdminRadioTrusted() then
207220
return false
208221
end

lua/streamradio_core/_include.lua

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ StreamRadioLib.Url.Load()
8181
StreamRadioLib.Interface.Load()
8282
StreamRadioLib.Filesystem.Load()
8383

84-
if SERVER then
85-
StreamRadioLib.Whitelist.Load()
86-
end
84+
StreamRadioLib.Whitelist.Load()
8785

8886
StreamRadioLib.Cfchttp.Load()
8987
StreamRadioLib.Cache.Load()

lua/streamradio_core/_load.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,13 @@ local function loadAddon()
270270
-- Sometimes the version is not known, yet.
271271

272272
if CLIENT then
273-
local NEED_VERSION = 240730
273+
local NEED_VERSION = 241029
274274

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

281281
if VERSION < NEED_VERSION then
282282
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)

lua/streamradio_core/classes/rendertarget.lua

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,15 @@ function CLASS:CreateRendertarget()
298298

299299
-- No ENUMS for thise values are available in the game.
300300
-- https://wiki.facepunch.com/gmod/Enums/TEXTUREFLAGS
301-
local textureFlags = bit.bor(4, 8, 16, 32, 512, 2048, 8192, 32768)
301+
local textureFlags = bit.bor(
302+
4, -- TEXTUREFLAGS_CLAMPS
303+
8, -- TEXTUREFLAGS_CLAMPT
304+
16, -- TEXTUREFLAGS_ANISOTROPIC
305+
32, -- TEXTUREFLAGS_HINT_DXT5
306+
512, -- TEXTUREFLAGS_NOLOD
307+
8192, -- TEXTUREFLAGS_EIGHTBITALPHA
308+
32768 -- TEXTUREFLAGS_RENDERTARGET
309+
)
302310

303311
local tex = GetRenderTargetEx(
304312
name, w, h,

lua/streamradio_core/classes/stream.lua

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ local retry_errors_block = {
9191

9292
local retry_errors_urlblocked = {
9393
[LIBError.STREAM_ERROR_URL_NOT_WHITELISTED] = true,
94+
[LIBError.STREAM_ERROR_URL_BLOCKED] = true,
9495
}
9596

9697
local function loadLibs()
@@ -757,19 +758,31 @@ function CLASS:IsAllowedInternalUrl(url, callback, logFailure)
757758
end
758759

759760
function CLASS:IsAllowedExternalUrl(url, callback)
760-
if self:CallHook("CanIgnoreWhitelist", url) then
761-
-- Sometimes we don't want/need to check the addon's whitelist
762-
-- E.g. when the owner of the radio entity is an admin.
763-
761+
if self:CallHook("CanSkipUrlChecks", url) then
762+
-- Sometimes we want to ignore the addon's whitelist
764763
callback(self, true, nil)
765764
return
766765
end
767766

768-
StreamRadioLib.Whitelist.IsAllowedAsync(url, function(allowed)
767+
local ent = self:GetEntity()
768+
local context = StreamRadioLib.Whitelist.BuildContext(ent)
769+
770+
StreamRadioLib.Whitelist.IsAllowedAsync(url, context, function(allowed, blockedByHook)
769771
if not IsValid(self) then return end
770772

771773
if not allowed then
772-
callback(self, allowed, LIBError.STREAM_ERROR_URL_NOT_WHITELISTED)
774+
if self:CallHook("CanBypassUrlBlock", url, blockedByHook) then
775+
-- Sometimes we want to ignore the block, but still to perform the checks.
776+
callback(self, true, nil)
777+
return
778+
end
779+
780+
if blockedByHook then
781+
callback(self, allowed, LIBError.STREAM_ERROR_URL_BLOCKED)
782+
return
783+
end
784+
785+
callback(self, false, LIBError.STREAM_ERROR_URL_NOT_WHITELISTED)
773786
return
774787
end
775788

@@ -791,7 +804,7 @@ function CLASS:DoUrlBackgroundCheck()
791804
return
792805
end
793806

794-
self.nextUrlBackgroundCheck = now + 1 + math.random() * 2
807+
self.nextUrlBackgroundCheck = now + 1 + math.random() * 9
795808

796809
if self:GetMuted() then
797810
return
@@ -823,7 +836,7 @@ function CLASS:DoUrlBackgroundCheck()
823836
self.urlBackgroundCheckRuns = true
824837

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

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

847860
if not isAllowed then
848861
if not isWhitelistError then
849-
-- Attempt to reconnect respecting the changed rules. It will likely fail and run its relatively complex error handling.
862+
-- Attempt to reconnect respecting the changed rules. It will likely fail and run its complex error handling.
850863
self:Reconnect()
851864
end
852865
else
@@ -2862,11 +2875,16 @@ function CLASS:OnSearch(url)
28622875
return true -- Allow url to be played
28632876
end
28642877

2865-
function CLASS:CanIgnoreWhitelist(url)
2878+
function CLASS:CanSkipUrlChecks(url)
28662879
-- override
28672880
return false -- Ignore the build-in whitelist?
28682881
end
28692882

2883+
function CLASS:CanBypassUrlBlock(url, blockedByHook)
2884+
-- override
2885+
return false -- Bypass the URL block?
2886+
end
2887+
28702888
function CLASS:OnClose()
28712889
-- override
28722890
end

lua/streamradio_core/client/cl_vgui.lua

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,13 @@ function PANEL:GetOrCreateStream()
325325
return true
326326
end
327327

328-
stream.CanIgnoreWhitelist = function( thisStream )
328+
stream.CanSkipUrlChecks = function( thisStream )
329329
if not IsValid( self ) then
330330
return false
331331
end
332332

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

lua/streamradio_core/client/cl_whitelist.lua

Lines changed: 102 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ table.Empty(LIB)
88
local LIBUtil = StreamRadioLib.Util
99
local LIBUrl = StreamRadioLib.Url
1010
local LIBNet = StreamRadioLib.Net
11+
local LIBHook = StreamRadioLib.Hook
1112

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

2021
local g_emptyFunction = function() end
2122

22-
local function callCallbacks(result, url)
23-
local callbacks = g_whitelistCallbacks[url]
23+
local function callCallbacks(url, ...)
24+
local callbacksList = g_whitelistCallbacks[url]
2425
g_whitelistCallbacks[url] = nil
2526

26-
if not callbacks then
27+
if not callbacksList then
2728
return
2829
end
2930

30-
for _, callback in ipairs(callbacks) do
31-
callback(result)
31+
for _, callbacks in pairs(callbacksList) do
32+
for _, callback in ipairs(callbacks) do
33+
callback(...)
34+
end
3235
end
3336
end
3437

3538
LIBNet.Receive("whitelist_check_url_result", function()
3639
local url = net.ReadString()
3740
local result = net.ReadBool()
41+
local blockedByHook = net.ReadBool()
3842

3943
url = LIBUrl.SanitizeUrl(url)
4044
if url == "" then
4145
return
4246
end
4347

44-
g_whitelistCache:Set(url, result)
48+
local now = CurTime()
49+
local lifetime = blockedByHook and 600 or 3600
50+
local expires = now + lifetime
51+
52+
g_whitelistCache:Set(url, {
53+
result = result,
54+
blockedByHook = blockedByHook,
55+
}, expires)
4556

46-
callCallbacks(result, url)
57+
callCallbacks(url, result, blockedByHook)
4758
end)
4859

4960
LIBNet.Receive("whitelist_clear_cache", function()
@@ -72,59 +83,122 @@ function LIB.AddCheckFunction(name, func)
7283
g_whitelistFunction[name] = func
7384
end
7485

75-
function LIB.IsAllowedSync(url)
86+
function LIB.BuildContext(ent, ply)
87+
context = context or {}
88+
89+
if not IsValid(ent) or not isentity(ent) then
90+
ent = nil
91+
end
92+
93+
if ent and ent.__IsRadio and not IsValid(ply) then
94+
ply = ent:GetRealRadioOwner()
95+
end
96+
97+
if not IsValid(ply) or not ply:IsPlayer() then
98+
ply = nil
99+
end
100+
101+
context.entity = ent
102+
context.player = ply
103+
104+
return context
105+
end
106+
107+
function LIB.SanitizeContext(context)
108+
context = context or {}
109+
110+
local ent = context.entity
111+
local ply = context.player
112+
113+
if not IsValid(ply) or not ply:IsPlayer() then
114+
context.player = LocalPlayer()
115+
end
116+
117+
if not IsValid(ent) or not isentity(ent) then
118+
context.entity = nil
119+
end
120+
121+
return context
122+
end
123+
124+
function LIB.IsAllowedSync(url, context)
76125
url = tostring(url or "")
77126

78127
if url == "" then
79-
return false
128+
return false, false
80129
end
81130

82131
if LIBUrl.IsOfflineURL(url) then
83-
return true
132+
return true, false
84133
end
85134

86135
url = LIBUrl.SanitizeOnlineUrl(url)
87136
if url == "" then
88-
return false
137+
return false, false
138+
end
139+
140+
context = LIB.SanitizeContext(context)
141+
142+
local now = CurTime()
143+
144+
local cacheItem = g_whitelistCache:Get(url, now)
145+
if cacheItem then
146+
-- Use cached result instead of asking the server again
147+
148+
local result = cacheItem.result or false
149+
local blockedByHook = cacheItem.blockedByHook or false
150+
151+
return result, blockedByHook
152+
end
153+
154+
local ply = context.player
155+
local ent = context.entity
156+
157+
local isAllowed = LIBHook.RunCustom("UrlIsAllowed", url, ply, ent)
158+
159+
if isAllowed == false then
160+
return false, true
89161
end
90162

91163
if not StreamRadioLib.IsUrlWhitelistEnabled() then
92164
-- allow all URLs if the whitelist is disabled
93-
return true
165+
return nil, false
94166
end
95167

96168
if callCheckFunctions(url) then
97-
return true
169+
return true, false
98170
end
99171

100-
if g_whitelistCache:Has(url) then
101-
local result = g_whitelistCache:Get(url)
102-
return result
103-
end
104-
105-
return nil
172+
return nil, nil
106173
end
107174

108-
function LIB.IsAllowedAsync(url, callback)
175+
function LIB.IsAllowedAsync(url, context, callback)
109176
url = tostring(url or "")
110177
callback = callback or g_emptyFunction
111178

112-
local result = LIB.IsAllowedSync(url)
179+
context = LIB.SanitizeContext(context)
180+
local ent = context.entity or NULL
181+
182+
local result, blockedByHook = LIB.IsAllowedSync(url, context)
113183

114184
if result ~= nil then
115-
callback(result)
185+
callback(result, blockedByHook or false)
116186
return
117187
end
118188

119-
local callbacks = g_whitelistCallbacks[url] or {}
120-
g_whitelistCallbacks[url] = callbacks
189+
local callbacksList = g_whitelistCallbacks[url] or {}
190+
g_whitelistCallbacks[url] = callbacksList
191+
192+
local callbacks = callbacksList[ent] or {}
193+
callbacksList[ent] = callbacks
121194

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

125198
if not hasSend then
126199
LIBNet.Start("whitelist_check_url")
127200
net.WriteString(url)
201+
net.WriteEntity(ent)
128202
net.SendToServer()
129203
end
130204
end
@@ -185,5 +259,9 @@ function LIB.QuickWhitelistRemove(url)
185259
g_whitelistCache:Remove(url)
186260
end
187261

262+
function LIB.Load()
263+
LIB.InvalidateCache()
264+
end
265+
188266
return true
189267

0 commit comments

Comments
 (0)