Validating user input with AppleScript

My applet basically takes lots of different user input, stores it, and displays it back to the user in various ways and allows the user to do stuff with it. Apart from Shane Stanley’s excellent Myriad Tables Lib, it doesn’t use AppleScript Objective C.

so far I’ve never really bothered validating user input, except for the basics like coercing numbers to numbers and ensuring they are within the expected range and coercing dates to date values and, in some cases, ensuring text entered is of a particular length or not more than one word. I’ve got a relatively small user base and have had no issues so far.

But as I’m in the process of rewriting my applet I’m planning to do more. I’m writing a generic handler for my library which will allow me to get and validate user input. At the moment it allows me to do all the basic stuff as above.

Should I be worried about any particular characters? I thought I’d probably blacklist the tab character as that shouldn’t be necessary and may make displaying the data wonky, and I’ll make allowing the return character optional. I feel I should probably disallow backslash and quote for good measure, though the way AppleScript handles these appears to be pretty safe. Are there any other characters I should be worried about?

My other option would be to whitelist which characters are allowed and just include upper and lower case letters, digits, and the handful of special characters I think would be relevant.

Most of the resources on this general topic are of course concerned with preventing code injection. I’m not sure if that’s something I need to be concerned about but if it’s a possibility and I can prevent it then why not.

for anyone who’s interested, my code is below.

on returnOBValue given prompt:thePrompt as text : "Please enter a number.", defaultValue:theValue : "", valueType:classOfValueToReturn : "", minValue:minValue as number : missing value, maxValue:maxValue as number : missing value, errorStringIfTooLow:errorStringIfTooLow as text : missing value, errorStringIfTooHigh:errorStringIfTooHigh as text : missing value, negativesAllowed:negativesAllowed as boolean : false, zeroAllowed:zeroAllowed as boolean : false, title:thisTitle as text : missing value, stepCount:theStep as integer : 0, backButtonName:backButtonName as text : "Back", nextButtonName:nextButtonName as text : "Next", emptyStringAllowed:emptyStringAllowed as boolean : false, spacesAllowed:spacesAllowed as boolean : true, multipleWordsAllowed:multipleWordsAllowed as boolean : true, listOfProhibitedCharacters:prohibitedCharacters as list : {}, multipleParagraphsAllowed:multipleParagraphsAllowed as boolean : false
	
	
	set prohibitedCharacters to prohibitedCharacters & {"/", "<", ">", "{", "}", "@", tab, return, "\\", "\""}
	if minValue is not missing value and errorStringIfTooLow is missing value then set errorStringIfTooLow to "The number you entered is too low. You may not enter a number lower than " & minValue & "."
	if maxValue is not missing value and errorStringIfTooHigh is missing value then set errorStringIfTooHigh to "The number you entered is too high. You may not enter a number greater than " & maxValue & "."
	set userCancelled to false
	set msg to ""
	repeat
		try
			if thisTitle is missing value then
				set dialogResult to (display dialog msg & thePrompt default answer theValue buttons {"Cancel", backButtonName, nextButtonName} cancel button 1 default button 3 with icon note)
			else
				set dialogResult to (display dialog msg & thePrompt default answer theValue with title thisTitle buttons {"Cancel", backButtonName, nextButtonName} cancel button 1 default button 3 with icon note)
			end if
		on error number -128
			set userCancelled to true
			exit repeat
		end try
		set theValue to text returned of dialogResult
		if button returned of dialogResult is nextButtonName then
			try
				if emptyStringAllowed is false and theValue is "" then error "sorry, this field is mandatory. You must enter something in it to proceed." & return & return
				
				-- if the returned value is to be a number and the text entered has a "$" then remove it
				if (class of classOfValueToReturn is integer or class of classOfValueToReturn is real) then
					if the first character of theValue is "$" then set theValue to (characters 2 through -1 of theValue) as text
					try
						set theValue to theValue as number
					end try
					
					-- ensure the value is the required class
					if class of classOfValueToReturn is integer and class of theValue is not integer then error "You must enter a whole number." & return & return
					if class of classOfValueToReturn is real and class of theValue is integer then
						set theValue to theValue as real
					else if class of classOfValueToReturn is real and class of theValue is not real then
						error "You must enter a number." & return & return
					end if
					
					if negativesAllowed is false and theValue is less than 0 then error "You may not enter a negative amount." & return & return
					if zeroAllowed is false and theValue is 0 then error "The amount cannot be 0. If it is, what's the point? 😕" & return & return
					if minValue is not missing value and theValue is less than minValue then error errorStringIfTooLow & return & return
					if maxValue is not missing value and theValue is greater than maxValue then error errorStringIfTooHigh & return & return
				else -- required class is not a number
					if maxValue is not missing value and (count of characters in theValue) is greater than maxValue then error "That's too many characters. The maximum number of characters you may enter is " & maxValue & "." & return & return
					if minValue is not missing value and (count of characters in theValue) is less than minValue then error "You must enter at least " & minValue & " characters." & return & return
					if spacesAllowed is false and theValue contains space then error "You may not enter spaces." & return & return
					if multipleWordsAllowed is false and (count of words in theValue) is greater than 1 then error "You may only enter one word." & return & return
					if multipleParagraphsAllowed is false and theValue contains linefeed then error "Multiple paragraphs are not permitted." & return & return
					repeat with theCharacter in prohibitedCharacters
						if theValue contains theCharacter then
							-- if the prohibited character is whitespace, then we need to give special attention to the error message
							if theCharacter is space then
								error "Sorry, spaces are not permitted." & return & return
							else if theCharacter is tab then
								error "Sorry, tab characters are not permitted." & return & return
							else if theCharacter is linefeed then
								error "Sorry, new lines are not permitted." & return & return
							else if theCharacter is return then
								error "Sorry, carriage returns/multiple paragraphs are not permitted." & return & return
							else
								error "Sorry, “" & theCharacter & "” is a prohibited character." & return & return
							end if
						end if
					end repeat
				end if
				set theStep to theStep + 1
				exit repeat
			on error msg
			end try
		else -- user chose back
			set theStep to theStep - 1
			exit repeat
		end if
	end repeat
	
	return {userCancelled:userCancelled, stepCount:theStep, valueReturned:theValue}
end returnOBValue