Repository: go-playground/validator
Pull request #983 (2022-08-04 22:15:19)

feat: ✨ add cve validator
## Fixes Or Enhances
Add validator to Common Vulnerabilities and Exposures Identifier according to mitre.org

**Make sure that you've checked the boxes below before you submit PR:**
- [X] Tests exist or have been written that cover this particular change.

@go-playground/validator-maintainers

coveralls: 
[![Coverage Status](https://coveralls.io/builds/51434006/badge)](https://coveralls.io/builds/51434006)

Coverage increased (+0.009%) to 74.186% when pulling **8a3ec10d5f2ecb528c8f12fd6a82edcc8ecda677 on EduardoRMello:feat/add-cve-validator** into **9e2ea4038020b5c7e3802a21cfa4e3afcfdcd276 on go-playground:master**.

EduardoRMello: Hello @deankarn ! Do you intend to maintain this repo ?
zemzale: @deankarn I don't have the right to merge/approve. Could you do it? 

Patch:
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/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 {


Test patch:
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_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"`