-- RestrictedFrames.lua (Part of the new Secure Headers implementation) -- -- Handle objects to access frames during restricted execution in order to -- allow safe manipulation of frame properties. These handles can be passed -- around safely without allowing their destinations or functions to be -- tampered with. -- -- Daniel Stephens (iriel@vigilance-committee.org) -- Nevin Flanagan (alestane@comcast.net) -- -- The design consists of several components... -- -- HANDLES: These are lightweight userdata objects with a shared metatable -- that stand in for frames. They're persistent once created (i.e. -- a given frame always has the same handle). Internally these map -- to a 'copied' frame object. Only explcitly protected frames can -- be assigned handles. -- -- METHODS: The handler methods are contained in a table which is then set -- as the __index on the handlers. These are in two groups, there -- are 'read' methods that may be blocked in some situations, and -- there are 'write' methods which simply require that an execution -- is active. -- -- Various methods (SetPoint/SetParent) take frame handles as relative -- frame arguments also. You can safely obtain frame handles (out of combat) -- by setting the '_frame-' attribute on a header frame to the frame -- you want a handle to, and it'll set the 'frameref-' attribute to be -- the handle to that frame (or nil if it's invalid or unprotected). -- This handle can then be retrieved using a GetAttribute call. --------------------------------------------------------------------------- local issecure = issecure; local select = select; local type = type; local unpack = unpack; local wipe = wipe; local pairs = pairs; local newproxy = newproxy; local error = error; local tostring = tostring; local tonumber = tonumber; local string = string; local rawget = rawget; local securecall = securecall; local GetCursorPosition = GetCursorPosition; local InCombatLockdown = InCombatLockdown; --------------------------------------------------------------------------- -- Frame Handles -- Userdata handles referencing explicitly protected frames -- -- LOCAL_FrameHandle_Protected_Frames -- handle keys, frame surrogate values -- (explicitly protected) -- LOCAL_FrameHandle_Other_Frames -- handle keys, frame surrogate values -- (possibly protected) -- LOCAL_FrameHandle_Lookup -- frame keys, handle values -- -- The lookup table auto-populates via an __index metamethod local LOCAL_FrameHandle_Protected_Frames = {}; local LOCAL_FrameHandle_Other_Frames = {}; local LOCAL_FrameHandle_Lookup = {}; setmetatable(LOCAL_FrameHandle_Protected_Frames, { __mode = "k" }); setmetatable(LOCAL_FrameHandle_Other_Frames, { __mode = "k" }); -- Setup metatable for prototype object local LOCAL_FrameHandle_Prototype = newproxy(true); -- HANDLE is the frame handle method namespace (populated below) local HANDLE = {}; do local meta = getmetatable(LOCAL_FrameHandle_Prototype); meta.__index = HANDLE; meta.__metatable = false; end function IsFrameHandle(handle, protected) local surrogate = LOCAL_FrameHandle_Protected_Frames[handle]; if ((surrogate == nil) and not protected) then surrogate = LOCAL_FrameHandle_Other_Frames[handle]; end return (surrogate ~= nil); end function GetFrameHandleFrame(handle, protected) local surrogate = LOCAL_FrameHandle_Protected_Frames[handle]; if ((surrogate == nil) and (not protected or not InCombatLockdown())) then surrogate = LOCAL_FrameHandle_Other_Frames[handle]; end if (surrogate ~= nil) then return surrogate[1]; end end local function FrameHandleLookup_index(t, frame) -- Create a 'surrogate' frame object local surrogate = { [0] = frame[0], [1] = frame }; setmetatable(surrogate, getmetatable(frame)); -- Test whether the frame is explcitly protected local _, protected = surrogate:IsProtected(); if (not issecure()) then return; end local handle = newproxy(LOCAL_FrameHandle_Prototype); LOCAL_FrameHandle_Lookup[frame] = handle; if (protected) then LOCAL_FrameHandle_Protected_Frames[handle] = surrogate; else LOCAL_FrameHandle_Other_Frames[handle] = surrogate; end return handle; end setmetatable(LOCAL_FrameHandle_Lookup, { __index = FrameHandleLookup_index; }); -- Gets the handle for a frame (if available) function GetFrameHandle(frame, protected) local handle = LOCAL_FrameHandle_Lookup[frame]; if (protected and (frame ~= nil)) then if (not LOCAL_FrameHandle_Protected_Frames[handle]) then return nil; end end return handle; end --------------------------------------------------------------------------- -- Action implementation support function -- -- GetHandleFrame -- Get the frame for a handle local function GetUnprotectedHandleFrame(handle) local frame = LOCAL_FrameHandle_Protected_Frames[handle]; if (not frame) then frame = LOCAL_FrameHandle_Other_Frames[handle]; if (not frame) then error("Invalid frame handle"); return; end end return frame; end local function GetHandleFrame(handle) local frame = LOCAL_FrameHandle_Protected_Frames[handle]; if (not frame) then frame = LOCAL_FrameHandle_Other_Frames[handle]; if (frame) then if (frame:IsProtected() or not InCombatLockdown()) then return frame; end end error("Invalid frame handle"); return; end return frame; end --------------------------------------------------------------------------- -- "GETTER" methods function HANDLE:GetName() return GetUnprotectedHandleFrame(self):GetName() end function HANDLE:GetID() return GetHandleFrame(self):GetID() end function HANDLE:IsShown() return GetHandleFrame(self):IsShown() end function HANDLE:IsVisible() return GetHandleFrame(self):IsVisible() end function HANDLE:GetWidth() return GetHandleFrame(self):GetWidth() end function HANDLE:GetHeight() return GetHandleFrame(self):GetHeight() end function HANDLE:GetRect() return GetHandleFrame(self):GetRect() end function HANDLE:GetScale() return GetHandleFrame(self):GetScale() end function HANDLE:GetEffectiveScale() return GetHandleFrame(self):GetEffectiveScale() end -- Cannot expose GetAlpha since alpha is not protected function HANDLE:GetFrameLevel() return GetHandleFrame(self):GetFrameLevel() end function HANDLE:GetObjectType() return GetUnprotectedHandleFrame(self):GetObjectType() end function HANDLE:IsObjectType(ot) return GetUnprotectedHandleFrame(self):IsObjectType(tostring(ot)) end function HANDLE:IsProtected() return GetUnprotectedHandleFrame(self):IsProtected(); end function HANDLE:GetAttribute(name) if (type(name) ~= "string" or name:match("^_")) then return; end local val = GetHandleFrame(self):GetAttribute(name) local tv = type(val); if (tv == "string" or tv == "number" or tv == "boolean" or val == nil) then return val; end if (tv == "userdata" and (LOCAL_FrameHandle_Protected_Frames[val] or LOCAL_FrameHandle_Other_Frames[val])) then return val; end return nil; end function HANDLE:GetFrameRef(label) if (type(label) ~= "string") then return; end local val = GetHandleFrame(self):GetAttribute("frameref-" .. label); local tv = type(val); if (tv == "userdata" and (LOCAL_FrameHandle_Protected_Frames[val] or LOCAL_FrameHandle_Other_Frames[val])) then return val; end return nil; end function HANDLE:GetEffectiveAttribute(name, button, prefix, suffix) if (type(name) ~= "string" or name:match("^_")) then return; end if (button ~= nil) then button = tostring(button) end if (prefix ~= nil) then prefix = tostring(prefix) if (prefix:match("^_")) then prefix = nil; end end if (suffix ~= nil) then suffix = tostring(suffix) end local val = SecureButton_GetModifiedAttribute(GetHandleFrame(self), name, button, prefix, suffix); local tv = type(val); if (tv == "string" or tv == "number" or tv == "boolean" or val == nil) then return val; end if (tv == "userdata" and (LOCAL_FrameHandle_Protected_Frames[val] or LOCAL_FrameHandle_Other_Frames[val])) then return val; end return nil; end local function FrameHandleMapper(nolockdown, frame, nextFrame, ...) if (not frame) then return; end -- Do an explicit protection check to avoid errors from -- the frame handle lookup local p = nolockdown; if (not p) then p = frame:IsProtected(); end if (p) then frame = LOCAL_FrameHandle_Lookup[frame]; if (frame) then if (nextFrame) then return frame, FrameHandleMapper(nolockdown, nextFrame, ...); else return frame; end end end if (nextFrame) then return FrameHandleMapper(nolockdown, nextFrame, ...); end end local function FrameHandleInserter(result, ...) local nolockdown = not InCombatLockdown(); local idx = #result; for i = 1, select('#', ...) do local frame = select(i, ...); -- Do an explicit protection check to avoid errors from -- the frame handle lookup local p = nolockdown; if (not p) then p = frame:IsProtected(); end if (p) then frame = LOCAL_FrameHandle_Lookup[frame]; if (frame) then idx = idx + 1; result[idx] = frame; end end end return result; end function HANDLE:GetChildren() return FrameHandleMapper(not InCombatLockdown(), GetHandleFrame(self):GetChildren()); end function HANDLE:GetChildList(tbl) return FrameHandleInserter(tbl, GetHandleFrame(self):GetChildren()); end function HANDLE:GetParent() return FrameHandleMapper(not InCombatLockdown(), GetHandleFrame(self):GetParent()); end -- NOTE: Cannot allow the frame to figure out if it has mouse focus -- because an insecure frame could be appearing on top of it. function HANDLE:GetMousePosition() local frame = GetHandleFrame(self); local x, y = GetCursorPosition() local l, b, w, h = frame:GetRect() if (not w or not h or w == 0 or h == 0) then return nil; end local e = frame:GetEffectiveScale(); x, y = x / e, y /e; x = x - l y = y - b if x < 0 or x > w or y < 0 or y > h then return nil else return x / w, y / h end end -- Used only for recursive check, since it's more expensive -- -- Only check protected and visible children local function RF_CheckUnderMouse(x, y, ...) for i = 1, select('#', ...) do local frame = select(i, ...); if (frame and frame:IsProtected() and frame:IsVisible()) then local l, b, w, h = frame:GetRect() if (w and h) then local e = frame:GetEffectiveScale(); local fx = x / e - l; if ((fx >= 0) and (fx <= w)) then local fy = y / e - b; if ((fy >= 0) and (fy <= h)) then return true; end end end if (RF_CheckUnderMouse(x, y, frame:GetChildren())) then return true; end end end end function HANDLE:IsUnderMouse(recursive) local frame = GetHandleFrame(self); local x, y = GetCursorPosition(); local l, b, w, h = frame:GetRect() if (w and h) then local e = frame:GetEffectiveScale(); local fx = x / e - l; if ((fx >= 0) and (fx <= w)) then local fy = y / e - b; if ((fy >= 0) and (fy <= h)) then return true; end end end if (not recursive) then return; end return RF_CheckUnderMouse(x, y, frame:GetChildren()); end function HANDLE:GetNumPoints() return GetHandleFrame(self):GetNumPoints(); end function HANDLE:GetPoint(i) local point, frame, relative, dx, dy = GetHandleFrame(self):GetPoint(i); local handle; if (frame) then handle = FrameHandleMapper(not InCombatLockdown(), frame); end if (handle or not frame) then return point, handle, relative, dx, dy; end end --------------------------------------------------------------------------- -- "SETTER" methods and actions function HANDLE:Show(skipAttr) local frame = GetHandleFrame(self); frame:Show(); if (not skipAttr) then frame:SetAttribute("statehidden", nil); end end function HANDLE:Hide(skipAttr) local frame = GetHandleFrame(self); frame:Hide(); if (not skipAttr) then frame:SetAttribute("statehidden", true); end end function HANDLE:SetID(id) GetHandleFrame(self):SetID(tonumber(id) or 0); end function HANDLE:SetWidth(width) GetHandleFrame(self):SetWidth(tonumber(width)); end function HANDLE:SetHeight(height) GetHandleFrame(self):SetHeight(tonumber(height)); end function HANDLE:SetScale(scale) GetHandleFrame(self):SetScale(tonumber(scale)); end function HANDLE:SetAlpha(alpha) GetHandleFrame(self):SetAlpha(tonumber(alpha)); end local _set_points = { TOP=true; BOTTOM=true; LEFT=true; RIGHT=true; CENTER=true; TOPLEFT=true; BOTTOMLEFT=true; TOPRIGHT=true; BOTTOMRIGHT=true; }; function HANDLE:ClearAllPoints() GetHandleFrame(self):ClearAllPoints(); end function HANDLE:SetPoint(point, relframe, relpoint, xofs, yofs) if (type(relpoint) == "number") then relpoint, xofs, yofs = nil, relpoint, xofs; end if (relpoint == nil) then relpoint = point; end if ((xofs == nil) and (yofs == nil)) then xofs, yofs = 0, 0; else xofs, yofs = tonumber(xofs), tonumber(yofs); end if (not _set_points[point]) then error("Invalid point '" .. tostring(point) .. "'"); return; end if (not _set_points[relpoint]) then error("Invalid relative point '" .. tostring(relpoint) .. "'"); return; end if (not (xofs and yofs)) then error("Invalid offset"); return end local frame = GetHandleFrame(self); local realrelframe = nil; if (type(relframe) == "userdata") then realrelframe = LOCAL_FrameHandle_Protected_Frames[relframe]; if (not realrelframe) then error("Invalid relative frame handle"); return; end elseif ((relframe == nil) or (relframe == "$screen")) then realrelframe = nil; elseif (relframe == "$cursor") then local cx, cy = GetCursorPosition(); local eff = frame:GetEffectiveScale(); xofs = xofs + (cx / eff); yofs = yofs + (cy / eff); relpoint = "BOTTOMLEFT"; realrelframe = nil; elseif (relframe == "$parent") then realrelframe = frame:GetParent(); else error("Invalid relative frame id '" .. tostring(relframe) .. "'"); return; end frame:SetPoint(point, realrelframe, relpoint, xofs, yofs); end function HANDLE:SetAllPoints(relframe) local frame = GetHandleFrame(self); local realrelframe = nil; if (type(relframe) == "userdata") then realrelframe = LOCAL_FrameHandle_Protected_Frames[relframe]; if (not realrelframe) then error("Invalid relative frame handle"); return; end elseif ((relframe == nil) or (relframe == "$screen")) then realrelframe = nil; elseif (relframe == "$parent") then realrelframe = frame:GetParent(); else error("Invalid relative frame id '" .. tostring(relframe) .. "'"); return; end frame:SetAllPoints(realrelframe); end function HANDLE:SetAttribute(name, value) if (type(name) ~= "string" or name:match("^_")) then error("Invalid attribute name"); return; end local tv = type(value); if (tv ~= "string" and tv ~= "nil" and tv ~= "number" and tv ~= "boolean") then if (not (tv == "userdata" and (LOCAL_FrameHandle_Protected_Frames[value] or LOCAL_FrameHandle_Other_Frames[value]))) then error("Invalid attribute value"); return; end end GetHandleFrame(self):SetAttribute(name, value); end function HANDLE:ClearBindings() ClearOverrideBindings(GetHandleFrame(self)); end function HANDLE:ClearBinding(key) SetOverrideBinding(GetHandleFrame(self), true, key, nil); end function HANDLE:SetBindingClick(priority, key, name, button) local tn = type(name); if (tn == "userdata") then if (LOCAL_FrameHandle_Protected_Frames[name] or LOCAL_FrameHandle_Other_Frames[name]) then name = name:GetName(); tn = type(name); end end if (tn ~= "string" or name:match(":")) then error("Invalid click target name"); return; end if ((button ~= nil) and type(button) ~= "string") then error("Invalid button name"); return; end SetOverrideBindingClick(GetHandleFrame(self), priority, key, name, button); end function HANDLE:SetBinding(priority, key, action) if (action ~= nil and type(action) ~= "string") then error("Invalid binding action"); return; end SetOverrideBinding(GetHandleFrame(self), priority, key, action); end function HANDLE:SetBindingSpell(priority, key, spell) if (type(spell) ~= "string") then error("Invalid binding spell"); return; end SetOverrideBindingSpell(GetHandleFrame(self), priority, key, spell); end function HANDLE:SetBindingMacro(priority, key, macro) if (type(macro) == "number") then macro = tostring(macro); elseif (type(macro) ~= "string") then error("Invalid binding macro"); return; end SetOverrideBindingMacro(GetHandleFrame(self), priority, key, macro); end function HANDLE:SetBindingItem(priority, key, item) if (type(item) ~= "string") then error("Invalid binding item"); return; end SetOverrideBindingItem(GetHandleFrame(self), priority, key, item); end function HANDLE:Raise() GetHandleFrame(self):Raise(); end function HANDLE:Lower() GetHandleFrame(self):Lower(); end function HANDLE:SetFrameLevel(level) GetHandleFrame(self):SetFrameLevel(tonumber(level)); end function HANDLE:SetParent(handle) local parent = nil; if (handle ~= nil) then if (type(handle) ~= "userdata") then error("Invalid frame handle for SetParent"); return; end parent = LOCAL_FrameHandle_Protected_Frames[handle]; if (not parent) then error("Invalid frame handle for SetParent"); return; end end GetHandleFrame(self):SetParent(parent); end function HANDLE:RegisterAutoHide(duration) RegisterAutoHide(GetHandleFrame(self), tonumber(duration)); end function HANDLE:UnregisterAutoHide() UnregisterAutoHide(GetHandleFrame(self)); end function HANDLE:AddToAutoHide(handle) if (type(handle) ~= "userdata") then error("Invalid frame handle for AddToAutoHide"); return; end local child = LOCAL_FrameHandle_Protected_Frames[handle]; if (not child) then error("Invalid frame handle for AddToAutoHide"); return; end AddToAutoHide(GetHandleFrame(self), child); end --------------------------------------------------------------------------- -- Type specific methods function HANDLE:Disable() local frame = GetHandleFrame(self); if (not frame:IsObjectType("Button")) then error("Frame is not a Button"); return; end frame:Disable(); end function HANDLE:Enable() local frame = GetHandleFrame(self); if (not frame:IsObjectType("Button")) then error("Frame is not a Button"); return; end frame:Enable(); end