forked from ITwrxOrg/Forget-Me-Not
227 lines
11 KiB
Nim
227 lines
11 KiB
Nim
#[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):
|
|
msgString = inputName & " must have a character count of at least " & $sizeValue & " ."
|
|
addFormError(inputName, msgString)
|
|
of "integer":
|
|
if not (parseIntIf(inputData) >= sizeValue):
|
|
msgString = inputName & " must have a value of at least " & $sizeValue & " ."
|
|
addFormError(inputName, msgString)
|
|
of "float":
|
|
if not (parseFloat(inputData) >= sizeValue.float):
|
|
msgString = inputName & " 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 = inputName & " must have a character count that is no greater than " & $sizeValue & " ."
|
|
addFormError(inputName, msgString)
|
|
of "integer":
|
|
if parseIntIf(inputData) > sizeValue:
|
|
msgString = inputName & " must have a value that is no greater than " & $sizeValue & " ."
|
|
addFormError(inputName, msgString)
|
|
of "float":
|
|
if parseFloat(inputData) > sizeValue.float:
|
|
msgString = inputName & " 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 = inputName & " must be a whole number."
|
|
addFormError(inputName, msgString)
|
|
elif "float" in validators:
|
|
try:
|
|
discard parseFloat(inputData)
|
|
inputType = "float"
|
|
except:
|
|
msgString = inputName & " 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 = inputName & " 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 = inputName & " 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 = inputName & " 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 = inputName & " 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 = inputName & " 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 = inputName & " 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 = inputName & " 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 = inputName & " 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 = inputName & " is not recognized as a valid email address."
|
|
addFormError(inputName, msgString)
|
|
if "min_complexity" in validators:
|
|
if not isStrongPassword(inputData):
|
|
msgString = inputName & " 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 = "send_date and send_time 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 = inputName & " 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)
|