Make NanoUI resistant to Topic spoofs

Move Topic() into a NanoUI-specific ui_act proc
Update to @YotaXP's latest JSON code.
Return focus to the mapwindow if a key is pressed in a NanoUI.
This commit is contained in:
Bjorn Neergaard
2015-12-15 22:37:52 -06:00
parent 070a081db8
commit da4842dddf
29 changed files with 354 additions and 168 deletions
+13 -12
View File
@@ -173,6 +173,19 @@ You can set verify to TRUE if you want send() to sleep until the client has the
//DEFINITIONS FOR ASSET DATUMS START HERE.
/datum/asset/simple/nanoui
assets = list(
"nanoui.lib.js" = 'nano/assets/nanoui.lib.js',
"nanoui.main.js" = 'nano/assets/nanoui.main.js',
"nanoui.templates.js" = 'nano/assets/nanoui.templates.js',
"nanoui.lib.css" = 'nano/assets/nanoui.lib.css',
"nanoui.common.css" = 'nano/assets/nanoui.common.css',
"nanoui.generic.css" = 'nano/assets/nanoui.generic.css',
"nanoui.nanotrasen.css" = 'nano/assets/nanoui.nanotrasen.css',
"fontawesome-webfont.eot" = 'nano/assets/fontawesome-webfont.eot',
"fontawesome-webfont.woff2" = 'nano/assets/fontawesome-webfont.woff2'
)
/datum/asset/simple/pda
assets = list(
"pda_atmos.png" = 'icons/pda_icons/pda_atmos.png',
@@ -219,18 +232,6 @@ You can set verify to TRUE if you want send() to sleep until the client has the
"large_stamp-law.png" = 'icons/stamp_icons/large_stamp-law.png'
)
/datum/asset/simple/nanoui
assets = list(
"nanoui.lib.js" = 'nano/assets/nanoui.lib.js',
"nanoui.main.js" = 'nano/assets/nanoui.main.js',
"nanoui.templates.js" = 'nano/assets/nanoui.templates.js',
"nanoui.lib.css" = 'nano/assets/nanoui.lib.css',
"nanoui.common.css" = 'nano/assets/nanoui.common.css',
"nanoui.generic.css" = 'nano/assets/nanoui.generic.css',
"nanoui.nanotrasen.css" = 'nano/assets/nanoui.nanotrasen.css',
"fontawesome-webfont.eot" = 'nano/assets/fontawesome-webfont.eot',
"fontawesome-webfont.woff2" = 'nano/assets/fontawesome-webfont.woff2'
)
//Registers HTML Interface assets.
/datum/asset/HTML_interface/register()
+201 -2
View File
@@ -1,6 +1,14 @@
/var/datum/jsonHelper/_jsonHelper = new // Solely as a namespace for procs.
/* Usage:
JSON.stringify(obj) - Converts lists and values into a JSON string.
JSON.parse(json) - Converts a JSON string into lists and values.
*/
/var/datum/jsonHelper/JSON = new // A namespace for procs.
// ************************************ WRITER ************************************
/datum/jsonHelper/proc/stringify(value)
return list2text(WriteValue(list(), value))
/datum/jsonHelper/proc/WriteValue(list/json, value)
. = json
if(isnum(value))
@@ -82,4 +90,195 @@
#undef Either
#undef CannotBeFlat
#undef CannotBeAssoc
#undef BadList
#undef BadList
// ************************************ READER ************************************
#define aBackspace 0x08
#define aTab 0x09
#define aLineBreak 0x0A
#define aVertTab 0x0B
#define aFormFeed 0x0C
#define aCarriageReturn 0x0D
#define aSpace 0x20
#define aZero 0x30
#define aNonBreakSpace 0xA0
#define Advance if(++readPos > jsonLen) { curAscii = 0; curChar = "" } else { curAscii = text2ascii(json, readPos); curChar = ascii2text(curAscii) } // Deal with it.
#define SkipWhitespace while(curAscii in whitespace) Advance
#define AdvanceWS Advance; SkipWhitespace
/datum/jsonHelper/var
readPos
jsonLen
json
curAscii
curChar
static/list/whitespace = list(aTab, aLineBreak, aVertTab, aFormFeed, aCarriageReturn, aSpace, aNonBreakSpace)
/datum/jsonHelper/proc/parse(json)
readPos = 0
jsonLen = length(json)
src.json = json
curAscii = 0
curChar = ""
AdvanceWS
var/value = ParseValue()
if(readPos < jsonLen)
throw EXCEPTION("Expected: End of JSON")
return value
/datum/jsonHelper/proc/ParseValue()
if(curChar == "\"")
return ParseString()
else if(curChar == "-" || (curAscii >= aZero && curAscii <= aZero + 9))
return ParseNumber()
else if(curChar == "{")
return ParseObject()
else if(curChar == "\[")
return ParseArray()
else if(curChar == "t")
if(copytext(json, readPos, readPos+4) == "true")
readPos += 3
AdvanceWS
return TRUE
else
throw EXCEPTION("Expected: 'true'")
else if(curChar == "f")
if(copytext(json, readPos, readPos+5) == "false")
readPos += 4
AdvanceWS
return FALSE
else
throw EXCEPTION("Expected: 'false'")
else if(curChar == "n")
if(copytext(json, readPos, readPos+4) == "null")
readPos += 3
AdvanceWS
return null
else
throw EXCEPTION("Expected: 'null'")
else if(curChar == "")
throw EXCEPTION("Unexpected: End of JSON")
else
throw EXCEPTION("Unexpected: '[curChar]'")
/datum/jsonHelper/proc/ParseString()
ASSERT(curChar == "\"")
Advance
var/list/chars = list()
while(readPos <= jsonLen)
if(curChar == "\"")
AdvanceWS
return list2text(chars)
else if(curChar == "\\")
Advance
switch(curChar)
if("\"", "\\", "/")
chars += ascii2text(curAscii)
if("b")
chars += ascii2text(aBackspace)
if("f")
chars += ascii2text(aFormFeed)
if("n")
chars += "\n"
if("r")
chars += ascii2text(aCarriageReturn) // Should we ignore these?
if("t")
chars += "\t"
if("u")
throw EXCEPTION("JSON \\uXXXX escape sequence not supported")
else
throw EXCEPTION("Invalid escape sequence")
Advance
else
chars += ascii2text(curAscii)
Advance
throw EXCEPTION("Unterminated string")
/datum/jsonHelper/proc/ParseNumber()
var/firstPos = readPos
if(curChar == "-")
Advance
if(curAscii >= aZero + 1 && curAscii <= aZero + 9)
do
Advance
while(curAscii >= aZero && curAscii <= aZero + 9)
else if(curAscii == aZero)
Advance
else
throw EXCEPTION("Expected: digit")
if(curChar == ".")
Advance
var/found = FALSE
while(curAscii >= aZero && curAscii <= aZero + 9)
found = TRUE
Advance
if(!found)
throw EXCEPTION("Expected: digit")
if(curChar == "E" || curChar == "e")
Advance
var/found = FALSE
if(curChar == "-")
Advance
else if(curChar == "+")
Advance
while(curAscii >= aZero && curAscii <= aZero + 9)
found = TRUE
Advance
if(!found)
throw EXCEPTION("Expected: digit")
SkipWhitespace
return text2num(copytext(json, firstPos, readPos))
/datum/jsonHelper/proc/ParseObject()
ASSERT(curChar == "{")
var/list/object = list()
AdvanceWS
while(curChar == "\"")
var/key = ParseString()
if(curChar != ":")
throw EXCEPTION("Expected: ':'")
AdvanceWS
object[key] = ParseValue()
if(curChar == ",")
AdvanceWS
else
break
if(curChar != "}")
throw EXCEPTION("Expected: string or '}'")
AdvanceWS
return object
/datum/jsonHelper/proc/ParseArray()
ASSERT(curChar == "\[")
var/list/array = list()
AdvanceWS
while(curChar != "]")
array += list(ParseValue()) // Wrapped in a list in case ParseValue() returns a list.
if(curChar == ",")
AdvanceWS
else
break
if(curChar != "]")
throw EXCEPTION("Expected: ']'")
AdvanceWS
return array
#undef aBackspace
#undef aTab
#undef aLineBreak
#undef aVertTab
#undef aFormFeed
#undef aCarriageReturn
#undef aSpace
#undef aZero
#undef aNonBreakSpace
#undef Advance
#undef SkipWhitespace
#undef AdvanceWS
+14
View File
@@ -27,6 +27,20 @@
datum/topic_state/state = default_state)
return -1 // Sorta implemented.
/**
* public
*
* Called on a NanoUI when the UI receieves a href.
* Think of this as Topic().
*
* required action string The action/button that has been invoked by the user.
* required params list A list of parameters attached to the button.
*
* return bool If the UI should be updated or not.
**/
/atom/movable/proc/ui_act(action, list/params)
return // Not implemented.
/**
* public
*
+6 -3
View File
@@ -214,7 +214,7 @@
// Generate JSON.
var/list/send_data = get_send_data(initial_data)
var/initial_data_json = replacetext(writeJson(send_data), "'", "\\'")
var/initial_data_json = replacetext(JSON.stringify(send_data), "'", "\\'")
// Generate the HTML document.
return {"
@@ -318,7 +318,7 @@
var/list/send_data = get_send_data(data) // Get the data to send.
// Send the new data to the recieveUpdate() Javascript function.
user << output(list2params(list(writeJson(send_data))), "[window_id].browser:receiveUpdate")
user << output(list2params(list(JSON.stringify(send_data))), "[window_id].browser:receiveUpdate")
/**
* private
@@ -332,7 +332,10 @@
if (status != NANO_INTERACTIVE || user != usr)
return // If UI is not interactive or usr calling Topic is not the UI user.
var/update = src_object.Topic(href, href_list, 0, state) // Call Topic() on the src_object.
var/action = href_list["nano"] // Pull the action out.
href_list -= "nano"
var/update = src_object.ui_act(action, href_list, state) // Call Topic() on the src_object.
if (src_object && update)
SSnano.update_uis(src_object) // If we have a src_object and its Topic() told us to update.
+10 -32
View File
@@ -721,22 +721,8 @@
/obj/machinery/power/apc/proc/can_use(mob/user, loud = 0) //used by attack_hand() and Topic()
if (IsAdminGhost(user))
if(IsAdminGhost(user))
return 1
if (user.stat)
user << "<span class='warning'>You must be conscious to use [src]!</span>"
return 0
if(!user.client)
return 0
if(!user.IsAdvancedToolUser())
user << "<span class='warning'>You don't have the dexterity to use [src]!</span>"
return 0
if(user.restrained())
user << "<span class='warning'>You must have free hands to use [src].</span>"
return 0
if(user.lying)
user << "<span class='warning'>You must stand to use [src]!</span>"
return 0
if(user.has_unlimited_silicon_privilege)
var/mob/living/silicon/ai/AI = user
var/mob/living/silicon/robot/robot = user
@@ -754,24 +740,16 @@
else
if ((!in_range(src, user) || !istype(src.loc, /turf)))
return 0
var/mob/living/carbon/human/H = user
if (istype(H))
if(H.getBrainLoss() >= 60)
H.visible_message("[H] stares cluelessly at [src] and drools.")
return 0
else if(prob(H.getBrainLoss()))
user << "<span class='danger'>You momentarily forget how to use [src].</span>"
return 0
return 1
/obj/machinery/power/apc/Topic(href, href_list)
/obj/machinery/power/apc/ui_act(action, params)
if(..())
return
if(!can_use(usr, 1))
return
switch(href_list["nano"])
switch(action)
if("lock")
coverlocked = !coverlocked
if ("breaker")
@@ -782,18 +760,18 @@
charging = 0
update_icon()
if("channel")
if (href_list["eqp"])
var/val = text2num(href_list["eqp"])
if (params["eqp"])
var/val = text2num(params["eqp"])
equipment = setsubsystem(val)
update_icon()
update()
else if (href_list["lgt"])
var/val = text2num(href_list["lgt"])
else if (params["lgt"])
var/val = text2num(params["lgt"])
lighting = setsubsystem(val)
update_icon()
update()
else if (href_list["env"])
var/val = text2num(href_list["env"])
else if (params["env"])
var/val = text2num(params["env"])
environ = setsubsystem(val)
update_icon()
update()
+4 -4
View File
@@ -350,11 +350,11 @@
)
return data
/obj/machinery/power/smes/Topic(href, href_list)
/obj/machinery/power/smes/ui_act(action, params)
if(..())
return
switch(href_list["nano"])
switch(action)
if("tryinput")
input_attempt = !input_attempt
log_smes(usr.ckey)
@@ -364,7 +364,7 @@
log_smes(usr.ckey)
update_icon()
if("input")
switch(href_list["set"])
switch(params["set"])
if("custom")
var/custom = input(usr, "What rate would you like this SMES to attempt to charge at? Max is [input_level_max].") as null|num
if(custom)
@@ -380,7 +380,7 @@
input_level = Clamp(input_level, 0, input_level_max)
log_smes(usr.ckey)
if("output")
switch(href_list["set"])
switch(params["set"])
if("custom")
var/custom = input(usr, "What rate would you like this SMES to attempt to output at? Max is [output_level_max].") as null|num
if(custom)
+7 -7
View File
@@ -437,24 +437,24 @@
targetdir = (targetdir + trackrate/abs(trackrate) + 360) % 360 //... do it
nexttime += 36000/abs(trackrate) //reset the counter for the next 1°
/obj/machinery/power/solar_control/Topic(href, href_list)
/obj/machinery/power/solar_control/ui_act(action, params)
if(..())
return
switch(href_list["nano"])
switch(action)
if("control")
if(href_list["cdir"])
src.cdir = dd_range(0,359,(360+src.cdir+text2num(href_list["cdir"]))%360)
if(params["cdir"])
src.cdir = dd_range(0,359,(360+src.cdir+text2num(params["cdir"]))%360)
src.targetdir = src.cdir
if(track == 2) //manual update, so losing auto-tracking
track = 0
spawn(1)
set_panels(cdir)
if(href_list["tdir"])
src.trackrate = dd_range(-7200,7200,src.trackrate+text2num(href_list["tdir"]))
if(params["tdir"])
src.trackrate = dd_range(-7200,7200,src.trackrate+text2num(params["tdir"]))
if(src.trackrate) nexttime = world.time + 36000/abs(trackrate)
if("tracking")
track = text2num(href_list["mode"])
track = text2num(params["mode"])
if(track == 2)
if(connected_tracker)
connected_tracker.set_angle(SSsun.angle)
@@ -7,6 +7,7 @@
icon_state = "dispenser"
use_power = 1
idle_power_usage = 40
interact_offline = 1
var/energy = 100
var/max_energy = 100
var/amount = 30
@@ -122,27 +123,27 @@
data["chemicals"] = chemicals
return data
/obj/machinery/chem_dispenser/Topic(href, href_list)
/obj/machinery/chem_dispenser/ui_act(action, params)
if(..())
return
switch(href_list["nano"])
switch(action)
if("amount")
amount = round(text2num(href_list["set"]), 5) // round to nearest 5
amount = round(text2num(params["set"]), 5) // round to nearest 5
if (amount < 0) // Since the user can actually type the commands himself, some sanity checking
amount = 0
if (amount > 100)
amount = 100
if("dispense")
if(beaker && dispensable_reagents.Find(href_list["reagent"]))
if(beaker && dispensable_reagents.Find(params["reagent"]))
var/datum/reagents/R = beaker.reagents
var/space = R.maximum_volume - R.total_volume
R.add_reagent(href_list["reagent"], min(amount, energy * 10, space))
R.add_reagent(params["reagent"], min(amount, energy * 10, space))
energy = max(energy - min(amount, energy * 10, space) / 10, 0)
if("remove")
if(beaker)
var/amount = text2num(href_list["amount"])
var/amount = text2num(params["amount"])
if(isnum(amount) && (amount > 0) && (amount in beaker.possible_transfer_amounts))
beaker.reagents.remove_all(amount)
if("eject")
@@ -150,7 +151,6 @@
beaker.loc = loc
beaker = null
overlays.Cut()
add_fingerprint(usr)
return 1
/obj/machinery/chem_dispenser/attackby(obj/item/I, mob/user, params)
@@ -77,18 +77,17 @@
return
interact(user)
/obj/machinery/chem_heater/Topic(href, href_list)
/obj/machinery/chem_heater/ui_act(action, params)
if(..())
return
switch(href_list["nano"])
switch(action)
if("power")
on = !on
if("temperature")
desired_temp = Clamp(input("Please input the target temperature", name) as num, 0, 1000)
if("eject")
eject_beaker()
add_fingerprint(usr)
return 1
/obj/machinery/chem_heater/interact(mob/user)