2025-05-15 08:01:35 -05:00
#[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 < https : / / www . gnu . org / licenses / > 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 ) :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must have a character count of at least " & $ sizeValue & " . "
2025-05-15 08:01:35 -05:00
addFormError ( inputName , msgString )
of " integer " :
if not ( parseIntIf ( inputData ) > = sizeValue ) :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must have a value of at least " & $ sizeValue & " . "
2025-05-15 08:01:35 -05:00
addFormError ( inputName , msgString )
of " float " :
if not ( parseFloat ( inputData ) > = sizeValue . float ) :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must have a value of at least " & $ sizeValue & " . "
2025-05-15 08:01:35 -05:00
addFormError ( inputName , msgString )
of " max " :
#how we determine size depends on input type.
case inputType :
of " string " :
if inputData . len > sizeValue :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must have a character count that is no greater than " & $ sizeValue & " . "
2025-05-15 08:01:35 -05:00
addFormError ( inputName , msgString )
of " integer " :
if parseIntIf ( inputData ) > sizeValue :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must have a value that is no greater than " & $ sizeValue & " . "
2025-05-15 08:01:35 -05:00
addFormError ( inputName , msgString )
of " float " :
if parseFloat ( inputData ) > sizeValue . float :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must have a value that is no greater than " & $ sizeValue & " . "
2025-05-15 08:01:35 -05:00
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 :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must be a whole number. "
2025-05-15 08:01:35 -05:00
addFormError ( inputName , msgString )
elif " float " in validators :
try :
discard parseFloat ( inputData )
inputType = " float "
except :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must be a floating point number; a number with a decimal point. i.e. ' 1.5 ' "
2025-05-15 08:01:35 -05:00
addFormError ( inputName , msgString )
elif " boolean " in validators :
if ( inputData = = " true " ) or not ( inputData = = " false " ) :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must be interpretable as a boolean. i.e. true, or false. "
2025-05-15 08:01:35 -05:00
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 :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field is required when " & conditionField & " isn ' t set. "
2025-05-15 08:01:35 -05:00
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 :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field must be empty if " & conditionField & " is not. "
2025-05-15 08:01:35 -05:00
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 :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field is also required when " & conditionField & " is set. "
2025-05-15 08:01:35 -05:00
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 :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field is required when " & conditionField1 & " is set and " & conditionField2 & " isn ' t set. "
2025-05-15 08:01:35 -05:00
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 :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field is required when " & conditionField1 & " is set and " & conditionField2 & " and " & conditionField3 & " aren ' t set. "
2025-05-15 08:01:35 -05:00
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 :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field is required when " & conditionField1 & " equals " & conditionField2
2025-05-15 08:01:35 -05:00
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 :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field is required when " & conditionField1 & " equals " & conditionField2 & " and " & conditionField3 & " is not set. "
2025-05-15 08:01:35 -05:00
addFormError ( inputName , msgString )
proc vStandard ( inputName : string , inputData : string , inputType : string , validators : seq [ string ] ) =
if " email " in validators :
if not isEmail ( inputData ) :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field is not recognized as a valid email address. "
2025-05-15 08:01:35 -05:00
addFormError ( inputName , msgString )
if " min_complexity " in validators :
if not isStrongPassword ( inputData ) :
2025-05-15 08:58:20 -05:00
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. "
2025-05-15 08:01:35 -05:00
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 :
2025-05-15 08:58:20 -05:00
msgString = " The ' send_date ' and ' send_time ' fields combined must be a DateTime in the future (compared to the DateTime at form submit). "
2025-05-15 08:01:35 -05:00
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 ) :
2025-05-15 08:58:20 -05:00
msgString = " The ' " & inputName & " ' field is required. "
2025-05-15 08:01:35 -05:00
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 )