Monday, November 08, 2010 at 12:06 AM.

system.verbs.builtins.webBrowser.protocols.safetyCheck

<<Version 2.0 - 9/5/95 Mason Hale
	<<10/29/95 Added alert parameter.  If true client is alerted of errors.  If false not alert is made.

on safetyCheck (textToBeChecked, alert = true, adrSafetyScriptsInit = @webBrowser.protocols.safeScripts) {
	local {
		tempWordCounter;
		workingCopyOfScript = "";
		foundVerbList = {};
		checkedVerbList = {};
		loopCounterTwo;
		loopCounterOne;
		escapedCharList = {"\r", "\t", "(", ")", "[", "]", "{", "}", ",", ";", "+"}};
	
	on debugReport (msg) {
		if not defined (scratchpad.results) {
			new (wpTextType, @scratchpad.results)};
		edit (@scratchpad.results);
		wp.setText (msg + "\r\rBefore:\r" + texttoBeChecked + "\r\rAfter:\r" + workingCopyOfScript + "\r\rVerbs to be checked:\r" + string (foundVerbList) + "\r\rVerbs checked (and passed):\r" + string (checkedVerbList))};
	
	on reportError (msg) {
		if alert {
			dialog.alert ("Script was not run!\r" + msg)}};
		<<debugReport (msg)
	
	<<workingCopyOfScript = textToBeChecked
	string.setWordChar (" ");
	if textToBeChecked contains "<<" {
		reportError ("Illegal instruction: Comments not allowed"); <<don't allow comments, it throws off identification of strings
		return (false)};
	if (textToBeChecked contains "“") or  (textToBeChecked contains "”") {
		reportError ("Illegal instruction: Curly quotes not allowed"); <<don't allow comments, it throws off identification of strings
		return (false)};
	textToBeChecked = string.replaceAll (textToBeChecked, "\\\"", "Æ"); <<replace escaped quotes
	for loopCounterTwo = 1 to string.countFields (textToBeChecked, "\"") { <<build macrostring, ignoring things between quotes
		local (testString = string.nthField (textToBeChecked, "\"", loopCounterTwo));
		if (mod (loopCounterTwo, 2)) > 0 { << test even/odd
			testString = string.lower (testString); << lowercase all verbs for replacement by string.replaceAll later
			if loopCounterTwo == string.countFields (textToBeChecked, "\"") {
				workingCopyOfScript = workingCopyOfScript + testString}
			else {
				workingCopyOfScript = workingCopyOfScript + testString + "\""};
			for loopCounterOne = 1 to sizeOf (escapedCharList) {
				testString = string.replaceAll (testString, escapedCharList[loopCounterOne], " ")}; <<replace escaped chars with spaces
			for loopCounterOne = 1 to string.countWords (testString) {
				bundle { <<catch illegal verb combinations
					if (string.nthWord (testString, loopCounterOne) beginsWith "@") and (string.nthWord (testString, loopCounterOne - 1) == "=") {
						reportError ("Illegal Instruction: = followed by @");
						return (false)};
					if (string.nthWord (testString, loopCounterOne) contains "=@") {
						reportError ("Illegal Instruction: = followed by @");
						return (false)};
					if (string.nthWord (testString, loopCounterOne) == "=") and (defined (string.nthWord (testString, loopCounterOne - 1)^)) {
						reportError ("Illegal Instruction: " + string.nthWord (testString, loopCounterOne - 1) + " " +  string.nthWord (testString, loopCounterOne) +  "\rCannot assign new values to database objects");
						return (false)}};
				if not (foundVerbList contains string.nthWord (teststring, loopCounterOne)) {
					foundVerbList = foundVerbList + string.nthWord (teststring, loopCounterOne)}}}
		else {
			if loopCounterTwo == string.countFields (textToBeChecked, "\"") {
				workingCopyOfScript = workingCopyOfScript + testString}
			else {
				workingCopyOfScript = workingCopyOfScript + testString + "\""}}};
	workingCopyOfScript = string.replaceAll (workingCopyOfScript, "Æ", "\\\""); <<put back escaped quotes
	<<At this point I have a list of verbs to check.  To speed things up that list could be generated by a UCMD.
	for loopCounterTwo = 1 to sizeOf (foundVerbList) {
		bundle { <<catch special exceptions/restrict keywords
			if foundVerbList [loopCounterTwo] beginsWith "kernel" {
				reportError ("Illegal Instruction: " + foundVerbList [loopCounterTwo]); <<don't allow calls the kernel directly
				return (false)};
			if foundVerbList [loopCounterTwo] == "with" {
				reportError ("Illegal Instruction: " + foundVerbList [loopCounterTwo]);
				return (false)};
			if foundVerbList [loopCounterTwo] contains "^" {
				reportError ("Illegal Instruction: " + foundVerbList [loopCounterTwo] + "\rExpansion of variables not allowed.");
				return (false)};
			if (foundVerbList [loopCounterTwo] endsWith ".") or (foundVerbList [loopCounterTwo] beginsWith ".") {
				reportError ("Illegal Instruction: " + foundVerbList [loopCounterTwo] + "\rVerb cannot begin or end with a period.");
				return (false)};
			if foundVerbList [loopCounterTwo] == "string" {
				checkedVerbList = checkedVerbList + foundVerbList [loopCounterTwo];
				continue}}; <<explicitly allow the string verb
		local (adrSafetyScripts = adrSafetyScriptsInit); << define address variable before with statement
		if defined (foundVerbList [loopCounterTwo]^) { <<only dangerous if defined
			bundle { << check address in table
				local (subTableCounter = 1, nextFieldHolder);
				loop {
					if not defined (adrSafetyScripts^) {
						reportError ("Illegal Instruction: " + foundVerbList [loopCounterTwo]);
						return (false)};
					nextFieldHolder = string.nthField (foundVerbList [loopCounterTwo], '.', subTableCounter++);
					if nextFieldHolder == "" { <<ran out of fields
						break};
					adrSafetyScripts = @adrSafetyScripts^.[nextFieldHolder]}};
			<<made it out of the loop, replace verb with object at address given
			case typeOf (adrSafetyScripts^) { <<addresses and strings only are supported
				addressType { << replace with address
					workingCopyOfScript = string.replaceAll (workingCopyOfScript, foundVerbList [loopCounterTwo], adrSafetyScripts^);
					checkedVerbList = checkedVerbList + foundVerbList [loopCounterTwo]};
				stringType { << make sure string value is coming from safeScripts table not elsewhere
					workingCopyOfScript = string.replaceAll (workingCopyOfScript, foundVerbList [loopCounterTwo], string (adrSafetyScripts));
					checkedVerbList = checkedVerbList + foundVerbList [loopCounterTwo]}}
			else {
				reportError ("Illegal Instruction: " + foundVerbList [loopCounterTwo]);
				return (false)}}
		else {
			<<Check for things like user.name = "King of the Dorks" but still allow edit (@user) to open objects remotely.
				<<also allow for subtables in variables created in the embedded script
			if (foundVerbList [loopCounterTwo] contains ".") and (!foundVerbList [loopCounterTwo] beginsWith "@") {
				if defined (string.nthField (foundVerbList [loopCounterTwo], '.', 1)^) {
					reportError ("Illegal Instruction: " + foundVerbList [loopCounterTwo]);
					return (false)}};
			checkedVerbList = checkedVerbList + foundVerbList [loopCounterTwo]}};
	<<debugReport ("Success")
	return (workingCopyOfScript)};


<<bundle <<test code
	<<local (x)
	<<bundle <<should pass
		<<x = "local (duration = 10, AMplitude = 200, frequency = 1000, ixxx);speaker.sound (duration, amplitude, frequency);"
		<<x = "edit%20(@webserver.utilities.readme);frontier.bringTofront (); "
		<<x = "dialog.notify%20(%22Hello%20World!%22);%0Dlocal%20(i);%0Dfor%20i%20%3D%201%20to%2010%20{%0D%09speaker.sound%20(10,250,%204000%20%2B%20(i%20*%201000))}"
		<<x = "edit (@root.user); Frontier.bringToFront (); dialog.notify (\"Hello \\\" \" %2B user.name %2B \"\\\"!\");"
		<<x = "local (s = string (clock.now ())); string (clock.ticks ());s = string.delete (s, 3, 2); dialog.notify (s);" 
		<<x = "DIALOG.notify (\"Hello \" %2B user.name %2B \"!\");dialog.NOTIFY (\"It is \" %2B string.delete (clock.now (), 3,2));dialog.notify (\"Are we having fun yet?\");"
	<<
	<<bundle <<should fail
		<<x = "local (rd = sizeOf (user), i); dialog.notify (rd)\r; for i = 1 to rd {;dialog.notify (nameOf (user[i]))};"
		<<x = "[\"file\"]  .   [\"delete\"] (\"Mac HD:Test\")"
		<<x = "[\"file\"].   [\"delete\"] (\"Mac HD:Test\")"
		<<x = "file.[\"delete\"] (\"Mac HD:Test\")"
		<<x = "[\"file\"]  .[\"delete\"] (\"Mac HD:Test\")"
		<<x = "[\"file\"].[\"delete\"] (\"Mac HD:Test\")"
		<<x = "scratchpad.test1 = “I wrote data to the OD”"
		<<x = "local (test) <<enclose \";file.delete (\"HD:Sacrificial\");<<\""
		<<x = "local (f = address (\"file.delete\"); f^ (\"Macintosh HD:Sacrificial File\");"
		<<x = "evaluate (-300);"
		<<x = "file.delete (\"Macintosh HD:Sacrificial File\")"
		<<x = "local (i =@file.delete); i^ (\"Macintosh HD:Sacrificial File\")"
		<<x = "dialog.notify (file.delete (\"Macintosh HD:Sacrificial File\"))"
		<<x = "new (tableType, @scratchpad.zzz);zzz.a = \"I'm back...\";"
		<<x = "new (addressType, @webBrowser.protocols.safeScripts.user); webBrowser.protocols.safeScripts.user = @file.delete;"
		<<x = "local (f = \"file\"); f = f %2B \".delete\"; f^ (\"Macintosh HD:Sacrificial File\");" 
		<<x  = "user.name = \"Dork King\"; dialog.notify (user.name);"
	<<x = toys.urlDecode (x) << "+" = %2B
	<<evaluate (safetyCheck (x))

<<I'm using if defined (verb) to identify verbs that might be dangerous.   By disallowing with statements
	<<that should allow variables and flag verbs that need to be checked.
	<<Can't assume tableType is safe.  Allows hacker to step through an array to get to the verb, or call it by index.

<<Embedded Script Rules
	<<No comments
	<<No curly quotes
	<<No with statements
	<<No ^
	<<No calls to kernel
	<<No = followed by @ (diallows assignment of scripts to variable)
	<<Verbs must match address entry in SafeScripts table exactly
	<<Cannot assign new values to database objects (i.e. scratchpad.x = "foo")

<<Old version
	<<on safetyCheck (textToBeChecked, adrSafetyScriptsInit = @webBrowser.protocols.safeScripts) <<8/1/95 MAH
		<<local (tempChunkCounter, tempParamCounter, tempLineCounter, tempWordCounter, workingCopyOfScript, checkedVerbList = {})
		<<workingCopyOfScript = textToBeChecked
		<<for tempLineCounter = 1 to string.countFields (textToBeChecked, ";") << break down into lines
			<<local (lineStringVarHolder = string.nthField (textToBeChecked, ";", tempLineCounter))
			<<for tempChunkCounter = 1 to string.countFields (lineStringVarHolder, "(") << break down into chunks
				<<local (chunkStringVarHolder = string.nthField (lineStringVarHolder, "(", tempChunkCounter))
				<<for tempParamCounter = 1 to string.countFields (chunkStringVarHolder, ",") <<break down into parameters
					<<local (paramStringVarHolder = string.nthField (chunkStringVarHolder, ",", tempParamCounter))
					<<for tempWordCounter = 1 to string.countWords (paramStringVarHolder) << break down into words
						<<local (verbStringVarHolder = string.nthWord (paramStringVarHolder, tempWordCounter))
						<<bundle << clean up verb string
							<<verbStringVarHolder = string.popTrailing (verbStringVarHolder, ")") << pop trailing closed parens
							<<verbStringVarHolder = string.popLeading (verbStringVarHolder, " ") << pop leading spaces
							<<verbStringVarHolder = string.popTrailing (verbStringVarHolder, " ") << pop trailing spaces
						<<bundle <<catch special exceptions/restrict keywords
							<<if verbStringVarHolder beginsWith "kernel"
								<<bundle <<debugging code
									<<if not defined (scratchpad.results)
										<<new (wpTextType, @scratchpad.results)
									<<edit (@scratchpad.results)
									<<wp.setText ("Illegal Instruction: Kernel verbs are not allowed\r\rBefore:\r" + texttoBeChecked + "\r\rAfter:\r" + workingCopyOfScript + "\r\rVerbs checked (and passed):\r" + string (checkedVerbList))
								<<return (false) <<don't allow calls the kernel directly
							<<if verbStringVarHolder == "with"
								<<bundle <<debugging code
									<<if not defined (scratchpad.results)
										<<new (wpTextType, @scratchpad.results)
									<<edit (@scratchpad.results)
									<<wp.setText ("Illegal Instruction: With statements are not allowed\r\rBefore:\r" + texttoBeChecked + "\r\rAfter:\r" + workingCopyOfScript + "\r\rVerbs checked (and passed):\r" + string (checkedVerbList))
								<<return (false) <<don't allow "with" statements
							<<if verbStringVarHolder contains "^"
								<<bundle <<debugging code
									<<if not defined (scratchpad.results)
										<<new (wpTextType, @scratchpad.results)
									<<edit (@scratchpad.results)
									<<wp.setText ("Illegal Instruction: Expansion of addresses not allowed\r\rBefore:\r" + texttoBeChecked + "\r\rAfter:\r" + workingCopyOfScript + "\r\rVerbs checked (and passed):\r" + string (checkedVerbList))
								<<return (false) <<don't allow expansion of addresses statements
							<<if string.nthChar (verbStringVarHolder, 1) == "@" and string.nthWord (paramStringVarHolder, tempWordCounter -1) == "="
								<<bundle <<debugging code
									<<if not defined (scratchpad.results)
										<<new (wpTextType, @scratchpad.results)
									<<edit (@scratchpad.results)
									<<wp.setText ("Illegal Instruction: Assigning address to variable\r\rBefore:\r" + texttoBeChecked + "\r\rAfter:\r" + workingCopyOfScript + "\r\rVerbs checked (and passed):\r" + string (checkedVerbList))
								<<return (false) <<Cannot copy database objects into variables (plugs a big security hole)
						<<if checkedVerbList contains verbStringVarHolder
							<<continue <<don't check the same word twice
						<<else
							<<local (adrSafetyScripts = adrSafetyScriptsInit) << define address variable before with statement
							<<with webBrowser.protocols.safeScripts << include custom safe scripts
								<<if defined (verbStringVarHolder^) <<only dangerous if defined
									<<bundle << check address in table
										<<local (subTableCounter =1, nextFieldHolder)
										<<loop
											<<if not defined (adrSafetyScripts^)
												<<bundle <<debugging code
													<<if not defined (scratchpad.results)
														<<new (wpTextType, @scratchpad.results)
													<<system.verbs.globals.edit (@scratchpad.results)
													<<system.verbs.builtins.wp.setText ("Illegal Verb: " + verbStringVarHolder + "\r\rBefore:\r" + texttoBeChecked + "\r\rAfter:\r" + workingCopyOfScript + "\r\rVerbs checked (and passed):\r" + system.verbs.globals.string (checkedVerbList))
												<<return (false) <<don't do anything
											<<nextFieldHolder = system.verbs.builtins.string.nthField (verbStringVarHolder, '.', subTableCounter++)
											<<if nextFieldHolder == "" <<ran out of fields
												<<break
											<<adrSafetyScripts = @adrSafetyScripts^.[nextFieldHolder]
									<<made it out of the loop, replace verb with object at address given
									<<case typeOf (adrSafetyScripts^) << scripts, addresses and string only are supported
										<<scriptType << run script
											<<need to be careful not to replace "string.replace" when replacing "string"
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, "." + verbStringVarHolder, "£°¹")
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, verbStringVarHolder + ".", "Æ°¢")
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, verbStringVarHolder, string (adrSafetyScripts) + " ")
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, "£°¹", "." + verbStringVarHolder)
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, "Æ°¢", verbStringVarHolder + ".")
											<<checkedVerbList = checkedVerbList + verbStringVarHolder
										<<addressType << replace with address
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, "." + verbStringVarHolder, "£°¹")
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, verbStringVarHolder + ".", "Æ°¢")
											<<workingCopyOfScript = root.system.verbs.builtins.string.replaceAll (workingCopyOfScript, verbStringVarHolder, adrSafetyScripts^)
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, "£°¹", "." + verbStringVarHolder)
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, "Æ°¢", verbStringVarHolder + ".")
											<<checkedVerbList = checkedVerbList + verbStringVarHolder
										<<stringType << make sure string value is coming from safeScripts table not elsewhere
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, "." + verbStringVarHolder, "£°¹")
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, verbStringVarHolder + ".", "Æ°¢")
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, verbStringVarHolder, string (adrSafetyScripts))
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, "£°¹", "." + verbStringVarHolder)
											<<workingCopyOfScript = system.verbs.builtins.string.replaceAll (workingCopyOfScript, "Æ°¢", verbStringVarHolder + ".")
											<<checkedVerbList = checkedVerbList + verbStringVarHolder
									<<else
										<<return (false)
									<<tableType << this allow string coercions & string.xxx verbs is it a hole?
										<<checkedVerbList = checkedVerbList + verbStringVarHolder
								<<else
									<<checkedVerbList = checkedVerbList + verbStringVarHolder
		<<bundle <<debugging code
			<<if not defined (scratchpad.results)
				<<new (wpTextType, @scratchpad.results)
			<<edit (@scratchpad.results)
			<<wp.setText ("Before:\r" + texttoBeChecked + "\r\rAfter:\r" + workingCopyOfScript + "\r\rVerbs checked (and passed):\r" + string (checkedVerbList))
		<<return (workingCopyOfScript)



This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.