mirror of
https://github.com/vgstation-coders/vgstation13.git
synced 2025-12-10 10:21:11 +00:00
513 lines
18 KiB
Plaintext
513 lines
18 KiB
Plaintext
/*
|
||
* Holds procs designed to help with filtering text
|
||
* Contains groups:
|
||
* SQL sanitization
|
||
* Text sanitization
|
||
* Text searches
|
||
* Text modification
|
||
* Misc
|
||
*/
|
||
|
||
|
||
/*
|
||
* SQL sanitization
|
||
*/
|
||
|
||
// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts.
|
||
/proc/sanitizeSQL(var/t as text)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/sanitizeSQL() called tick#: [world.time]")
|
||
//var/sanitized_text = replacetext(t, "'", "\\'")
|
||
//sanitized_text = replacetext(sanitized_text, "\"", "\\\"")
|
||
|
||
var/sqltext = dbcon.Quote(t)
|
||
//testing("sanitizeSQL(): BEFORE copytext(): [sqltext]")
|
||
sqltext = copytext(sqltext, 2, length(sqltext))//Quote() adds quotes around input, we already do that
|
||
//testing("sanitizeSQL(): AFTER copytext(): [sqltext]")
|
||
return sqltext
|
||
|
||
/*
|
||
/mob/verb/SanitizeTest(var/t as text)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/mob/verb/SanitizeTest() called tick#: [world.time]")
|
||
src << "IN: [t]"
|
||
src << "OUT: [sanitizeSQL(t)]"
|
||
*/
|
||
/*
|
||
* Text sanitization
|
||
*/
|
||
|
||
//Simply removes < and > and limits the length of the message
|
||
/proc/strip_html_simple(var/t,var/limit=MAX_MESSAGE_LEN)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/strip_html_simple() called tick#: [world.time]")
|
||
var/list/strip_chars = list("<",">")
|
||
t = copytext(t,1,limit)
|
||
for(var/char in strip_chars)
|
||
var/index = findtext(t, char)
|
||
while(index)
|
||
t = copytext(t, 1, index) + copytext(t, index+1)
|
||
index = findtext(t, char)
|
||
return t
|
||
|
||
/proc/strip_html_properly(input = "")
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/strip_html_properly() called tick#: [world.time]")
|
||
// these store the position of < and > respectively
|
||
var/opentag = 0
|
||
var/closetag = 0
|
||
|
||
while (input)
|
||
opentag = rfindtext(input, "<")
|
||
closetag = findtext(input, ">", opentag + 1)
|
||
|
||
if (!opentag || !closetag)
|
||
break
|
||
|
||
input = copytext(input, 1, opentag) + copytext(input, closetag + 1)
|
||
|
||
return input
|
||
|
||
/proc/rfindtext(Haystack, Needle, Start = 1, End = 0)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/rfindtext() called tick#: [world.time]")
|
||
var/i = findtext(Haystack, Needle, Start, End)
|
||
|
||
while (i)
|
||
. = i
|
||
i = findtext(Haystack, Needle, i + 1, End)
|
||
|
||
//Removes a few problematic characters
|
||
/proc/sanitize_simple(var/t,var/list/repl_chars = list("\n"="#","\t"="#","<22>"="<22>"))
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/sanitize_simple() called tick#: [world.time]")
|
||
for(var/char in repl_chars)
|
||
var/index = findtext(t, char)
|
||
while(index)
|
||
t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index+1)
|
||
index = findtext(t, char)
|
||
return t
|
||
|
||
//Runs byond's sanitization proc along-side sanitize_simple
|
||
/proc/sanitize(var/t,var/list/repl_chars = null)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/sanitize() called tick#: [world.time]")
|
||
return html_encode(sanitize_simple(t,repl_chars))
|
||
|
||
//Runs sanitize and strip_html_simple
|
||
//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' after sanitize() calls byond's html_encode()
|
||
/proc/strip_html(var/t,var/limit=MAX_MESSAGE_LEN)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/strip_html() called tick#: [world.time]")
|
||
return copytext((sanitize(strip_html_simple(t))),1,limit)
|
||
|
||
//Runs byond's sanitization proc along-side strip_html_simple
|
||
//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' that html_encode() would cause
|
||
/proc/adminscrub(var/t,var/limit=MAX_MESSAGE_LEN)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/adminscrub() called tick#: [world.time]")
|
||
return copytext((html_encode(strip_html_simple(t))),1,limit)
|
||
|
||
/proc/reverse_text(txt)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/reverse_text() called tick#: [world.time]")
|
||
var/i = length(txt)+1
|
||
. = ""
|
||
while(--i)
|
||
. += copytext(txt,i,i+1)
|
||
|
||
/*
|
||
* returns null if there is any bad text in the string
|
||
*/
|
||
/proc/reject_bad_text(const/text, var/max_length = 512)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/reject_bad_text() called tick#: [world.time]")
|
||
var/text_length = length(text)
|
||
|
||
if(text_length > max_length)
|
||
return // message too long
|
||
|
||
var/non_whitespace = FALSE
|
||
|
||
for(var/i = 1 to text_length)
|
||
switch(text2ascii(text, i))
|
||
if(62, 60, 92, 47)
|
||
return // rejects the text if it contains these bad characters: <, >, \ or /
|
||
if(127 to 255)
|
||
return // rejects weird letters like <20>
|
||
if(0 to 31)
|
||
return // more weird stuff
|
||
if(32)
|
||
continue //whitespace
|
||
else
|
||
non_whitespace = TRUE
|
||
|
||
if(non_whitespace)
|
||
return text // only accepts the text if it has some non-spaces
|
||
|
||
// Used to get a sanitized input.
|
||
/proc/stripped_input(var/mob/user, var/message = "", var/title = "", var/default = "", var/max_length=MAX_MESSAGE_LEN)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/stripped_input() called tick#: [world.time]")
|
||
var/name = input(user, message, title, default)
|
||
return strip_html_simple(name, max_length)
|
||
|
||
//Filters out undesirable characters from names
|
||
/proc/reject_bad_name(var/t_in, var/allow_numbers=0, var/max_length=MAX_NAME_LEN)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/reject_bad_name() called tick#: [world.time]")
|
||
if(!t_in || length(t_in) > max_length)
|
||
return //Rejects the input if it is null or if it is longer then the max length allowed
|
||
|
||
var/number_of_alphanumeric = 0
|
||
var/last_char_group = 0
|
||
var/t_out = ""
|
||
|
||
for(var/i=1, i<=length(t_in), i++)
|
||
var/ascii_char = text2ascii(t_in,i)
|
||
switch(ascii_char)
|
||
// A .. Z
|
||
if(65 to 90) //Uppercase Letters
|
||
t_out += ascii2text(ascii_char)
|
||
number_of_alphanumeric++
|
||
last_char_group = 4
|
||
|
||
// a .. z
|
||
if(97 to 122) //Lowercase Letters
|
||
if(last_char_group<2) t_out += ascii2text(ascii_char-32) //Force uppercase first character
|
||
else t_out += ascii2text(ascii_char)
|
||
number_of_alphanumeric++
|
||
last_char_group = 4
|
||
|
||
// 0 .. 9
|
||
if(48 to 57) //Numbers
|
||
if(!last_char_group) continue //suppress at start of string
|
||
if(!allow_numbers) continue
|
||
t_out += ascii2text(ascii_char)
|
||
number_of_alphanumeric++
|
||
last_char_group = 3
|
||
|
||
// ' - .
|
||
if(39,45,46) //Common name punctuation
|
||
if(!last_char_group) continue
|
||
t_out += ascii2text(ascii_char)
|
||
last_char_group = 2
|
||
|
||
// ~ | @ : # $ % & * +
|
||
if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI)
|
||
if(!last_char_group) continue //suppress at start of string
|
||
if(!allow_numbers) continue
|
||
t_out += ascii2text(ascii_char)
|
||
last_char_group = 2
|
||
|
||
//Space
|
||
if(32)
|
||
if(last_char_group <= 1) continue //suppress double-spaces and spaces at start of string
|
||
t_out += ascii2text(ascii_char)
|
||
last_char_group = 1
|
||
else
|
||
return
|
||
|
||
if(number_of_alphanumeric < 2) return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '"
|
||
|
||
if(last_char_group == 1)
|
||
t_out = copytext(t_out,1,length(t_out)) //removes the last character (in this case a space)
|
||
|
||
for(var/bad_name in list("space","floor","wall","r-wall","monkey","unknown","inactive ai","plating")) //prevents these common metagamey names
|
||
if(cmptext(t_out,bad_name)) return //(not case sensitive)
|
||
|
||
return t_out
|
||
|
||
//checks text for html tags
|
||
//if tag is not in whitelist (var/list/paper_tag_whitelist in global.dm)
|
||
//relpaces < with <
|
||
proc/checkhtml(var/t)
|
||
//writepanic("[__FILE__].[__LINE__] \\/proc/checkhtml() called tick#: [world.time]")
|
||
t = sanitize_simple(t, list("&#"="."))
|
||
var/p = findtext(t,"<",1)
|
||
while (p) //going through all the tags
|
||
var/start = p++
|
||
var/tag = copytext(t,p, p+1)
|
||
if (tag != "/")
|
||
while (reject_bad_text(copytext(t, p, p+1), 1))
|
||
tag = copytext(t,start, p)
|
||
p++
|
||
tag = copytext(t,start+1, p)
|
||
if (!(tag in paper_tag_whitelist)) //if it's unkown tag, disarming it
|
||
t = copytext(t,1,start-1) + "<" + copytext(t,start+1)
|
||
p = findtext(t,"<",p)
|
||
return t
|
||
/*
|
||
* Text searches
|
||
*/
|
||
|
||
//Checks the beginning of a string for a specified sub-string
|
||
//Returns the position of the substring or 0 if it was not found
|
||
/proc/dd_hasprefix(text, prefix)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/dd_hasprefix() called tick#: [world.time]")
|
||
var/start = 1
|
||
var/end = length(prefix) + 1
|
||
return findtext(text, prefix, start, end)
|
||
|
||
//Checks the beginning of a string for a specified sub-string. This proc is case sensitive
|
||
//Returns the position of the substring or 0 if it was not found
|
||
/proc/dd_hasprefix_case(text, prefix)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/dd_hasprefix_case() called tick#: [world.time]")
|
||
var/start = 1
|
||
var/end = length(prefix) + 1
|
||
return findtextEx(text, prefix, start, end)
|
||
|
||
//Checks the end of a string for a specified substring.
|
||
//Returns the position of the substring or 0 if it was not found
|
||
/proc/dd_hassuffix(text, suffix)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/dd_hassuffix() called tick#: [world.time]")
|
||
var/start = length(text) - length(suffix)
|
||
if(start)
|
||
return findtext(text, suffix, start, null)
|
||
return
|
||
|
||
//Checks the end of a string for a specified substring. This proc is case sensitive
|
||
//Returns the position of the substring or 0 if it was not found
|
||
/proc/dd_hassuffix_case(text, suffix)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/dd_hassuffix_case() called tick#: [world.time]")
|
||
var/start = length(text) - length(suffix)
|
||
if(start)
|
||
return findtextEx(text, suffix, start, null)
|
||
|
||
/*
|
||
* Text modification
|
||
*/
|
||
/proc/replacetext(text, find, replacement)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/replacetext() called tick#: [world.time]")
|
||
return list2text(text2list(text, find), replacement)
|
||
|
||
/proc/replacetextEx(text, find, replacement)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/replacetextEx() called tick#: [world.time]")
|
||
return list2text(text2listEx(text, find), replacement)
|
||
|
||
//Adds 'u' number of zeros ahead of the text 't'
|
||
/proc/add_zero(t, u)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/add_zero() called tick#: [world.time]")
|
||
while (length(t) < u)
|
||
t = "0[t]"
|
||
return t
|
||
|
||
//Adds 'u' number of spaces ahead of the text 't'
|
||
/proc/add_lspace(t, u)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/add_lspace() called tick#: [world.time]")
|
||
while(length(t) < u)
|
||
t = " [t]"
|
||
return t
|
||
|
||
//Adds 'u' number of spaces behind the text 't'
|
||
/proc/add_tspace(t, u)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/add_tspace() called tick#: [world.time]")
|
||
while(length(t) < u)
|
||
t = "[t] "
|
||
return t
|
||
|
||
//Returns a string with reserved characters and spaces before the first letter removed
|
||
/proc/trim_left(text)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/trim_left() called tick#: [world.time]")
|
||
for (var/i = 1 to length(text))
|
||
if (text2ascii(text, i) > 32)
|
||
return copytext(text, i)
|
||
return ""
|
||
|
||
//Returns a string with reserved characters and spaces after the last letter removed
|
||
/proc/trim_right(text)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/trim_right() called tick#: [world.time]")
|
||
for (var/i = length(text), i > 0, i--)
|
||
if (text2ascii(text, i) > 32)
|
||
return copytext(text, 1, i + 1)
|
||
|
||
return ""
|
||
|
||
//Returns a string with reserved characters and spaces before the first word and after the last word removed.
|
||
/proc/trim(text)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/trim() called tick#: [world.time]")
|
||
return trim_left(trim_right(text))
|
||
|
||
//Returns a string with the first element of the string capitalized.
|
||
/proc/capitalize(var/t as text)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/capitalize() called tick#: [world.time]")
|
||
return uppertext(copytext(t, 1, 2)) + copytext(t, 2)
|
||
|
||
//Centers text by adding spaces to either side of the string.
|
||
/proc/dd_centertext(message, length)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/dd_centertext() called tick#: [world.time]")
|
||
var/new_message = message
|
||
var/size = length(message)
|
||
var/delta = length - size
|
||
if(size == length)
|
||
return new_message
|
||
if(size > length)
|
||
return copytext(new_message, 1, length + 1)
|
||
if(delta == 1)
|
||
return new_message + " "
|
||
if(delta % 2)
|
||
new_message = " " + new_message
|
||
delta--
|
||
var/spaces = add_lspace("",delta/2-1)
|
||
return spaces + new_message + spaces
|
||
|
||
//Limits the length of the text. Note: MAX_MESSAGE_LEN and MAX_NAME_LEN are widely used for this purpose
|
||
/proc/dd_limittext(message, length)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/dd_limittext() called tick#: [world.time]")
|
||
var/size = length(message)
|
||
if(size <= length)
|
||
return message
|
||
return copytext(message, 1, length + 1)
|
||
|
||
/proc/stringmerge(var/text,var/compare,replace = "*")
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/stringmerge() called tick#: [world.time]")
|
||
//This proc fills in all spaces with the "replace" var (* by default) with whatever
|
||
//is in the other string at the same spot (assuming it is not a replace char).
|
||
//This is used for fingerprints
|
||
var/newtext = text
|
||
if(length(text) != length(compare))
|
||
return 0
|
||
for(var/i = 1, i < length(text), i++)
|
||
var/a = copytext(text,i,i+1)
|
||
var/b = copytext(compare,i,i+1)
|
||
//if it isn't both the same letter, or if they are both the replacement character
|
||
//(no way to know what it was supposed to be)
|
||
if(a != b)
|
||
if(a == replace) //if A is the replacement char
|
||
newtext = copytext(newtext,1,i) + b + copytext(newtext, i+1)
|
||
else if(b == replace) //if B is the replacement char
|
||
newtext = copytext(newtext,1,i) + a + copytext(newtext, i+1)
|
||
else //The lists disagree, Uh-oh!
|
||
return 0
|
||
return newtext
|
||
|
||
/proc/stringpercent(var/text,character = "*")
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/stringpercent() called tick#: [world.time]")
|
||
//This proc returns the number of chars of the string that is the character
|
||
//This is used for detective work to determine fingerprint completion.
|
||
if(!text || !character)
|
||
return 0
|
||
var/count = 0
|
||
for(var/i = 1, i <= length(text), i++)
|
||
var/a = copytext(text,i,i+1)
|
||
if(a == character)
|
||
count++
|
||
return count
|
||
|
||
/**
|
||
* Format number with thousands seperators.
|
||
* @param number Number to format.
|
||
* @param sep seperator to use
|
||
*/
|
||
/proc/format_num(var/number, var/sep=",")
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/format_num() called tick#: [world.time]")
|
||
var/c="" // Current char
|
||
var/list/parts = text2list("[number]",".")
|
||
var/origtext = "[parts[1]]"
|
||
var/len = length(origtext)
|
||
var/offset = len % 3
|
||
for(var/i=1;i<=len;i++)
|
||
c = copytext(origtext,i,i+1)
|
||
. += c
|
||
if((i%3)==offset && i!=len)
|
||
. += sep
|
||
if(parts.len==2)
|
||
. += ".[parts[2]]"
|
||
|
||
var/global/list/watt_suffixes = list("W", "KW", "MW", "GW", "TW", "PW", "EW", "ZW", "YW")
|
||
/proc/format_watts(var/number)
|
||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/format_watts() called tick#: [world.time]")
|
||
if(number<0) return "-[format_watts(number)]"
|
||
if(number==0) return "0 W"
|
||
|
||
var/i=1
|
||
while (round(number/1000) >= 1)
|
||
number/=1000
|
||
i++
|
||
return "[format_num(number)] [watt_suffixes[i]]"
|
||
|
||
|
||
// Custom algorithm since stackoverflow is full of complete garbage and even the MS algorithm sucks.
|
||
// Uses recursion, in places.
|
||
// (c)2015 Rob "N3X15" Nelson <nexisentertainment@gmail.com>
|
||
// Available under the MIT license.
|
||
|
||
var/list/number_digits=list(
|
||
"one",
|
||
"two",
|
||
"three",
|
||
"four",
|
||
"five",
|
||
"six",
|
||
"seven",
|
||
"eight",
|
||
"nine",
|
||
"ten",
|
||
"eleven",
|
||
"twelve",
|
||
"thirteen",
|
||
"fourteen",
|
||
"fifteen",
|
||
"sixteen",
|
||
"seventeen",
|
||
"eighteen",
|
||
"nineteen",
|
||
)
|
||
|
||
var/list/number_tens=list(
|
||
null, // 0 :V
|
||
null, // teens, special case
|
||
"twenty",
|
||
"thirty",
|
||
"forty",
|
||
"fifty",
|
||
"sixty",
|
||
"seventy",
|
||
"eighty",
|
||
"ninety"
|
||
)
|
||
|
||
var/list/number_units=list(
|
||
null, // Don't yell units
|
||
"thousand",
|
||
"million",
|
||
"billion"
|
||
)
|
||
|
||
/proc/num2words(var/number, var/zero="zero", var/minus="minus", var/hundred="hundred", var/list/digits=number_digits, var/list/tens=number_tens, var/list/units=number_units, var/recursion=0)
|
||
if(!isnum(number))
|
||
warning("num2words fed a non-number: [number]")
|
||
return list()
|
||
number=round(number)
|
||
//testing("num2words [recursion] ([number])")
|
||
if(number == 0)
|
||
return list(zero)
|
||
|
||
if(number < 0)
|
||
return list(minus) + num2words(abs(number), zero, minus, hundred, digits, tens, units, recursion+1)
|
||
|
||
var/list/out=list()
|
||
if(number < 1000)
|
||
var/hundreds = round(number/100)
|
||
//testing(" ([recursion]) hundreds=[hundreds]")
|
||
if(hundreds)
|
||
out += num2words(hundreds, zero, minus, hundred, digits, tens, units, recursion+1) + list(hundred)
|
||
number %= 100
|
||
|
||
if(number < 100)
|
||
// Teens
|
||
if(number <= 19)
|
||
out.Add(digits[number])
|
||
else
|
||
var/tens_place = tens[round(number/10)+1]
|
||
//testing(" ([recursion]) tens_place=[round(number/10)+1] = [tens_place]")
|
||
if(tens_place!=null)
|
||
out.Add(tens_place)
|
||
number = number%10
|
||
//testing(" ([recursion]) number%10+1 = [number+1] = [digits[number+1]]")
|
||
if(number>0)
|
||
out.Add(digits[number])
|
||
else
|
||
var/i=1
|
||
while(round(number) > 0)
|
||
var/unit_number = number%1000
|
||
//testing(" ([recursion]) [number]%1000 = [unit_number] ([i])")
|
||
if(unit_number > 0)
|
||
if(units[i])
|
||
//testing(" ([recursion]) units = [units[i]]")
|
||
out = list(units[i]) + out
|
||
out = num2words(unit_number, zero, minus, hundred, digits, tens, units, recursion+1) + out
|
||
number /= 1000
|
||
i++
|
||
//testing(" ([recursion]) out=list("+list2text(out,", ")+")")
|
||
return out
|
||
|
||
///mob/verb/test_num2words(var/number as num)
|
||
// usr << "\"[list2text(num2words(number), " ")]\"" |