#[Copyright 2024 ITwrx. This file is part of ITwrxorg-SiteUpdata. ITwrxorg-SiteUpdata is released under the GNU Affero General Public License 3.0. See COPYING or for details.]# import std/[strutils, re, typetraits, times, tables] import valido/[email, password] import "form", "global" var msgString{.threadvar.}: string proc vSize(sizeName: string, sizeValue: int, inputName: string, inputData: string, inputType: string) = case sizeName: of "min": #how we determine size depends on input type. case inputType: of "string": if not (inputData.len >= sizeValue): msgString = "The '" & inputName & "' field must have a character count of at least " & $sizeValue & " ." addFormError(inputName, msgString) of "integer": if not (parseIntIf(inputData) >= sizeValue): msgString = "The '" & inputName & "' field must have a value of at least " & $sizeValue & " ." addFormError(inputName, msgString) of "float": if not (parseFloat(inputData) >= sizeValue.float): msgString = "The '" & inputName & "' field must have a value of at least " & $sizeValue & " ." addFormError(inputName, msgString) of "max": #how we determine size depends on input type. case inputType: of "string": if inputData.len > sizeValue: msgString = "The '" & inputName & "' field must have a character count that is no greater than " & $sizeValue & " ." addFormError(inputName, msgString) of "integer": if parseIntIf(inputData) > sizeValue: msgString = "The '" & inputName & "' field must have a value that is no greater than " & $sizeValue & " ." addFormError(inputName, msgString) of "float": if parseFloat(inputData) > sizeValue.float: msgString = "The '" & inputName & "' field must have a value that is no greater than " & $sizeValue & " ." addFormError(inputName, msgString) proc vType(inputName: string, inputData: string, validators: seq[string]): string = var inputType: string if "integer" in validators: try: #don't try to parse empty string as int. if inputData.len > 0: discard parseIntIf(inputData) inputType = "integer" except: msgString = "The '" & inputName & "' field must be a whole number." addFormError(inputName, msgString) elif "float" in validators: try: discard parseFloat(inputData) inputType = "float" except: msgString = "The '" & inputName & "' field must be a floating point number; a number with a decimal point. i.e. '1.5'" addFormError(inputName, msgString) elif "boolean" in validators: if (inputData == "true") or not (inputData == "false"): msgString = "The '" & inputName & "' field must be interpretable as a boolean. i.e. true, or false." addFormError(inputName, msgString) inputType = "boolean" else: inputType = "string" return inputType proc vRegex(inputName: string, inputData: string, inputType: string, validators: seq[string]) = #check for regex validators. for v in validators: if match(v, re"^(min):([0-9]+)$"): #get size's value from the string. let vNameSeq = v.split(':') let sizeName = vNameSeq[0] let sizeValue = parseIntIf(vNameSeq[1]) vSize(sizeName, sizeValue, inputName, inputData, inputType) if match(v, re"^(max):([0-9]+)$"): let vNameSeq = v.split(':') let sizeName = vNameSeq[0] let sizeValue = parseIntIf(vNameSeq[1]) vSize(sizeName, sizeValue, inputName, inputData, inputType) #ex. matches: "required_without:parent_id" if match(v, re"^(required_without):([a-z_]+)$"): let vNameSeq = v.split(':') #skip 0 index, as that's just the name of the validator. let conditionField = vNameSeq[1] var conditionFieldInputData{.threadvar.}: string conditionFieldInputData = formInput(conditionField) if conditionFieldInputData.len == 0: if inputData.len == 0: msgString = "The '" & inputName & "' field is required when " & conditionField & " isn't set." addFormError(inputName, msgString) if match(v, re"^(must_unset_with):([a-z_]+)$"): let vNameSeq = v.split(':') #skip 0 index, as that's just the name of the validator. let conditionField = vNameSeq[1] var conditionFieldInputData: string conditionFieldInputData = formInput(conditionField) if conditionFieldInputData.len > 0: if inputData.len > 0: msgString = "The '" & inputName & "' field must be empty if " & conditionField & " is not." addFormError(inputName, msgString) if match(v, re"^(required_with):([a-z_]+)$"): let vNameSeq = v.split(':') #skip 0 index, as that's just the name of the validator. let conditionField = vNameSeq[1] var conditionFieldInputData: string conditionFieldInputData = formInput(conditionField) if conditionFieldInputData.len > 0: if inputData.len == 0: msgString = "The '" & inputName & "' field is also required when " & conditionField & " is set." addFormError(inputName, msgString) if match(v, re"^(required_with):([a-z_]+):(without):([a-z_]+)$"): let vNameSeq = v.split(':') #skip 0 index, as that's just the name of the validator. let conditionField1 = vNameSeq[1] var conditionField1InputData: string conditionField1InputData = formInput(conditionField1) let conditionField2 = vNameSeq[3] var conditionField2InputData: string conditionField2InputData = formInput(conditionField2) if conditionField1InputData.len > 0 and not conditionField2InputData.len > 0: if inputData.len == 0: msgString = "The '" & inputName & "' field is required when " & conditionField1 & " is set and " & conditionField2 & " isn't set." addFormError(inputName, msgString) if match(v, re"^(required_with):([a-z_]+):(without):([a-z_]+):(and_without):([a-z_]+)$"): let vNameSeq = v.split(':') #skip 0 index, as that's just the name of the validator. let conditionField1 = vNameSeq[1] var conditionField1InputData: string conditionField1InputData = formInput(conditionField1) let conditionField2 = vNameSeq[3] var conditionField2InputData: string conditionField2InputData = formInput(conditionField2) let conditionField3 = vNameSeq[5] var conditionField3InputData: string conditionField3InputData = formInput(conditionField3) if conditionField1InputData.len > 0 and not conditionField2InputData.len > 0 and not conditionField3InputData.len > 0: if inputData.len == 0: msgString = "The '" & inputName & "' field is required when " & conditionField1 & " is set and " & conditionField2 & " and " & conditionField3 & " aren't set." addFormError(inputName, msgString) if match(v, re"^(required_when):([a-z_]+):(equals):([a-z_]+)$"): let vNameSeq = v.split(':') #skip 0 index, as that's just the name of the validator. let conditionField1 = vNameSeq[1] var conditionField1InputData: string conditionField1InputData = formInput(conditionField1) let conditionField2 = vNameSeq[3] if conditionField1InputData.len > 0 and conditionField1InputData == conditionField2: if inputData.len == 0: msgString = "The '" & inputName & "' field is required when " & conditionField1 & " equals " & conditionField2 addFormError(inputName, msgString) if match(v, re"^(required_when):([a-z_]+):(equals):([a-z_]+):(without):([a-z_]+)$"): let vNameSeq = v.split(':') #skip 0 index, as that's just the name of the validator. let conditionField1 = vNameSeq[1] var conditionField1InputData: string conditionField1InputData = formInput(conditionField1) let conditionField2 = vNameSeq[3] let conditionField3 = vNameSeq[5] var conditionField3InputData: string conditionField3InputData = formInput(conditionField3) if conditionField1InputData.len > 0 and conditionField1InputData == conditionField2 and conditionField3InputData.len == 0: if inputData.len == 0: msgString = "The '" & inputName & "' field is required when " & conditionField1 & " equals " & conditionField2 & " and " & conditionField3 & " is not set." addFormError(inputName, msgString) proc vStandard(inputName: string, inputData: string, inputType: string, validators: seq[string]) = if "email" in validators: if not isEmail(inputData): msgString = "The '" & inputName & "' field is not recognized as a valid email address." addFormError(inputName, msgString) if "min_complexity" in validators: if not isStrongPassword(inputData): msgString = "The '" & inputName & "' field is not random/complex enough. Try to make your " & inputName & " more unpredictable by adding random numbers, random letter casing, unrelated words, special characters, etc." addFormError(inputName, msgString) proc vHardcoded*(inputDataAll: Table[string, string], validators: seq[string]) = ##Warning: this validator requires specific (hardcoded) input names to exist in the posted form data. if "future_datetime" in validators: let nowDT = now() var inputDT: DateTime let inputDateString = inputDataAll.getOrDefault("send_date", "") let inputTimeHrString = inputDataAll.getOrDefault("send_time_hr", "") let inputTimeMinString = inputDataAll.getOrDefault("send_time_min", "") let inputTimeAmPmString = inputDataAll.getOrDefault("send_time_am_pm", "") if inputDateString.len > 0 and inputTimeHrString.len > 0 and inputTimeMinString.len > 0 and inputTimeAmPmString.len > 0: let inputDatetimeString = inputDateString & " " & inputTimeHrString & ":" & inputTimeMinString & ":" & inputTimeAmPmString inputDT = parse(inputDatetimeString, "yyyy-M-d h:m:tt") else: inputDT = now() if inputDT <= nowDT: msgString = "The 'send_date' and 'send_time' fields combined must be a DateTime in the future (compared to the DateTime at form submit)." addFormError("send_date", msgString) proc vInput*(inputName: string, validators: seq[string]) = var inputType: string var inputData: string var inputDataAll: Table[string, string] inputData = formInput(inputName) inputDataAll = formInputAll() #check if 'required' is set and validate input if it exists. otherwise return error. if "required" in validators: if (inputData.len == 0): msgString = "The '" & inputName & "' field is required." addFormError(inputName, msgString) else: #proceed with validation. #get inputType. inputType = vType(inputName, inputData, validators) #check for other validators and run if they exist. vStandard(inputName, inputData, inputType, validators) vHardcoded(inputDataAll, validators) vRegex(inputName, inputData, inputType, validators) #required is not set, but input could still exist. validate it. else: #get inputType. inputType = vType(inputName, inputData, validators) #check for other validators and run if they exist. vStandard(inputName, inputData, inputType, validators) vHardcoded(inputDataAll, validators) vRegex(inputName, inputData, inputType, validators)