3
0
Files
corteza/compose/service/values/sanitizer.go
2020-07-17 13:37:11 +02:00

258 lines
5.7 KiB
Go

package values
import (
"strconv"
"strings"
"time"
"github.com/cortezaproject/corteza-server/compose/types"
)
type (
sanitizer struct{}
)
// Sanitizer initializes sanitizer
//
// Not really needed, following pattern in the package
func Sanitizer() *sanitizer {
return &sanitizer{}
}
// Run cleans up input data
// - fix multi-value order/place index
// - trim all the strings!
// - parse & format input values to match field specific -- nullify/falsify invalid
// - field kind specific, no errors raised, data is modified
//
// Existing data (when updating record) is not yet loaded at this point
func (s sanitizer) Run(m *types.Module, vv types.RecordValueSet) (out types.RecordValueSet) {
out = make([]*types.RecordValue, 0, len(vv))
for _, f := range m.Fields {
// Reorder and sanitize place value (no gaps)
//
// Values are ordered when received so we treat them like it
// and assign the appropriate place no.
var i = 0
for _, v := range vv.FilterByName(f.Name) {
if v.IsDeleted() {
continue
}
c := v.Clone()
c.Place = uint(i)
out = append(out, c)
i++
}
}
var (
f *types.ModuleField
kind string
)
for _, v := range out {
f = m.Fields.FindByName(v.Name)
if f == nil {
// Unknown field,
// if it is not handled before,
// sanitizer does not care about it
continue
}
if v.IsDeleted() || !v.Updated {
// Ignore unchanged and deleted
continue
}
kind = strings.ToLower(f.Kind)
if kind != "string" {
// Trim all but string
v.Value = strings.TrimSpace(v.Value)
}
if f.IsRef() {
if refy.MatchString(v.Value) {
v.Ref, _ = strconv.ParseUint(v.Value, 10, 64)
}
if v.Ref == 0 {
v.Value = ""
}
}
// Per field type validators
switch strings.ToLower(f.Kind) {
case "bool":
v = s.sBool(v, f, m)
case "datetime":
v = s.sDatetime(v, f, m)
case "number":
v = s.sNumber(v, f, m)
// Uncomment when they become relevant for sanitization
//case "email":
// v = s.sEmail(v, f, m)
//case "file":
// v = s.sFile(v, f, m)
//case "record":
// v = s.sRecord(v, f, m)
//case "select":
// v = s.sSelect(v, f, m)
//case "string":
// v = s.sString(v, f, m)
//case "url":
// v = s.sUrl(v, f, m)
//case "user":
// v = s.sUser(v, f, m)
}
}
return
}
func (sanitizer) sBool(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
if truthy.MatchString(strings.ToLower(v.Value)) {
v.Value = strBoolTrue
} else {
v.Value = strBoolFalse
}
return v
}
func (sanitizer) sDatetime(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
var (
// input format set
inputFormats []string
// output format
internalFormat string
)
if f.Options.Bool("onlyDate") {
internalFormat = datetimeInternalFormatDate
inputFormats = []string{
datetimeInternalFormatDate,
"02 Jan 06",
"Monday, 02-Jan-06",
"Mon, 02 Jan 2006",
"2006/_1/_2",
}
} else if f.Options.Bool("onlyTime") {
internalFormat = datetimeIntenralFormatTime
inputFormats = []string{
datetimeIntenralFormatTime,
"15:04",
"15:04:05Z07:00",
"15:04:05 MST",
"15:04:05 -0700",
"15:04 MST",
"15:04Z07:00",
"15:04 -0700",
time.Kitchen,
}
} else {
internalFormat = datetimeInternalFormatFull
// date & time
inputFormats = []string{
datetimeInternalFormatFull,
time.RFC1123Z,
time.RFC1123,
time.RFC850,
time.RFC822Z,
time.RFC822,
time.RubyDate,
time.UnixDate,
time.ANSIC,
"2006/_1/_2 15:04:05",
"2006/_1/_2 15:04",
}
// if string looks like a RFC 3330 (ISO 8601), see if we need to suffix it with Z
if isoDaty.MatchString(v.Value) && !hasTimezone.MatchString(v.Value) {
// No timezone, add Z to satisfy parser
v.Value = v.Value + "Z"
// Simplifiy list of rules
inputFormats = []string{time.RFC3339}
}
}
for _, format := range inputFormats {
parsed, err := time.Parse(format, v.Value)
if err == nil {
v.Value = parsed.UTC().Format(internalFormat)
return v
}
}
v.Value = ""
return v
}
// sNumber sanitizes
func (sanitizer) sNumber(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
base, err := strconv.ParseFloat(v.Value, 64)
if err != nil {
v.Value = "0"
return v
}
// calculate percision
var p = 0
if f.Options != nil {
p = int(f.Options.Int64(fieldOpt_Number_precision))
if p < fieldOpt_Number_precision_min {
p = fieldOpt_Number_precision_min
} else if p > fieldOpt_Number_precision_max {
p = fieldOpt_Number_precision_max
}
}
// Format the value to the desired precision
v.Value = strconv.FormatFloat(base, 'f', p, 64)
// In case of fractures, remove trailing 0's
if strings.Contains(v.Value, ".") {
v.Value = strings.TrimRight(v.Value, "0")
v.Value = strings.TrimRight(v.Value, ".")
}
return v
}
//
//func (sanitizer) sEmail(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
// // @todo extract from "name" <email> format
// return v
//}
//
//func (sanitizer) sFile(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
// return v
//}
//
//
//func (sanitizer) sRecord(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
// return v
//}
//
//func (sanitizer) sSelect(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
// return v
//}
//
//func (sanitizer) sString(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
// return v
//}
//
//func (sanitizer) sUrl(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
// return v
//}
//
//func (sanitizer) sUser(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue {
// return v
//}