diff --git a/README.md b/README.md index acdf773..06b0796 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,7 @@ Baked-in Validations | tiger192 | TIGER192 hash | | semver | Semantic Versioning 2.0.0 | | ulid | Universally Unique Lexicographically Sortable Identifier ULID | +| cve | Common Vulnerabilities and Exposures Identifier (CVE id) | ### Comparisons: | Tag | Description | diff --git a/baked_in.go b/baked_in.go index 48d71b9..48a01ca 100644 --- a/baked_in.go +++ b/baked_in.go @@ -223,6 +223,7 @@ var ( "semver": isSemverFormat, "dns_rfc1035_label": isDnsRFC1035LabelFormat, "credit_card": isCreditCard, + "cve": isCveFormat, "luhn_checksum": hasLuhnChecksum, "mongodb": isMongoDB, "cron": isCron, @@ -2674,6 +2675,13 @@ func isSemverFormat(fl FieldLevel) bool { return semverRegex.MatchString(semverString) } +// isCveFormat is the validation function for validating if the current field's value is a valid cve id, defined in CVE mitre org +func isCveFormat(fl FieldLevel) bool { + cveString := fl.Field().String() + + return cveRegex.MatchString(cveString) +} + // isDnsRFC1035LabelFormat is the validation function // for validating if the current field's value is // a valid dns RFC 1035 label, defined in RFC 1035. diff --git a/doc.go b/doc.go index 66d5e98..4178462 100644 --- a/doc.go +++ b/doc.go @@ -1350,6 +1350,15 @@ More information on https://semver.org/ Usage: semver + +# CVE Identifier + +This validates that a string value is a valid cve id, defined in cve mitre. +More information on https://cve.mitre.org/ + + Usage: cve + + # Credit Card This validates that a string value contains a valid credit card number using Luhn algoritm. diff --git a/regexes.go b/regexes.go index a65ac6f..ba450b3 100644 --- a/regexes.go +++ b/regexes.go @@ -65,6 +65,7 @@ const ( bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$` semverRegexString = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/ dnsRegexStringRFC1035Label = "^[a-z]([-a-z0-9]*[a-z0-9]){0,62}$" + cveRegexString = `^CVE-(1999|2\d{3})-(0[^0]\d{2}|0\d[^0]\d{1}|0\d{2}[^0]|[1-9]{1}\d{3,})$` // CVE Format Id https://cve.mitre.org/cve/identifiers/syntaxchange.html mongodbRegexString = "^[a-f\\d]{24}$" cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})` ) @@ -130,6 +131,7 @@ var ( bicRegex = regexp.MustCompile(bicRegexString) semverRegex = regexp.MustCompile(semverRegexString) dnsRegexRFC1035Label = regexp.MustCompile(dnsRegexStringRFC1035Label) + cveRegex = regexp.MustCompile(cveRegexString) mongodbRegex = regexp.MustCompile(mongodbRegexString) cronRegex = regexp.MustCompile(cronRegexString) ) diff --git a/translations/en/en.go b/translations/en/en.go index 0668bb2..486c95e 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -1366,6 +1366,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be a valid boolean value", override: false, }, + { + tag: "cve", + translation: "{0} must be a valid cve identifier", + override: false, + }, } for _, t := range translations { diff --git a/translations/en/en_test.go b/translations/en/en_test.go index 69f4ee0..df07897 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_test.go @@ -153,6 +153,7 @@ func TestTranslations(t *testing.T) { PostCodeCountry string PostCodeByField string `validate:"postcode_iso3166_alpha2_field=PostCodeCountry"` BooleanString string `validate:"boolean"` + CveString string `validate:"cve"` } var test Test @@ -206,6 +207,7 @@ func TestTranslations(t *testing.T) { test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"} test.Datetime = "2008-Feb-01" test.BooleanString = "A" + test.CveString = "A" test.Inner.RequiredIf = "abcd" @@ -695,6 +697,10 @@ func TestTranslations(t *testing.T) { ns: "Test.BooleanString", expected: "BooleanString must be a valid boolean value", }, + { + ns: "Test.CveString", + expected: "CveString must be a valid cve identifier", + }, } for _, tt := range tests { diff --git a/translations/pt_BR/pt_BR.go b/translations/pt_BR/pt_BR.go index d6883aa..009646c 100644 --- a/translations/pt_BR/pt_BR.go +++ b/translations/pt_BR/pt_BR.go @@ -1321,6 +1321,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} deve ser um valor booleano válido", override: false, }, + { + tag: "cve", + translation: "{0} deve ser um identificador cve válido", + override: false, + }, } for _, t := range translations { diff --git a/translations/pt_BR/pt_BR_test.go b/translations/pt_BR/pt_BR_test.go index 426f246..d50ad9d 100644 --- a/translations/pt_BR/pt_BR_test.go +++ b/translations/pt_BR/pt_BR_test.go @@ -140,6 +140,7 @@ func TestTranslations(t *testing.T) { OneOfString string `validate:"oneof=red green"` OneOfInt int `validate:"oneof=5 63"` BooleanString string `validate:"boolean"` + CveString string `validate:"cve"` } var test Test @@ -173,6 +174,7 @@ func TestTranslations(t *testing.T) { test.NumericString = "12E.00" test.NumberString = "12E" test.BooleanString = "A" + test.CveString = "A" test.Excludes = "este é um texto de teste" test.ExcludesAll = "Isso é Ótimo!" @@ -625,6 +627,10 @@ func TestTranslations(t *testing.T) { ns: "Test.BooleanString", expected: "BooleanString deve ser um valor booleano válido", }, + { + ns: "Test.CveString", + expected: "CveString deve ser um identificador cve válido", + }, } for _, tt := range tests { diff --git a/validator_test.go b/validator_test.go index fddf79b..bb94b3e 100644 --- a/validator_test.go +++ b/validator_test.go @@ -12374,6 +12374,50 @@ func TestSemverFormatValidation(t *testing.T) { } } +func TestCveFormatValidation(t *testing.T) { + + tests := []struct { + value string `validate:"cve"` + tag string + expected bool + }{ + {"CVE-1999-0001", "cve", true}, + {"CVE-1998-0001", "cve", false}, + {"CVE-2000-0001", "cve", true}, + {"CVE-2222-0001", "cve", true}, + {"2222-0001", "cve", false}, + {"-2222-0001", "cve", false}, + {"CVE22220001", "cve", false}, + {"CVE-2222-000001", "cve", false}, + {"CVE-2222-100001", "cve", true}, + {"CVE-2222-99999999999", "cve", true}, + {"CVE-3000-0001", "cve", false}, + {"CVE-1999-0000", "cve", false}, + {"CVE-2099-0000", "cve", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.value, test.tag) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d cve failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d cve failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "cve" { + t.Fatalf("Index: %d cve failed Error: %s", i, errs) + } + } + } + } +} + func TestRFC1035LabelFormatValidation(t *testing.T) { tests := []struct { value string `validate:"dns_rfc1035_label"`