From 030800a8550659dfef76077c35692dee28893121 Mon Sep 17 00:00:00 2001 From: Kyriakos Georgiou Date: Mon, 26 Mar 2018 22:22:56 -0400 Subject: [PATCH 01/27] Add validation for base64 URL safe values --- baked_in.go | 6 ++++++ doc.go | 9 +++++++++ regexes.go | 2 ++ validator_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/baked_in.go b/baked_in.go index 231b78e..66ecc61 100644 --- a/baked_in.go +++ b/baked_in.go @@ -96,6 +96,7 @@ var ( "url": isURL, "uri": isURI, "base64": isBase64, + "base64url": isBase64URL, "contains": contains, "containsany": containsAny, "containsrune": containsRune, @@ -845,6 +846,11 @@ func isBase64(fl FieldLevel) bool { return base64Regex.MatchString(fl.Field().String()) } +// IsBase64URL is the validation function for validating if the current field's value is a valid base64 URL safe string. +func isBase64URL(fl FieldLevel) bool { + return base64URLRegex.MatchString(fl.Field().String()) +} + // IsURI is the validation function for validating if the current field's value is a valid URI. func isURI(fl FieldLevel) bool { diff --git a/doc.go b/doc.go index f7efe23..5dfc518 100644 --- a/doc.go +++ b/doc.go @@ -609,6 +609,15 @@ this with the omitempty tag. Usage: base64 +Base64URL String + +This validates that a string value contains a valid base64 URL safe value. +Although an empty string is a valid base64 URL safe value, this will report +an empty string as an error, if you wish to accept an empty string as valid +you can use this with the omitempty tag. + + Usage: base64url + Contains This validates that a string value contains the substring value. diff --git a/regexes.go b/regexes.go index 78f3ea0..5dee75f 100644 --- a/regexes.go +++ b/regexes.go @@ -17,6 +17,7 @@ const ( hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" + base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$" iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$" uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" @@ -49,6 +50,7 @@ var ( hslaRegex = regexp.MustCompile(hslaRegexString) emailRegex = regexp.MustCompile(emailRegexString) base64Regex = regexp.MustCompile(base64RegexString) + base64URLRegex = regexp.MustCompile(base64URLRegexString) iSBN10Regex = regexp.MustCompile(iSBN10RegexString) iSBN13Regex = regexp.MustCompile(iSBN13RegexString) uUID3Regex = regexp.MustCompile(uUID3RegexString) diff --git a/validator_test.go b/validator_test.go index 9600d0f..c6467db 100644 --- a/validator_test.go +++ b/validator_test.go @@ -5,6 +5,7 @@ import ( "context" "database/sql" "database/sql/driver" + "encoding/base64" "encoding/json" "fmt" "reflect" @@ -4398,6 +4399,53 @@ func TestBase64Validation(t *testing.T) { AssertError(t, errs, "", "", "", "", "base64") } +func TestBase64URLValidation(t *testing.T) { + validate := New() + + testCases := []struct { + decoded, encoded string + success bool + }{ + // empty string, although a valid base64 string, should fail + {"", "", false}, + // invalid length + {"", "a", false}, + // base64 with padding + {"f", "Zg==", true}, + {"fo", "Zm8=", true}, + // base64 without padding + {"foo", "Zm9v", true}, + {"", "Zg", false}, + {"", "Zm8", false}, + // base64 URL safe encoding with invalid, special characters '+' and '/' + {"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+", false}, + {"\x14\xfb\x9c\x03\xf9\x73", "FPucA/lz", false}, + // base64 URL safe encoding with valid, special characters '-' and '_' + {"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l-", true}, + {"\x14\xfb\x9c\x03\xf9\x73", "FPucA_lz", true}, + // non base64 characters + {"", "@mc=", false}, + {"", "Zm 9", false}, + } + for _, tc := range testCases { + err := validate.Var(tc.encoded, "base64url") + if tc.success { + Equal(t, err, nil) + // make sure encoded value is decoded back to the expected value + d, err := base64.URLEncoding.DecodeString(tc.encoded) + Equal(t, err, nil) + Equal(t, tc.decoded, string(d)) + } else { + NotEqual(t, err, nil) + if len(tc.encoded) > 0 { + // make sure that indeed the encoded value was faulty + _, err := base64.URLEncoding.DecodeString(tc.encoded) + NotEqual(t, err, nil) + } + } + } +} + func TestNoStructLevelValidation(t *testing.T) { type Inner struct { From ba5836f763296c1b4fb7c75e72d42501981e481c Mon Sep 17 00:00:00 2001 From: josh Date: Fri, 6 Apr 2018 19:58:09 -0700 Subject: [PATCH 02/27] add bitcoin and ethereum address regexes --- regexes.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/regexes.go b/regexes.go index 78f3ea0..82f7ec2 100644 --- a/regexes.go +++ b/regexes.go @@ -32,6 +32,9 @@ const ( sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 + btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address + btcAddressRegexStringBech32 = `^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` ) var ( @@ -64,4 +67,7 @@ var ( sSNRegex = regexp.MustCompile(sSNRegexString) hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952) hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123) + btcAddressRegex = regexp.MustCompile(btcAddressRegexString) + btcAddressRegexBech32 = regexp.MustCompile(btcAddressRegexStringBech32) + ethAddressRegex = regexp.MustCompile(ethAddressRegexString) ) From 2a8a33391358a707428f8f49f78e2cdd93dc3a96 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 8 Apr 2018 10:17:23 -0700 Subject: [PATCH 03/27] Update doc.go --- doc.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc.go b/doc.go index 5dfc518..14b78e2 100644 --- a/doc.go +++ b/doc.go @@ -611,7 +611,8 @@ this with the omitempty tag. Base64URL String -This validates that a string value contains a valid base64 URL safe value. +This validates that a string value contains a valid base64 URL safe value +according the the RFC4648 spec. Although an empty string is a valid base64 URL safe value, this will report an empty string as an error, if you wish to accept an empty string as valid you can use this with the omitempty tag. From d3984c2e0b6c0706c2d3bfa46730495657742583 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 8 Apr 2018 10:21:03 -0700 Subject: [PATCH 04/27] Update main.go --- _examples/simple/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_examples/simple/main.go b/_examples/simple/main.go index 9e3d106..459356d 100644 --- a/_examples/simple/main.go +++ b/_examples/simple/main.go @@ -52,7 +52,7 @@ func validateStruct() { Addresses: []*Address{address}, } - // returns nil or ValidationErrors ( map[string]*FieldError ) + // returns nil or ValidationErrors ( []FieldError ) err := validate.Struct(user) if err != nil { From ff1ee42e838f15a68230cebda8aaa21427506175 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 8 Apr 2018 10:23:56 -0700 Subject: [PATCH 05/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31bdcb0..7b3ab46 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.13.0-green.svg) +![Project status](https://img.shields.io/badge/version-9.14.0-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) From 36d83b0b83a881aa09b2d4e5d6a6493f435b4c82 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 06/27] Added in validation function, tests, and docs --- baked_in.go | 16 ++++++++++ doc.go | 16 +++++++++- validator_test.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/baked_in.go b/baked_in.go index 231b78e..df790d8 100644 --- a/baked_in.go +++ b/baked_in.go @@ -105,6 +105,8 @@ var ( "isbn": isISBN, "isbn10": isISBN10, "isbn13": isISBN13, + "eth_addr": isEthereumAddress, + "btc_addr": isBitcoinAddress, "uuid": isUUID, "uuid3": isUUID3, "uuid4": isUUID4, @@ -387,6 +389,20 @@ func isISBN10(fl FieldLevel) bool { return checksum%11 == 0 } +// IsEthereumAddress is the validation function for validating if the field's value is a valid ethereum address based currently only on the format +func isEthereumAddress(fl FieldLevel) bool { + field := fl.Field() + + return ethAddressRegex.MatchString(field.String()) +} + +// IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address, currently only based on the format +func isBitcoinAddress(fl FieldLevel) bool { + field := fl.Field() + + return btcAddressRegex.MatchString(field.String()) || btcAddressRegexBech32.MatchString(field.String()) +} + // ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param. func excludesRune(fl FieldLevel) bool { return !containsRune(fl) diff --git a/doc.go b/doc.go index f7efe23..5522ebb 100644 --- a/doc.go +++ b/doc.go @@ -609,6 +609,21 @@ this with the omitempty tag. Usage: base64 +Bitcoin Address + +This validates that a string value contains a valid bitcoin address. +The format of the string is checked to ensure it matches one of the three formats +P2PKH, P2SH, or Bech32. Currently no further validation is performed. + + Usage: btc_addr + +Ethereum Address + +This validates that a string value contains a valid ethereum address. +The format of the string is checked to ensure it matches the standard Ethereum address format + + Usage: eth_addr + Contains This validates that a string value contains the substring value. @@ -665,7 +680,6 @@ This validates that a string value contains a valid isbn13 value. Usage: isbn13 - Universally Unique Identifier UUID This validates that a string value contains a valid UUID. diff --git a/validator_test.go b/validator_test.go index 9600d0f..d8be987 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4398,6 +4398,85 @@ func TestBase64Validation(t *testing.T) { AssertError(t, errs, "", "", "", "", "base64") } +func TestEthereumAddressValidation(t *testing.T){ + + validate := New() + + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, + {"123f681646d4a755815f9cb19e1acc8565a0c2ac", false}, + {"0x02F9AE5f22EA3fA88F05780B30385bECFacbf130", true}, + {"0x123f681646d4a755815f9cb19e1acc8565a0c2ac", true}, + } + + + + for i, test := range tests { + + errs := validate.Var(test.param, "eth_addr") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d eth_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d eth_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "eth_addr" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } + } + } + } +} + +func TestBitcoinAddressValidation(t *testing.T){ + + validate := New() + + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, + {"123f681646d4a755815f9cb19e1acc8565a0c2ac", false}, + {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, + {"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy", true}, + {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, + } + + + + for i, test := range tests { + + errs := validate.Var(test.param, "btc_addr") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "btc_addr" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } + } + } + } +} + + + func TestNoStructLevelValidation(t *testing.T) { type Inner struct { From 60417282a404387d562239f17211e67d7f66c17d Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 07/27] verifies p2pkh addresses --- baked_in.go | 48 +++++++++++++++++++++++++++++++++++++++++++++-- validator_test.go | 15 ++++++++------- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/baked_in.go b/baked_in.go index df790d8..55bd4c4 100644 --- a/baked_in.go +++ b/baked_in.go @@ -11,6 +11,8 @@ import ( "sync" "time" "unicode/utf8" + "crypto/sha256" + "bytes" ) // Func accepts a FieldLevel interface for all validation needs. The return @@ -398,9 +400,51 @@ func isEthereumAddress(fl FieldLevel) bool { // IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address, currently only based on the format func isBitcoinAddress(fl FieldLevel) bool { - field := fl.Field() + address := fl.Field().String() + + if btcAddressRegex.MatchString(address) { + + alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + + decode := [25]byte{} + + for _, n := range []byte(address) { + d := bytes.IndexByte(alphabet, n) + + if(d == -1){ + return false + } + + for i := 24; i >= 0; i-- { + d += 58 * int(decode[i]) + decode[i] = byte(d % 256) + d /= 256 + } + } + + + if decode[0] != 0 { + return false + } + + h := sha256.New() + h.Write(decode[:21]) + d := h.Sum([]byte{}) + h = sha256.New() + h.Write(d) + + validchecksum := [4]byte{} + computedchecksum := [4]byte{} + + copy(computedchecksum[:], h.Sum(d[:0])) + copy(validchecksum[:], decode[21:]) + + println(address, "::", validchecksum[:], computedchecksum[:]) + + return validchecksum == computedchecksum + } - return btcAddressRegex.MatchString(field.String()) || btcAddressRegexBech32.MatchString(field.String()) + return btcAddressRegexBech32.MatchString(address) } // ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param. diff --git a/validator_test.go b/validator_test.go index d8be987..c16d426 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4446,10 +4446,11 @@ func TestBitcoinAddressValidation(t *testing.T){ }{ {"", false}, {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, - {"123f681646d4a755815f9cb19e1acc8565a0c2ac", false}, - {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, - {"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy", true}, - {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, + {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, + {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, // valid p2pkh address + {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, // valid p2sh address + {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, // valid bech32 address + } @@ -4460,15 +4461,15 @@ func TestBitcoinAddressValidation(t *testing.T){ if test.expected { if !IsEqual(errs, nil) { - t.Fatalf("Index: %d btc_addr failed Error: %s", i, errs) + t.Fatalf("Index: %d btc_addr failed with Error: %s", i, errs) } } else { if IsEqual(errs, nil) { - t.Fatalf("Index: %d btc_addr failed Error: %s", i, errs) + t.Fatalf("Index: %d btc_addr failed with Error: %s", i, errs) } else { val := getError(errs, "", "") if val.Tag() != "btc_addr" { - t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + t.Fatalf("Index: %d Latitude failed with Error: %s", i, errs) } } } From 3df85bdcfcb38cf72848f07236c888bf0b8f4402 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 08/27] p2sh validation and more tests --- baked_in.go | 2 +- validator_test.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/baked_in.go b/baked_in.go index 55bd4c4..41f68a2 100644 --- a/baked_in.go +++ b/baked_in.go @@ -423,7 +423,7 @@ func isBitcoinAddress(fl FieldLevel) bool { } - if decode[0] != 0 { + if !(decode[0] == 0 || decode[0] == 5) { return false } diff --git a/validator_test.go b/validator_test.go index c16d426..aaab196 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4446,7 +4446,10 @@ func TestBitcoinAddressValidation(t *testing.T){ }{ {"", false}, {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, - {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, + {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, // invalid p2pkh address with invalid characters + {"1P9RQEr2XeE3PEb44ZE35sfZRRW1JH8Uqx", false}, // invald p2pkh address with valid characters + {"3P14159I73E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with invalid characters + {"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with valid characters {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, // valid p2pkh address {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, // valid p2sh address {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, // valid bech32 address From 6deddf27eb8d4d5e680e7afb4af6a844d9744021 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 09/27] proper bech32 address validation --- baked_in.go | 153 +++++++++++++++++++++++++++++++++++++--------- doc.go | 11 +++- regexes.go | 6 +- validator_test.go | 47 ++++++++++++-- 4 files changed, 181 insertions(+), 36 deletions(-) diff --git a/baked_in.go b/baked_in.go index 41f68a2..1dae5a9 100644 --- a/baked_in.go +++ b/baked_in.go @@ -109,6 +109,7 @@ var ( "isbn13": isISBN13, "eth_addr": isEthereumAddress, "btc_addr": isBitcoinAddress, + "btc_addr_bech32": isBitcoinBech32Address, "uuid": isUUID, "uuid3": isUUID3, "uuid4": isUUID4, @@ -393,58 +394,150 @@ func isISBN10(fl FieldLevel) bool { // IsEthereumAddress is the validation function for validating if the field's value is a valid ethereum address based currently only on the format func isEthereumAddress(fl FieldLevel) bool { - field := fl.Field() + address := fl.Field().String() + + if !ethAddressRegex.MatchString(address) { + return false + } + + if ethaddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) { + return true + } - return ethAddressRegex.MatchString(field.String()) + // checksum validation is blocked by https://github.com/golang/crypto/pull/28 + + return true } -// IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address, currently only based on the format +// IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address func isBitcoinAddress(fl FieldLevel) bool { address := fl.Field().String() - if btcAddressRegex.MatchString(address) { - - alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + if !btcAddressRegex.MatchString(address) { + return false + } - decode := [25]byte{} + alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") - for _, n := range []byte(address) { - d := bytes.IndexByte(alphabet, n) + decode := [25]byte{} - if(d == -1){ - return false - } + for _, n := range []byte(address) { + d := bytes.IndexByte(alphabet, n) - for i := 24; i >= 0; i-- { - d += 58 * int(decode[i]) - decode[i] = byte(d % 256) - d /= 256 - } + for i := 24; i >= 0; i-- { + d += 58 * int(decode[i]) + decode[i] = byte(d % 256) + d /= 256 } + } - if !(decode[0] == 0 || decode[0] == 5) { + if !(decode[0] == 0 || decode[0] == 5) { + return false + } + + h := sha256.New() + h.Write(decode[:21]) + d := h.Sum([]byte{}) + h = sha256.New() + h.Write(d) + + validchecksum := [4]byte{} + computedchecksum := [4]byte{} + + copy(computedchecksum[:], h.Sum(d[:0])) + copy(validchecksum[:], decode[21:]) + + return validchecksum == computedchecksum +} + +// IsBitcoinAddress is the validation function for validating if the field's value is a valid bech32 btc address +func isBitcoinBech32Address(fl FieldLevel) bool { + address := fl.Field().String() + + if !btcAddressRegexBech32.MatchString(address){ + return false + } + + am := len(address) % 8 + + if am == 0 || am == 3 || am == 5{ + return false + } + + address = strings.ToLower(address) + + alphabet := "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + + hr := []int{3, 3, 0, 2, 3} // the human readable part will always be bc + dp := []int{} + + for _, c := range []rune(address[3:]) { + dp = append(dp, strings.IndexRune(alphabet, c)) + } + + ver := dp[0] + + if ver < 0 || ver > 16{ + return false + } + + if ver == 0 { + if len(address) != 42 && len(address) != 62 { return false } + } + + values := append(hr, dp...) + + GEN := []int{ 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 } + + p := 1 + + for _, v := range values { + b := p >> 25 + p = (p & 0x1ffffff) << 5 ^ v + + for i := 0; i < 5; i++ { + if (b >> uint(i)) & 1 == 1 { + p ^= GEN[i] + } + } + } - h := sha256.New() - h.Write(decode[:21]) - d := h.Sum([]byte{}) - h = sha256.New() - h.Write(d) + if p != 1 { + return false + } - validchecksum := [4]byte{} - computedchecksum := [4]byte{} + b := uint(0) + acc := 0 + mv := (1 << 5) - 1 - copy(computedchecksum[:], h.Sum(d[:0])) - copy(validchecksum[:], decode[21:]) + sw := []int{} - println(address, "::", validchecksum[:], computedchecksum[:]) + dp = dp[:len(dp) - 6] - return validchecksum == computedchecksum + for _, v := range dp[1:]{ + if v < 0 || (v >> 5) != 0{ + return false + } + acc = (acc << 5) | v + b += 5 + for b >= 8{ + b -= 8 + sw = append(sw, (acc>>b)&mv) + } + } + + if len(sw) < 2 || len(sw) > 40{ + return false + } + + if ver == 0 && len(sw) != 20 && len(sw) != 32 { + return false } - return btcAddressRegexBech32.MatchString(address) + return true } // ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param. diff --git a/doc.go b/doc.go index 5522ebb..fdb689a 100644 --- a/doc.go +++ b/doc.go @@ -613,14 +613,23 @@ Bitcoin Address This validates that a string value contains a valid bitcoin address. The format of the string is checked to ensure it matches one of the three formats -P2PKH, P2SH, or Bech32. Currently no further validation is performed. +P2PKH, P2SH and performs checksum validation. Usage: btc_addr +Bitcoin Bech32 Address (segwit) + +This validates that a string value contains a valid bitcoin Bech32 address as defined +by bip-0173 (https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) +Special thanks to Pieter Wuille for providng reference implementations. + + Usage: btc_addr_bech32 + Ethereum Address This validates that a string value contains a valid ethereum address. The format of the string is checked to ensure it matches the standard Ethereum address format +Full validation is blocked by https://github.com/golang/crypto/pull/28 Usage: eth_addr diff --git a/regexes.go b/regexes.go index 82f7ec2..3492c6a 100644 --- a/regexes.go +++ b/regexes.go @@ -33,8 +33,10 @@ const ( hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address - btcAddressRegexStringBech32 = `^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + btcAddressRegexStringBech32 = `^([bB][cC]1)[02-9ac-hj-np-zAC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` + ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` + ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` ) var ( @@ -70,4 +72,6 @@ var ( btcAddressRegex = regexp.MustCompile(btcAddressRegexString) btcAddressRegexBech32 = regexp.MustCompile(btcAddressRegexStringBech32) ethAddressRegex = regexp.MustCompile(ethAddressRegexString) + ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) + ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) ) diff --git a/validator_test.go b/validator_test.go index aaab196..a9ef1d4 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4452,12 +4452,8 @@ func TestBitcoinAddressValidation(t *testing.T){ {"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with valid characters {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, // valid p2pkh address {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, // valid p2sh address - {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, // valid bech32 address - } - - for i, test := range tests { errs := validate.Var(test.param, "btc_addr") @@ -4479,6 +4475,49 @@ func TestBitcoinAddressValidation(t *testing.T){ } } +func TestBitcoinBech32AddressValidation(t *testing.T){ + + validate := New() + + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, // invalid bech32 address with invalid startingcharacters + {"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", false}, + {"bc1rw5uspcuh", false}, + {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", false}, + {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", false}, + {"bc1gmk9yu", false}, + {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", true}, + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", true}, + {"BC1SW50QA3JX3S", true}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", true}, + } + + for i, test := range tests { + + errs := validate.Var(test.param, "btc_addr_bech32") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr_bech32 failed with Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr_bech32 failed with Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "btc_addr_bech32" { + t.Fatalf("Index: %d Latitude failed with Error: %s", i, errs) + } + } + } + } +} func TestNoStructLevelValidation(t *testing.T) { From ce6284a6fc9915bcfc735ce87d0347224c8a3944 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 10/27] confirm only all upper or all lower case address are valid --- baked_in.go | 2 +- regexes.go | 138 +++++++++++++++++++++++----------------------- validator_test.go | 1 + 3 files changed, 72 insertions(+), 69 deletions(-) diff --git a/baked_in.go b/baked_in.go index 1dae5a9..057ba0f 100644 --- a/baked_in.go +++ b/baked_in.go @@ -455,7 +455,7 @@ func isBitcoinAddress(fl FieldLevel) bool { func isBitcoinBech32Address(fl FieldLevel) bool { address := fl.Field().String() - if !btcAddressRegexBech32.MatchString(address){ + if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address){ return false } diff --git a/regexes.go b/regexes.go index 3492c6a..13ae0b6 100644 --- a/regexes.go +++ b/regexes.go @@ -3,75 +3,77 @@ package validator import "regexp" const ( - alphaRegexString = "^[a-zA-Z]+$" - alphaNumericRegexString = "^[a-zA-Z0-9]+$" - alphaUnicodeRegexString = "^[\\p{L}]+$" - alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$" - numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$" - numberRegexString = "^[0-9]+$" - hexadecimalRegexString = "^[0-9a-fA-F]+$" - hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" - rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$" - rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" - hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" - hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" - emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" - base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" - iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" - iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$" - uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" - uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" - uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" - uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" - aSCIIRegexString = "^[\x00-\x7F]*$" - printableASCIIRegexString = "^[\x20-\x7E]*$" - multibyteRegexString = "[^\x00-\x7F]" - dataURIRegexString = "^data:.+\\/(.+);base64$" - latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" - longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" - sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` - hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 - hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 - btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address - btcAddressRegexStringBech32 = `^([bB][cC]1)[02-9ac-hj-np-zAC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 - ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` - ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` - ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` + alphaRegexString = "^[a-zA-Z]+$" + alphaNumericRegexString = "^[a-zA-Z0-9]+$" + alphaUnicodeRegexString = "^[\\p{L}]+$" + alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$" + numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$" + numberRegexString = "^[0-9]+$" + hexadecimalRegexString = "^[0-9a-fA-F]+$" + hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" + rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$" + rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" + hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" + hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" + emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" + base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" + iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" + iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$" + uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" + uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + aSCIIRegexString = "^[\x00-\x7F]*$" + printableASCIIRegexString = "^[\x20-\x7E]*$" + multibyteRegexString = "[^\x00-\x7F]" + dataURIRegexString = "^data:.+\\/(.+);base64$" + latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" + longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" + sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` + hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 + hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 + btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address + btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` + ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` + ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` ) var ( - alphaRegex = regexp.MustCompile(alphaRegexString) - alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString) - alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString) - alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString) - numericRegex = regexp.MustCompile(numericRegexString) - numberRegex = regexp.MustCompile(numberRegexString) - hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString) - hexcolorRegex = regexp.MustCompile(hexcolorRegexString) - rgbRegex = regexp.MustCompile(rgbRegexString) - rgbaRegex = regexp.MustCompile(rgbaRegexString) - hslRegex = regexp.MustCompile(hslRegexString) - hslaRegex = regexp.MustCompile(hslaRegexString) - emailRegex = regexp.MustCompile(emailRegexString) - base64Regex = regexp.MustCompile(base64RegexString) - iSBN10Regex = regexp.MustCompile(iSBN10RegexString) - iSBN13Regex = regexp.MustCompile(iSBN13RegexString) - uUID3Regex = regexp.MustCompile(uUID3RegexString) - uUID4Regex = regexp.MustCompile(uUID4RegexString) - uUID5Regex = regexp.MustCompile(uUID5RegexString) - uUIDRegex = regexp.MustCompile(uUIDRegexString) - aSCIIRegex = regexp.MustCompile(aSCIIRegexString) - printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString) - multibyteRegex = regexp.MustCompile(multibyteRegexString) - dataURIRegex = regexp.MustCompile(dataURIRegexString) - latitudeRegex = regexp.MustCompile(latitudeRegexString) - longitudeRegex = regexp.MustCompile(longitudeRegexString) - sSNRegex = regexp.MustCompile(sSNRegexString) - hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952) - hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123) - btcAddressRegex = regexp.MustCompile(btcAddressRegexString) - btcAddressRegexBech32 = regexp.MustCompile(btcAddressRegexStringBech32) - ethAddressRegex = regexp.MustCompile(ethAddressRegexString) - ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) - ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) + alphaRegex = regexp.MustCompile(alphaRegexString) + alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString) + alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString) + alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString) + numericRegex = regexp.MustCompile(numericRegexString) + numberRegex = regexp.MustCompile(numberRegexString) + hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString) + hexcolorRegex = regexp.MustCompile(hexcolorRegexString) + rgbRegex = regexp.MustCompile(rgbRegexString) + rgbaRegex = regexp.MustCompile(rgbaRegexString) + hslRegex = regexp.MustCompile(hslRegexString) + hslaRegex = regexp.MustCompile(hslaRegexString) + emailRegex = regexp.MustCompile(emailRegexString) + base64Regex = regexp.MustCompile(base64RegexString) + iSBN10Regex = regexp.MustCompile(iSBN10RegexString) + iSBN13Regex = regexp.MustCompile(iSBN13RegexString) + uUID3Regex = regexp.MustCompile(uUID3RegexString) + uUID4Regex = regexp.MustCompile(uUID4RegexString) + uUID5Regex = regexp.MustCompile(uUID5RegexString) + uUIDRegex = regexp.MustCompile(uUIDRegexString) + aSCIIRegex = regexp.MustCompile(aSCIIRegexString) + printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString) + multibyteRegex = regexp.MustCompile(multibyteRegexString) + dataURIRegex = regexp.MustCompile(dataURIRegexString) + latitudeRegex = regexp.MustCompile(latitudeRegexString) + longitudeRegex = regexp.MustCompile(longitudeRegexString) + sSNRegex = regexp.MustCompile(sSNRegexString) + hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952) + hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123) + btcAddressRegex = regexp.MustCompile(btcAddressRegexString) + btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32) + btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32) + ethAddressRegex = regexp.MustCompile(ethAddressRegexString) + ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) + ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) ) diff --git a/validator_test.go b/validator_test.go index a9ef1d4..0c121b7 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4488,6 +4488,7 @@ func TestBitcoinBech32AddressValidation(t *testing.T){ {"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", false}, {"bc1rw5uspcuh", false}, {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"BC1QW508d6QEJxTDG4y5R3ZArVARY0C5XW7KV8F3T4", false}, {"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", false}, {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, {"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", false}, From 073682ea75151552c065098c4b587321a16bc5d3 Mon Sep 17 00:00:00 2001 From: josh Date: Mon, 9 Apr 2018 01:49:06 -0700 Subject: [PATCH 11/27] added in more test cases and removed some redundant validation steps --- baked_in.go | 19 ++-------- validator_test.go | 92 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/baked_in.go b/baked_in.go index 057ba0f..0688e49 100644 --- a/baked_in.go +++ b/baked_in.go @@ -431,11 +431,6 @@ func isBitcoinAddress(fl FieldLevel) bool { } } - - if !(decode[0] == 0 || decode[0] == 5) { - return false - } - h := sha256.New() h.Write(decode[:21]) d := h.Sum([]byte{}) @@ -451,7 +446,7 @@ func isBitcoinAddress(fl FieldLevel) bool { return validchecksum == computedchecksum } -// IsBitcoinAddress is the validation function for validating if the field's value is a valid bech32 btc address +// IsBitcoinBech32Address is the validation function for validating if the field's value is a valid bech32 btc address func isBitcoinBech32Address(fl FieldLevel) bool { address := fl.Field().String() @@ -512,15 +507,9 @@ func isBitcoinBech32Address(fl FieldLevel) bool { b := uint(0) acc := 0 mv := (1 << 5) - 1 - sw := []int{} - dp = dp[:len(dp) - 6] - - for _, v := range dp[1:]{ - if v < 0 || (v >> 5) != 0{ - return false - } + for _, v := range dp[1:len(dp) - 6]{ acc = (acc << 5) | v b += 5 for b >= 8{ @@ -533,10 +522,6 @@ func isBitcoinBech32Address(fl FieldLevel) bool { return false } - if ver == 0 && len(sw) != 20 && len(sw) != 32 { - return false - } - return true } diff --git a/validator_test.go b/validator_test.go index 0c121b7..2d345bb 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4445,13 +4445,85 @@ func TestBitcoinAddressValidation(t *testing.T){ expected bool }{ {"", false}, + {"x", false}, {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, - {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, // invalid p2pkh address with invalid characters - {"1P9RQEr2XeE3PEb44ZE35sfZRRW1JH8Uqx", false}, // invald p2pkh address with valid characters - {"3P14159I73E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with invalid characters - {"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with valid characters - {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, // valid p2pkh address - {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, // valid p2sh address + {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, + {"1P9RQEr2XeE3PEb44ZE35sfZRRW1JH8Uqx", false}, + {"3P14159I73E4gFr7JterCCQh9QjiTjiZrG", false}, + {"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false}, + {"37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y", false}, + {"dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv", false}, + {"MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S", false}, + {"rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf", false}, + {"4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq", false}, + {"7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb", false}, + {"17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs", false}, + {"KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3", false}, + {"7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th", false}, + {"cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va", false}, + {"gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk", false}, + {"emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs", false}, + {"7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo", false}, + {"1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso", false}, + {"31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq", false}, + {"DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN", false}, + {"2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i", false}, + {"7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos", false}, + {"1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu", false}, + {"2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb", false}, + {"8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ", false}, + {"163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ", false}, + {"2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu", false}, + {"461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU", false}, + {"2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs", false}, + {"cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn", false}, + {"gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj", false}, + {"nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny", false}, + {"L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc", false}, + {"7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ ",false}, + {"2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP", false}, + {"dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw", false}, + {"HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX", false}, + {"4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB", false}, + {"31uEbMgunupShBVTewXjtqbBv5MndwfXhb", false}, + {"175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W", false}, + {"Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ", false}, + {"Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs", false}, + {"6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ", false}, + {"giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4", false}, + {"cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK", false}, + {"37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig", false}, + {"EsYbG4tWWWY45G31nox838qNdzksbPySWc", false}, + {"nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT", false}, + {"cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx", false}, + {"1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde", false}, + {"2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU", false}, + {"ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf", false}, + {"Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd", false}, + {"2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED", false}, + {"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", true}, + {"1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", true}, + {"1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", true}, + {"1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", true}, + {"1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", true}, + {"19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", true}, + {"13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", true}, + {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, + {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, + {"3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", true}, + {"3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", true}, + {"3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", true}, + {"33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", true}, + {"3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", true}, + {"37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", true}, + {"3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", true}, + {"12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP", true}, + {"12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y", true}, + {"1oNLrsHnBcR6dpaBpwz3LSwutbUNkNSjs", true}, + {"1SQHtwR5oJRKLfiWQ2APsAd9miUc4k2ez", true}, + {"116CGDLddrZhMrTwhCVJXtXQpxygTT1kHd", true}, + {"3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt", true}, + } for i, test := range tests { @@ -4484,17 +4556,23 @@ func TestBitcoinBech32AddressValidation(t *testing.T){ expected bool }{ {"", false}, - {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, // invalid bech32 address with invalid startingcharacters + {"bc1rw5uspcuh", false}, + {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, {"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", false}, + {"qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", false}, {"bc1rw5uspcuh", false}, {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, {"BC1QW508d6QEJxTDG4y5R3ZArVARY0C5XW7KV8F3T4", false}, {"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", false}, + {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"bc1pw508d6qejxtdg4y5r3zarqfsj6c3", false}, {"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", false}, {"bc1gmk9yu", false}, + {"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", false}, {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", true}, {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", true}, + {"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", true}, {"BC1SW50QA3JX3S", true}, {"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", true}, } From 9f62099643d87d0c6c232be81cd22731bfc5e6e8 Mon Sep 17 00:00:00 2001 From: Ashton Kinslow Date: Tue, 1 May 2018 16:19:59 -0500 Subject: [PATCH 12/27] add missing Engine() to default validator to meet new interface requirements in gin --- _examples/gin-upgrading-overriding/v8_to_v9.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/_examples/gin-upgrading-overriding/v8_to_v9.go b/_examples/gin-upgrading-overriding/v8_to_v9.go index 272b670..014e3f5 100644 --- a/_examples/gin-upgrading-overriding/v8_to_v9.go +++ b/_examples/gin-upgrading-overriding/v8_to_v9.go @@ -29,6 +29,11 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error { return nil } +func (v *defaultValidator) Engine() interface{} { + v.lazyinit() + return v.validate +} + func (v *defaultValidator) lazyinit() { v.once.Do(func() { v.validate = validator.New() From 8ae3903dd38edd82b65674bdb19d5e76805a294c Mon Sep 17 00:00:00 2001 From: James Service Date: Fri, 4 May 2018 13:20:05 +0100 Subject: [PATCH 13/27] Extend the unique tag to also cover map values. --- baked_in.go | 33 ++++++++++++++++++++------------- validator_test.go | 23 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/baked_in.go b/baked_in.go index 3c83b73..755bf19 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1,7 +1,9 @@ package validator import ( + "bytes" "context" + "crypto/sha256" "fmt" "net" "net/url" @@ -11,8 +13,6 @@ import ( "sync" "time" "unicode/utf8" - "crypto/sha256" - "bytes" ) // Func accepts a FieldLevel interface for all validation needs. The return @@ -187,7 +187,7 @@ func isOneOf(fl FieldLevel) bool { return false } -// isUnique is the validation function for validating if each array|slice element is unique +// isUnique is the validation function for validating if each array|slice|map value is unique func isUnique(fl FieldLevel) bool { field := fl.Field() @@ -195,12 +195,19 @@ func isUnique(fl FieldLevel) bool { switch field.Kind() { case reflect.Slice, reflect.Array: - m := reflect.MakeMap(reflect.MapOf(fl.Field().Type().Elem(), v.Type())) + m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type())) for i := 0; i < field.Len(); i++ { m.SetMapIndex(field.Index(i), v) } return field.Len() == m.Len() + case reflect.Map: + m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type())) + + for _, k := range field.MapKeys() { + m.SetMapIndex(field.MapIndex(k), v) + } + return field.Len() == m.Len() default: panic(fmt.Sprintf("Bad field type %T", field.Interface())) } @@ -451,13 +458,13 @@ func isBitcoinAddress(fl FieldLevel) bool { func isBitcoinBech32Address(fl FieldLevel) bool { address := fl.Field().String() - if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address){ + if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address) { return false } am := len(address) % 8 - if am == 0 || am == 3 || am == 5{ + if am == 0 || am == 3 || am == 5 { return false } @@ -474,7 +481,7 @@ func isBitcoinBech32Address(fl FieldLevel) bool { ver := dp[0] - if ver < 0 || ver > 16{ + if ver < 0 || ver > 16 { return false } @@ -486,16 +493,16 @@ func isBitcoinBech32Address(fl FieldLevel) bool { values := append(hr, dp...) - GEN := []int{ 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 } + GEN := []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} p := 1 for _, v := range values { b := p >> 25 - p = (p & 0x1ffffff) << 5 ^ v + p = (p&0x1ffffff)<<5 ^ v for i := 0; i < 5; i++ { - if (b >> uint(i)) & 1 == 1 { + if (b>>uint(i))&1 == 1 { p ^= GEN[i] } } @@ -510,16 +517,16 @@ func isBitcoinBech32Address(fl FieldLevel) bool { mv := (1 << 5) - 1 sw := []int{} - for _, v := range dp[1:len(dp) - 6]{ + for _, v := range dp[1 : len(dp)-6] { acc = (acc << 5) | v b += 5 - for b >= 8{ + for b >= 8 { b -= 8 sw = append(sw, (acc>>b)&mv) } } - if len(sw) < 2 || len(sw) > 40{ + if len(sw) < 2 || len(sw) > 40 { return false } diff --git a/validator_test.go b/validator_test.go index 2be460c..fd80b8d 100644 --- a/validator_test.go +++ b/validator_test.go @@ -7696,6 +7696,18 @@ func TestUniqueValidation(t *testing.T) { param interface{} expected bool }{ + // Arrays + {[2]string{"a", "b"}, true}, + {[2]int{1, 2}, true}, + {[2]float64{1, 2}, true}, + {[2]interface{}{"a", "b"}, true}, + {[2]interface{}{"a", 1}, true}, + {[2]float64{1, 1}, false}, + {[2]int{1, 1}, false}, + {[2]string{"a", "a"}, false}, + {[2]interface{}{"a", "a"}, false}, + {[4]interface{}{"a", 1, "b", 1}, false}, + // Slices {[]string{"a", "b"}, true}, {[]int{1, 2}, true}, {[]float64{1, 2}, true}, @@ -7706,6 +7718,17 @@ func TestUniqueValidation(t *testing.T) { {[]string{"a", "a"}, false}, {[]interface{}{"a", "a"}, false}, {[]interface{}{"a", 1, "b", 1}, false}, + // Maps + {map[string]string{"one": "a", "two": "b"}, true}, + {map[string]int{"one": 1, "two": 2}, true}, + {map[string]float64{"one": 1, "two": 2}, true}, + {map[string]interface{}{"one": "a", "two": "b"}, true}, + {map[string]interface{}{"one": "a", "two": 1}, true}, + {map[string]float64{"one": 1, "two": 1}, false}, + {map[string]int{"one": 1, "two": 1}, false}, + {map[string]string{"one": "a", "two": "a"}, false}, + {map[string]interface{}{"one": "a", "two": "a"}, false}, + {map[string]interface{}{"one": "a", "two": 1, "three": "b", "four": 1}, false}, } validate := New() From fc5fbce7063ef79ce400c2028d1e478350101f28 Mon Sep 17 00:00:00 2001 From: James Service Date: Fri, 4 May 2018 13:23:26 +0100 Subject: [PATCH 14/27] Add new unique functionality to docstring. --- doc.go | 1 + 1 file changed, 1 insertion(+) diff --git a/doc.go b/doc.go index e8ef618..602fdc8 100644 --- a/doc.go +++ b/doc.go @@ -506,6 +506,7 @@ to the top level struct. Unique For arrays & slices, unique will ensure that there are no duplicates. +For maps, unique will ensure that there are no duplicate values. Usage: unique From d978a4b4218b1728b8f726ca948ac2d6da3ee467 Mon Sep 17 00:00:00 2001 From: James Service Date: Fri, 4 May 2018 13:34:51 +0100 Subject: [PATCH 15/27] Undo whitespace changes from gofmt. --- baked_in.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/baked_in.go b/baked_in.go index 755bf19..d3730ca 100644 --- a/baked_in.go +++ b/baked_in.go @@ -458,13 +458,13 @@ func isBitcoinAddress(fl FieldLevel) bool { func isBitcoinBech32Address(fl FieldLevel) bool { address := fl.Field().String() - if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address) { + if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address){ return false } am := len(address) % 8 - if am == 0 || am == 3 || am == 5 { + if am == 0 || am == 3 || am == 5{ return false } @@ -481,7 +481,7 @@ func isBitcoinBech32Address(fl FieldLevel) bool { ver := dp[0] - if ver < 0 || ver > 16 { + if ver < 0 || ver > 16{ return false } @@ -493,16 +493,16 @@ func isBitcoinBech32Address(fl FieldLevel) bool { values := append(hr, dp...) - GEN := []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} + GEN := []int{ 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 } p := 1 for _, v := range values { b := p >> 25 - p = (p&0x1ffffff)<<5 ^ v + p = (p & 0x1ffffff) << 5 ^ v for i := 0; i < 5; i++ { - if (b>>uint(i))&1 == 1 { + if (b >> uint(i)) & 1 == 1 { p ^= GEN[i] } } @@ -517,16 +517,16 @@ func isBitcoinBech32Address(fl FieldLevel) bool { mv := (1 << 5) - 1 sw := []int{} - for _, v := range dp[1 : len(dp)-6] { + for _, v := range dp[1:len(dp) - 6]{ acc = (acc << 5) | v b += 5 - for b >= 8 { + for b >= 8{ b -= 8 sw = append(sw, (acc>>b)&mv) } } - if len(sw) < 2 || len(sw) > 40 { + if len(sw) < 2 || len(sw) > 40{ return false } From 54da7fa48f65e4950621cf75e97ed7fbba4d7601 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sat, 5 May 2018 09:23:38 -0700 Subject: [PATCH 16/27] fix linting and spelling errors --- Makefile | 16 +++++ README.md | 124 ++++++++++++++++++------------------ baked_in.go | 29 +++++---- cache.go | 2 +- doc.go | 38 +++++------ field_level.go | 2 +- translations.go | 2 +- translations/en/en.go | 12 ++-- translations/fr/fr.go | 12 ++-- translations/pt_BR/pt_BR.go | 12 ++-- validator.go | 4 +- validator_instance.go | 10 +-- validator_test.go | 18 +++--- 13 files changed, 156 insertions(+), 125 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aeeee9d --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +GOCMD=go + +linters-install: + $(GOCMD) get -u github.com/alecthomas/gometalinter + gometalinter --install + +lint: linters-install + gometalinter --vendor --disable-all --enable=vet --enable=vetshadow --enable=golint --enable=maligned --enable=megacheck --enable=ineffassign --enable=misspell --enable=errcheck --enable=goconst ./... + +test: + $(GOCMD) test -cover -race ./... + +bench: + $(GOCMD) test -bench=. -benchmem ./... + +.PHONY: test lint linters-install \ No newline at end of file diff --git a/README.md b/README.md index 7bc80b4..6ef0cc6 100644 --- a/README.md +++ b/README.md @@ -66,71 +66,73 @@ Please see http://godoc.org/gopkg.in/go-playground/validator.v9 for detailed usa Benchmarks ------ -###### Run on MacBook Pro (15-inch, 2017) Go version go1.9.4 darwin/amd64 +###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64 ```go goos: darwin goarch: amd64 pkg: github.com/go-playground/validator -BenchmarkFieldSuccess-8 20000000 86.4 ns/op 0 B/op 0 allocs/op -BenchmarkFieldSuccessParallel-8 50000000 27.6 ns/op 0 B/op 0 allocs/op -BenchmarkFieldFailure-8 5000000 297 ns/op 208 B/op 4 allocs/op -BenchmarkFieldFailureParallel-8 20000000 107 ns/op 208 B/op 4 allocs/op -BenchmarkFieldArrayDiveSuccess-8 2000000 618 ns/op 201 B/op 11 allocs/op -BenchmarkFieldArrayDiveSuccessParallel-8 10000000 225 ns/op 201 B/op 11 allocs/op -BenchmarkFieldArrayDiveFailure-8 2000000 863 ns/op 412 B/op 16 allocs/op -BenchmarkFieldArrayDiveFailureParallel-8 5000000 322 ns/op 413 B/op 16 allocs/op -BenchmarkFieldMapDiveSuccess-8 1000000 1336 ns/op 432 B/op 18 allocs/op -BenchmarkFieldMapDiveSuccessParallel-8 3000000 474 ns/op 432 B/op 18 allocs/op -BenchmarkFieldMapDiveFailure-8 1000000 1103 ns/op 512 B/op 16 allocs/op -BenchmarkFieldMapDiveFailureParallel-8 5000000 412 ns/op 512 B/op 16 allocs/op -BenchmarkFieldMapDiveWithKeysSuccess-8 1000000 1572 ns/op 480 B/op 21 allocs/op -BenchmarkFieldMapDiveWithKeysSuccessParallel-8 3000000 615 ns/op 480 B/op 21 allocs/op -BenchmarkFieldMapDiveWithKeysFailure-8 1000000 1438 ns/op 721 B/op 21 allocs/op -BenchmarkFieldMapDiveWithKeysFailureParallel-8 3000000 543 ns/op 721 B/op 21 allocs/op -BenchmarkFieldCustomTypeSuccess-8 10000000 230 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.5 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-8 5000000 284 ns/op 208 B/op 4 allocs/op -BenchmarkFieldCustomTypeFailureParallel-8 20000000 118 ns/op 208 B/op 4 allocs/op -BenchmarkFieldOrTagSuccess-8 2000000 824 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagSuccessParallel-8 3000000 472 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagFailure-8 3000000 487 ns/op 224 B/op 5 allocs/op -BenchmarkFieldOrTagFailureParallel-8 5000000 405 ns/op 224 B/op 5 allocs/op -BenchmarkStructLevelValidationSuccess-8 10000000 214 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationSuccessParallel-8 20000000 78.0 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationFailure-8 3000000 475 ns/op 304 B/op 8 allocs/op -BenchmarkStructLevelValidationFailureParallel-8 10000000 200 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-8 3000000 403 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 143 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeFailure-8 2000000 655 ns/op 424 B/op 9 allocs/op -BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 286 ns/op 440 B/op 10 allocs/op -BenchmarkStructFilteredSuccess-8 2000000 598 ns/op 288 B/op 9 allocs/op -BenchmarkStructFilteredSuccessParallel-8 10000000 231 ns/op 288 B/op 9 allocs/op -BenchmarkStructFilteredFailure-8 3000000 455 ns/op 256 B/op 7 allocs/op -BenchmarkStructFilteredFailureParallel-8 10000000 197 ns/op 256 B/op 7 allocs/op -BenchmarkStructPartialSuccess-8 3000000 552 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialSuccessParallel-8 10000000 206 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialFailure-8 2000000 750 ns/op 480 B/op 11 allocs/op -BenchmarkStructPartialFailureParallel-8 5000000 317 ns/op 480 B/op 11 allocs/op -BenchmarkStructExceptSuccess-8 2000000 853 ns/op 496 B/op 12 allocs/op -BenchmarkStructExceptSuccessParallel-8 10000000 179 ns/op 240 B/op 5 allocs/op -BenchmarkStructExceptFailure-8 2000000 698 ns/op 464 B/op 10 allocs/op -BenchmarkStructExceptFailureParallel-8 5000000 276 ns/op 464 B/op 10 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-8 3000000 412 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 148 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldFailure-8 2000000 630 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCrossFieldFailureParallel-8 10000000 244 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 610 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 205 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 861 ns/op 320 B/op 9 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 315 ns/op 320 B/op 9 allocs/op -BenchmarkStructSimpleSuccess-8 5000000 279 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleSuccessParallel-8 20000000 86.4 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleFailure-8 2000000 636 ns/op 424 B/op 9 allocs/op -BenchmarkStructSimpleFailureParallel-8 10000000 264 ns/op 424 B/op 9 allocs/op -BenchmarkStructComplexSuccess-8 1000000 1539 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexSuccessParallel-8 3000000 557 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexFailure-8 300000 4136 ns/op 3041 B/op 53 allocs/op -BenchmarkStructComplexFailureParallel-8 1000000 1855 ns/op 3041 B/op 53 allocs/op +BenchmarkFieldSuccess-8 20000000 83.6 ns/op 0 B/op 0 allocs/op +BenchmarkFieldSuccessParallel-8 50000000 26.8 ns/op 0 B/op 0 allocs/op +BenchmarkFieldFailure-8 5000000 291 ns/op 208 B/op 4 allocs/op +BenchmarkFieldFailureParallel-8 20000000 107 ns/op 208 B/op 4 allocs/op +BenchmarkFieldArrayDiveSuccess-8 2000000 623 ns/op 201 B/op 11 allocs/op +BenchmarkFieldArrayDiveSuccessParallel-8 10000000 237 ns/op 201 B/op 11 allocs/op +BenchmarkFieldArrayDiveFailure-8 2000000 859 ns/op 412 B/op 16 allocs/op +BenchmarkFieldArrayDiveFailureParallel-8 5000000 335 ns/op 413 B/op 16 allocs/op +BenchmarkFieldMapDiveSuccess-8 1000000 1292 ns/op 432 B/op 18 allocs/op +BenchmarkFieldMapDiveSuccessParallel-8 3000000 467 ns/op 432 B/op 18 allocs/op +BenchmarkFieldMapDiveFailure-8 1000000 1082 ns/op 512 B/op 16 allocs/op +BenchmarkFieldMapDiveFailureParallel-8 5000000 425 ns/op 512 B/op 16 allocs/op +BenchmarkFieldMapDiveWithKeysSuccess-8 1000000 1539 ns/op 480 B/op 21 allocs/op +BenchmarkFieldMapDiveWithKeysSuccessParallel-8 3000000 613 ns/op 480 B/op 21 allocs/op +BenchmarkFieldMapDiveWithKeysFailure-8 1000000 1413 ns/op 721 B/op 21 allocs/op +BenchmarkFieldMapDiveWithKeysFailureParallel-8 3000000 575 ns/op 721 B/op 21 allocs/op +BenchmarkFieldCustomTypeSuccess-8 10000000 216 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.2 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-8 5000000 274 ns/op 208 B/op 4 allocs/op +BenchmarkFieldCustomTypeFailureParallel-8 20000000 116 ns/op 208 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-8 2000000 740 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagSuccessParallel-8 3000000 474 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagFailure-8 3000000 471 ns/op 224 B/op 5 allocs/op +BenchmarkFieldOrTagFailureParallel-8 3000000 414 ns/op 224 B/op 5 allocs/op +BenchmarkStructLevelValidationSuccess-8 10000000 213 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationSuccessParallel-8 20000000 91.8 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationFailure-8 3000000 473 ns/op 304 B/op 8 allocs/op +BenchmarkStructLevelValidationFailureParallel-8 10000000 234 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-8 5000000 385 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 161 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeFailure-8 2000000 640 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 318 ns/op 440 B/op 10 allocs/op +BenchmarkStructFilteredSuccess-8 2000000 597 ns/op 288 B/op 9 allocs/op +BenchmarkStructFilteredSuccessParallel-8 10000000 266 ns/op 288 B/op 9 allocs/op +BenchmarkStructFilteredFailure-8 3000000 454 ns/op 256 B/op 7 allocs/op +BenchmarkStructFilteredFailureParallel-8 10000000 214 ns/op 256 B/op 7 allocs/op +BenchmarkStructPartialSuccess-8 3000000 502 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialSuccessParallel-8 10000000 225 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialFailure-8 2000000 702 ns/op 480 B/op 11 allocs/op +BenchmarkStructPartialFailureParallel-8 5000000 329 ns/op 480 B/op 11 allocs/op +BenchmarkStructExceptSuccess-8 2000000 793 ns/op 496 B/op 12 allocs/op +BenchmarkStructExceptSuccessParallel-8 10000000 193 ns/op 240 B/op 5 allocs/op +BenchmarkStructExceptFailure-8 2000000 639 ns/op 464 B/op 10 allocs/op +BenchmarkStructExceptFailureParallel-8 5000000 300 ns/op 464 B/op 10 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-8 3000000 417 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 163 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldFailure-8 2000000 645 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 285 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 3000000 588 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 221 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 868 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 337 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleSuccess-8 5000000 260 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleSuccessParallel-8 20000000 90.6 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleFailure-8 2000000 619 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleFailureParallel-8 5000000 296 ns/op 424 B/op 9 allocs/op +BenchmarkStructComplexSuccess-8 1000000 1454 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexSuccessParallel-8 3000000 579 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexFailure-8 300000 4140 ns/op 3041 B/op 53 allocs/op +BenchmarkStructComplexFailureParallel-8 1000000 2127 ns/op 3041 B/op 53 allocs/op +BenchmarkOneof-8 10000000 140 ns/op 0 B/op 0 allocs/op +BenchmarkOneofParallel-8 20000000 70.1 ns/op 0 B/op 0 allocs/op ``` Complementary Software diff --git a/baked_in.go b/baked_in.go index d3730ca..64b71cb 100644 --- a/baked_in.go +++ b/baked_in.go @@ -440,10 +440,10 @@ func isBitcoinAddress(fl FieldLevel) bool { } h := sha256.New() - h.Write(decode[:21]) + _, _ = h.Write(decode[:21]) d := h.Sum([]byte{}) h = sha256.New() - h.Write(d) + _, _ = h.Write(d) validchecksum := [4]byte{} computedchecksum := [4]byte{} @@ -458,13 +458,13 @@ func isBitcoinAddress(fl FieldLevel) bool { func isBitcoinBech32Address(fl FieldLevel) bool { address := fl.Field().String() - if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address){ + if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address) { return false } am := len(address) % 8 - if am == 0 || am == 3 || am == 5{ + if am == 0 || am == 3 || am == 5 { return false } @@ -473,15 +473,16 @@ func isBitcoinBech32Address(fl FieldLevel) bool { alphabet := "qpzry9x8gf2tvdw0s3jn54khce6mua7l" hr := []int{3, 3, 0, 2, 3} // the human readable part will always be bc - dp := []int{} + addr := address[3:] + dp := make([]int, 0, len(addr)) - for _, c := range []rune(address[3:]) { + for _, c := range addr { dp = append(dp, strings.IndexRune(alphabet, c)) } ver := dp[0] - if ver < 0 || ver > 16{ + if ver < 0 || ver > 16 { return false } @@ -493,16 +494,16 @@ func isBitcoinBech32Address(fl FieldLevel) bool { values := append(hr, dp...) - GEN := []int{ 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 } + GEN := []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} p := 1 for _, v := range values { b := p >> 25 - p = (p & 0x1ffffff) << 5 ^ v + p = (p&0x1ffffff)<<5 ^ v for i := 0; i < 5; i++ { - if (b >> uint(i)) & 1 == 1 { + if (b>>uint(i))&1 == 1 { p ^= GEN[i] } } @@ -515,18 +516,18 @@ func isBitcoinBech32Address(fl FieldLevel) bool { b := uint(0) acc := 0 mv := (1 << 5) - 1 - sw := []int{} + var sw []int - for _, v := range dp[1:len(dp) - 6]{ + for _, v := range dp[1 : len(dp)-6] { acc = (acc << 5) | v b += 5 - for b >= 8{ + for b >= 8 { b -= 8 sw = append(sw, (acc>>b)&mv) } } - if len(sw) < 2 || len(sw) > 40{ + if len(sw) < 2 || len(sw) > 40 { return false } diff --git a/cache.go b/cache.go index c7fb0fb..a7a4202 100644 --- a/cache.go +++ b/cache.go @@ -223,7 +223,7 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s current.typeof = typeKeys if i == 0 || prevTag != typeDive { - panic(fmt.Sprintf("'%s' tag must be immediately preceeded by the '%s' tag", keysTag, diveTag)) + panic(fmt.Sprintf("'%s' tag must be immediately preceded by the '%s' tag", keysTag, diveTag)) } current.typeof = typeKeys diff --git a/doc.go b/doc.go index 602fdc8..6eef72c 100644 --- a/doc.go +++ b/doc.go @@ -168,7 +168,7 @@ StructOnly When a field that is a nested struct is encountered, and contains this flag any validation on the nested struct will be run, but none of the nested -struct fields will be validated. This is usefull if inside of you program +struct fields will be validated. This is useful if inside of you program you know the struct will be valid, but need to verify it has been assigned. NOTE: only "required" and "omitempty" can be used on a struct itself. @@ -772,103 +772,103 @@ This validates that a string value contains a valid U.S. Social Security Number. Internet Protocol Address IP -This validates that a string value contains a valid IP Adress. +This validates that a string value contains a valid IP Address. Usage: ip Internet Protocol Address IPv4 -This validates that a string value contains a valid v4 IP Adress. +This validates that a string value contains a valid v4 IP Address. Usage: ipv4 Internet Protocol Address IPv6 -This validates that a string value contains a valid v6 IP Adress. +This validates that a string value contains a valid v6 IP Address. Usage: ipv6 Classless Inter-Domain Routing CIDR -This validates that a string value contains a valid CIDR Adress. +This validates that a string value contains a valid CIDR Address. Usage: cidr Classless Inter-Domain Routing CIDRv4 -This validates that a string value contains a valid v4 CIDR Adress. +This validates that a string value contains a valid v4 CIDR Address. Usage: cidrv4 Classless Inter-Domain Routing CIDRv6 -This validates that a string value contains a valid v6 CIDR Adress. +This validates that a string value contains a valid v6 CIDR Address. Usage: cidrv6 Transmission Control Protocol Address TCP -This validates that a string value contains a valid resolvable TCP Adress. +This validates that a string value contains a valid resolvable TCP Address. Usage: tcp_addr Transmission Control Protocol Address TCPv4 -This validates that a string value contains a valid resolvable v4 TCP Adress. +This validates that a string value contains a valid resolvable v4 TCP Address. Usage: tcp4_addr Transmission Control Protocol Address TCPv6 -This validates that a string value contains a valid resolvable v6 TCP Adress. +This validates that a string value contains a valid resolvable v6 TCP Address. Usage: tcp6_addr User Datagram Protocol Address UDP -This validates that a string value contains a valid resolvable UDP Adress. +This validates that a string value contains a valid resolvable UDP Address. Usage: udp_addr User Datagram Protocol Address UDPv4 -This validates that a string value contains a valid resolvable v4 UDP Adress. +This validates that a string value contains a valid resolvable v4 UDP Address. Usage: udp4_addr User Datagram Protocol Address UDPv6 -This validates that a string value contains a valid resolvable v6 UDP Adress. +This validates that a string value contains a valid resolvable v6 UDP Address. Usage: udp6_addr Internet Protocol Address IP -This validates that a string value contains a valid resolvable IP Adress. +This validates that a string value contains a valid resolvable IP Address. Usage: ip_addr Internet Protocol Address IPv4 -This validates that a string value contains a valid resolvable v4 IP Adress. +This validates that a string value contains a valid resolvable v4 IP Address. Usage: ip4_addr Internet Protocol Address IPv6 -This validates that a string value contains a valid resolvable v6 IP Adress. +This validates that a string value contains a valid resolvable v6 IP Address. Usage: ip6_addr Unix domain socket end point Address -This validates that a string value contains a valid Unix Adress. +This validates that a string value contains a valid Unix Address. Usage: unix_addr Media Access Control Address MAC -This validates that a string value contains a valid MAC Adress. +This validates that a string value contains a valid MAC Address. Usage: mac @@ -913,7 +913,7 @@ Validator notes: of a regex which conflict with the validation definitions. Although workarounds can be made, they take away from using pure regex's. Furthermore it's quick and dirty but the regex's become harder to - maintain and are not reusable, so it's as much a programming philosiphy + maintain and are not reusable, so it's as much a programming philosophy as anything. In place of this new validator functions should be created; a regex can diff --git a/field_level.go b/field_level.go index 6d73192..cbfbc15 100644 --- a/field_level.go +++ b/field_level.go @@ -17,7 +17,7 @@ type FieldLevel interface { Field() reflect.Value // returns the field's name with the tag - // name takeing precedence over the fields actual name. + // name taking precedence over the fields actual name. FieldName() string // returns the struct field's name diff --git a/translations.go b/translations.go index 4465abb..4d9d75c 100644 --- a/translations.go +++ b/translations.go @@ -7,5 +7,5 @@ import ut "github.com/go-playground/universal-translator" type TranslationFunc func(ut ut.Translator, fe FieldError) string // RegisterTranslationsFunc allows for registering of translations -// for a 'ut.Translator' for use withing the 'TranslationFunc' +// for a 'ut.Translator' for use within the 'TranslationFunc' type RegisterTranslationsFunc func(ut ut.Translator) error diff --git a/translations/en/en.go b/translations/en/en.go index 3ac1c47..f38fa1a 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -429,7 +429,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END } t, err = ut.T("lt-datetime", fe.Field()) @@ -548,7 +549,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END } t, err = ut.T("lte-datetime", fe.Field()) @@ -667,7 +669,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END } t, err = ut.T("gt-datetime", fe.Field()) @@ -786,7 +789,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END } t, err = ut.T("gte-datetime", fe.Field()) diff --git a/translations/fr/fr.go b/translations/fr/fr.go index 871cf5f..1bf5dae 100644 --- a/translations/fr/fr.go +++ b/translations/fr/fr.go @@ -429,7 +429,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END } t, err = ut.T("lt-datetime", fe.Field()) @@ -548,7 +549,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END } t, err = ut.T("lte-datetime", fe.Field()) @@ -667,7 +669,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END } t, err = ut.T("gt-datetime", fe.Field()) @@ -786,7 +789,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END } t, err = ut.T("gte-datetime", fe.Field()) diff --git a/translations/pt_BR/pt_BR.go b/translations/pt_BR/pt_BR.go index 13a00bd..dcb223d 100644 --- a/translations/pt_BR/pt_BR.go +++ b/translations/pt_BR/pt_BR.go @@ -429,7 +429,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("a tag '%s' não pode ser usada em uma struct type.", fe.Tag()) + err = fmt.Errorf("a tag '%s' não pode ser usada em uma struct type", fe.Tag()) + goto END } t, err = ut.T("lt-datetime", fe.Field()) @@ -548,7 +549,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type.", fe.Tag()) + err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type", fe.Tag()) + goto END } t, err = ut.T("lte-datetime", fe.Field()) @@ -667,7 +669,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type.", fe.Tag()) + err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type", fe.Tag()) + goto END } t, err = ut.T("gt-datetime", fe.Field()) @@ -786,7 +789,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er case reflect.Struct: if fe.Type() != reflect.TypeOf(time.Time{}) { - err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type.", fe.Tag()) + err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type", fe.Tag()) + goto END } t, err = ut.T("gte-datetime", fe.Field()) diff --git a/validator.go b/validator.go index 483e0a2..67473f1 100644 --- a/validator.go +++ b/validator.go @@ -213,8 +213,8 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr CONTINUE: // if len == 0 then validating using 'Var' or 'VarWithValue' // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm... - // VarWithField - this allows for validating against each field withing the struct against a specific value - // pretty handly in certain situations + // VarWithField - this allows for validating against each field within the struct against a specific value + // pretty handy in certain situations if len(cf.name) > 0 { ns = append(append(ns, cf.altName...), '.') structNs = append(append(structNs, cf.name...), '.') diff --git a/validator_instance.go b/validator_instance.go index e84b452..a07e4e2 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -97,7 +97,7 @@ func New() *Validate { for k, val := range bakedInValidators { // no need to error check here, baked in will always be valid - v.registerValidation(k, wrapFunc(val), true) + _ = v.registerValidation(k, wrapFunc(val), true) } v.pool = &sync.Pool{ @@ -489,7 +489,7 @@ func (v *Validate) StructExceptCtx(ctx context.Context, s interface{}, fields .. // // WARNING: a struct can be passed for validation eg. time.Time is a struct or // if you have a custom type and have registered a custom type handler, so must -// allow it; however unforseen validations will occur if trying to validate a +// allow it; however unforeseen validations will occur if trying to validate a // struct that is meant to be passed to 'validate.Struct' // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. @@ -507,7 +507,7 @@ func (v *Validate) Var(field interface{}, tag string) error { // // WARNING: a struct can be passed for validation eg. time.Time is a struct or // if you have a custom type and have registered a custom type handler, so must -// allow it; however unforseen validations will occur if trying to validate a +// allow it; however unforeseen validations will occur if trying to validate a // struct that is meant to be passed to 'validate.Struct' // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. @@ -541,7 +541,7 @@ func (v *Validate) VarCtx(ctx context.Context, field interface{}, tag string) (e // // WARNING: a struct can be passed for validation eg. time.Time is a struct or // if you have a custom type and have registered a custom type handler, so must -// allow it; however unforseen validations will occur if trying to validate a +// allow it; however unforeseen validations will occur if trying to validate a // struct that is meant to be passed to 'validate.Struct' // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. @@ -560,7 +560,7 @@ func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string // // WARNING: a struct can be passed for validation eg. time.Time is a struct or // if you have a custom type and have registered a custom type handler, so must -// allow it; however unforseen validations will occur if trying to validate a +// allow it; however unforeseen validations will occur if trying to validate a // struct that is meant to be passed to 'validate.Struct' // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. diff --git a/validator_test.go b/validator_test.go index fd80b8d..ae38537 100644 --- a/validator_test.go +++ b/validator_test.go @@ -1016,7 +1016,7 @@ func TestCrossStructLteFieldValidation(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "ltecsfield") - // this test is for the WARNING about unforseen validation issues. + // this test is for the WARNING about unforeseen validation issues. errs = validate.VarWithValue(test, now, "ltecsfield") NotEqual(t, errs, nil) Equal(t, len(errs.(ValidationErrors)), 6) @@ -1113,7 +1113,7 @@ func TestCrossStructLtFieldValidation(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "ltcsfield") - // this test is for the WARNING about unforseen validation issues. + // this test is for the WARNING about unforeseen validation issues. errs = validate.VarWithValue(test, now, "ltcsfield") NotEqual(t, errs, nil) AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "ltcsfield") @@ -1221,7 +1221,7 @@ func TestCrossStructGteFieldValidation(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "gtecsfield") - // this test is for the WARNING about unforseen validation issues. + // this test is for the WARNING about unforeseen validation issues. errs = validate.VarWithValue(test, now, "gtecsfield") NotEqual(t, errs, nil) AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtecsfield") @@ -1317,7 +1317,7 @@ func TestCrossStructGtFieldValidation(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "gtcsfield") - // this test is for the WARNING about unforseen validation issues. + // this test is for the WARNING about unforeseen validation issues. errs = validate.VarWithValue(test, now, "gtcsfield") NotEqual(t, errs, nil) AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtcsfield") @@ -4432,8 +4432,8 @@ func TestBase64URLValidation(t *testing.T) { if tc.success { Equal(t, err, nil) // make sure encoded value is decoded back to the expected value - d, err := base64.URLEncoding.DecodeString(tc.encoded) - Equal(t, err, nil) + d, innerErr := base64.URLEncoding.DecodeString(tc.encoded) + Equal(t, innerErr, nil) Equal(t, tc.decoded, string(d)) } else { NotEqual(t, err, nil) @@ -6838,8 +6838,8 @@ func TestTranslations(t *testing.T) { }, func(ut ut.Translator, fe FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field()) - if err != nil { + t, transErr := ut.T(fe.Tag(), fe.Field()) + if transErr != nil { fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError)) return fe.(*fieldError).Error() } @@ -7835,7 +7835,7 @@ func TestKeys(t *testing.T) { // test bad tag definitions PanicMatches(t, func() { validate.Var(map[string]string{"key": "val"}, "endkeys,dive,eq=val") }, "'endkeys' tag encountered without a corresponding 'keys' tag") - PanicMatches(t, func() { validate.Var(1, "keys,eq=1,endkeys") }, "'keys' tag must be immediately preceeded by the 'dive' tag") + PanicMatches(t, func() { validate.Var(1, "keys,eq=1,endkeys") }, "'keys' tag must be immediately preceded by the 'dive' tag") // test custom tag name validate = New() From ee0bbdea519c2c66777c6bfc943838291734ba6c Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sat, 5 May 2018 09:24:17 -0700 Subject: [PATCH 17/27] increment version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ef0cc6..e55c77d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.15.0-green.svg) +![Project status](https://img.shields.io/badge/version-9.16.0-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) From 379fb6704796aa9ef4f823c880f1ad29a1b93022 Mon Sep 17 00:00:00 2001 From: Joachim Adomeit Date: Wed, 9 May 2018 16:50:03 +0200 Subject: [PATCH 18/27] Fix for Email - Hostname is not validated correctly #359 --- regexes.go | 2 +- validator_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/regexes.go b/regexes.go index 85e1f0a..131df4c 100644 --- a/regexes.go +++ b/regexes.go @@ -15,7 +15,7 @@ const ( rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" - emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" + emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$" iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" diff --git a/validator_test.go b/validator_test.go index ae38537..463bd83 100644 --- a/validator_test.go +++ b/validator_test.go @@ -6118,6 +6118,11 @@ func TestEmail(t *testing.T) { errs = validate.Var(s, "email") Equal(t, errs, nil) + s = "mail@domain_with_underscores.org" + errs = validate.Var(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "email") + s = "" errs = validate.Var(s, "email") NotEqual(t, errs, nil) From 801c8790f3bb6098968493546dc06d87cccb4454 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Wed, 9 May 2018 08:31:40 -0700 Subject: [PATCH 19/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e55c77d..cc494bd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.16.0-green.svg) +![Project status](https://img.shields.io/badge/version-9.16.1-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) From ffe836d7363bc7ab3f56d2fc90ce09bfb5a0c2c5 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Wed, 9 May 2018 09:00:03 -0700 Subject: [PATCH 20/27] Expanded numeric & number validations It was originally intended for these to only be used on strings, however it makes sense to also use them if your dealing with type interface{} so now ints and floats return true for this validation also as they are both numeric and numbers. closes #356 --- README.md | 2 +- baked_in.go | 14 ++++++++++++-- doc.go | 1 + validator_test.go | 6 ++---- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cc494bd..a75fed0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.16.1-green.svg) +![Project status](https://img.shields.io/badge/version-9.17.0-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) diff --git a/baked_in.go b/baked_in.go index 64b71cb..3f87da4 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1097,12 +1097,22 @@ func isHexadecimal(fl FieldLevel) bool { // IsNumber is the validation function for validating if the current field's value is a valid number. func isNumber(fl FieldLevel) bool { - return numberRegex.MatchString(fl.Field().String()) + switch fl.Field().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: + return true + default: + return numberRegex.MatchString(fl.Field().String()) + } } // IsNumeric is the validation function for validating if the current field's value is a valid numeric value. func isNumeric(fl FieldLevel) bool { - return numericRegex.MatchString(fl.Field().String()) + switch fl.Field().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: + return true + default: + return numericRegex.MatchString(fl.Field().String()) + } } // IsAlphanum is the validation function for validating if the current field's value is a valid alphanumeric value. diff --git a/doc.go b/doc.go index 6eef72c..be93d79 100644 --- a/doc.go +++ b/doc.go @@ -538,6 +538,7 @@ Numeric This validates that a string value contains a basic numeric value. basic excludes exponents etc... +for integers or float it returns true. Usage: numeric diff --git a/validator_test.go b/validator_test.go index 463bd83..0823812 100644 --- a/validator_test.go +++ b/validator_test.go @@ -6241,8 +6241,7 @@ func TestNumber(t *testing.T) { i := 1 errs = validate.Var(i, "number") - NotEqual(t, errs, nil) - AssertError(t, errs, "", "", "", "", "number") + Equal(t, errs, nil) } func TestNumeric(t *testing.T) { @@ -6285,8 +6284,7 @@ func TestNumeric(t *testing.T) { i := 1 errs = validate.Var(i, "numeric") - NotEqual(t, errs, nil) - AssertError(t, errs, "", "", "", "", "numeric") + Equal(t, errs, nil) } func TestAlphaNumeric(t *testing.T) { From 8d3aa5bde278f6085727e64321c6e06d96b76889 Mon Sep 17 00:00:00 2001 From: Tengis B Date: Thu, 10 May 2018 11:57:50 +0700 Subject: [PATCH 21/27] :pencil2: fixed typo in comment --- validator_instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator_instance.go b/validator_instance.go index a07e4e2..400efcf 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -71,7 +71,7 @@ type Validate struct { structCache *structCache } -// New returns a new instacne of 'validate' with sane defaults. +// New returns a new instance of 'validate' with sane defaults. func New() *Validate { tc := new(tagCache) From f28cb45dc0f0f406ba2015cdb59a5203ad220bc2 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Mon, 14 May 2018 07:57:39 -0700 Subject: [PATCH 22/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a75fed0..74a8156 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.17.0-green.svg) +![Project status](https://img.shields.io/badge/version-9.17.1-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) From f2fdb60d7d7d10f139c4acc73561886bb56acddb Mon Sep 17 00:00:00 2001 From: Noi Bar Date: Wed, 27 Jun 2018 12:03:42 +0300 Subject: [PATCH 23/27] Add file path validation --- baked_in.go | 19 +++++++++++++++++++ testdata/a.go | 1 + validator_test.go | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 testdata/a.go diff --git a/baked_in.go b/baked_in.go index 3f87da4..17fc6c3 100644 --- a/baked_in.go +++ b/baked_in.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "net/url" + "os" "reflect" "strconv" "strings" @@ -97,6 +98,7 @@ var ( "email": isEmail, "url": isURL, "uri": isURI, + "file": isFile, "base64": isBase64, "base64url": isBase64URL, "contains": contains, @@ -1060,6 +1062,23 @@ func isURL(fl FieldLevel) bool { panic(fmt.Sprintf("Bad field type %T", field.Interface())) } +// IsFile is the validation function for validating if the current field's value is a valid file path. +func isFile(fl FieldLevel) bool { + field := fl.Field() + + switch field.Kind() { + case reflect.String: + fileInfo, err := os.Stat(field.String()) + if err != nil { + return false + } + + return !fileInfo.IsDir() + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + // IsEmail is the validation function for validating if the current field's value is a valid email address. func isEmail(fl FieldLevel) bool { return emailRegex.MatchString(fl.Field().String()) diff --git a/testdata/a.go b/testdata/a.go new file mode 100644 index 0000000..69d29d3 --- /dev/null +++ b/testdata/a.go @@ -0,0 +1 @@ +package testdata diff --git a/validator_test.go b/validator_test.go index 0823812..36c61fa 100644 --- a/validator_test.go +++ b/validator_test.go @@ -8,6 +8,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "path/filepath" "reflect" "strings" "testing" @@ -4446,6 +4447,39 @@ func TestBase64URLValidation(t *testing.T) { } } +func TestFileValidation(t *testing.T) { + validate := New() + + tests := []struct { + title string + param string + expected bool + }{ + {"empty path", "", false}, + {"regular file", filepath.Join("testdata", "a.go"), true}, + {"missing file", filepath.Join("testdata", "no.go"), false}, + {"directory, not a file", "testdata", false}, + } + + for _, test := range tests { + errs := validate.Var(test.param, "file") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Test: '%s' failed Error: %s", test.title, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Test: '%s' failed Error: %s", test.title, errs) + } + } + } + + PanicMatches(t, func() { + validate.Var(6, "file") + }, "Bad field type int") +} + func TestEthereumAddressValidation(t *testing.T) { validate := New() From fb72538343eda99e59ac13a8e53bf0eb83ca94c8 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Thu, 28 Jun 2018 12:28:26 -0700 Subject: [PATCH 24/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74a8156..c136ebf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.17.1-green.svg) +![Project status](https://img.shields.io/badge/version-9.18.0-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) From ea9cb424fd03b97a607cb5f6b8205e4181840fda Mon Sep 17 00:00:00 2001 From: Noi Bar Date: Thu, 28 Jun 2018 23:28:43 +0300 Subject: [PATCH 25/27] doc: add file validation documentation --- doc.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc.go b/doc.go index be93d79..320253a 100644 --- a/doc.go +++ b/doc.go @@ -587,6 +587,14 @@ does any email provider accept all posibilities. Usage: email +File path + +This validates that a string value contains a valid file path and that +the file exists on the machine. +This is done using os.Stat, which is a platform independent function. + + Usage: file + URL String This validates that a string value contains a valid url From 0a9b75fbfdb5730dfff911981b428046167f33e6 Mon Sep 17 00:00:00 2001 From: heatwole Date: Thu, 28 Jun 2018 18:11:36 -0500 Subject: [PATCH 26/27] Adding html and url encoding and updating docs --- baked_in.go | 15 ++++++ doc.go | 21 ++++++++ regexes.go | 6 +++ validator_test.go | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) diff --git a/baked_in.go b/baked_in.go index 3f87da4..ad1db78 100644 --- a/baked_in.go +++ b/baked_in.go @@ -144,6 +144,9 @@ var ( "fqdn": isFQDN, "unique": isUnique, "oneof": isOneOf, + "html": isHTML, + "html_encoded": isHTMLEncoded, + "url_encoded": isURLEncoded, } ) @@ -163,6 +166,18 @@ func parseOneOfParam2(s string) []string { return vals } +func isURLEncoded(fl FieldLevel) bool { + return uRLEncodedRegex.MatchString(fl.Field().String()) +} + +func isHTMLEncoded(fl FieldLevel) bool { + return hTMLEncodedRegex.MatchString(fl.Field().String()) +} + +func isHTML(fl FieldLevel) bool { + return hTMLRegex.MatchString(fl.Field().String()) +} + func isOneOf(fl FieldLevel) bool { vals := parseOneOfParam2(fl.Param()) diff --git a/doc.go b/doc.go index be93d79..1163749 100644 --- a/doc.go +++ b/doc.go @@ -895,6 +895,27 @@ This validates that a string value contains a valid FQDN. Usage: fqdn +HTML Tags + +This validates that a string value appears to be an HTML element tag +including those described at https://developer.mozilla.org/en-US/docs/Web/HTML/Element + + Usage: html + +HTML Encoded + +This validates that a string value is a proper character reference in decimal +or hexadecimal format + + Usage: html_encoded + +URL Encoded + +This validates that a string value is percent-encoded (URL encoded) according +to https://tools.ietf.org/html/rfc3986#section-2.1 + + Usage: url_encoded + Alias Validators and Tags NOTE: When returning an error, the tag returned in "FieldError" will be diff --git a/regexes.go b/regexes.go index 131df4c..07a8705 100644 --- a/regexes.go +++ b/regexes.go @@ -39,6 +39,9 @@ const ( ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` + uRLEncodedRegexString = `(%[A-Fa-f0-9]{2})` + hTMLEncodedRegexString = `&#[x]?([0-9a-fA-F]{2})|(>)|(<)|(")|(&)+[;]?` + hTMLRegexString = `<[/]?([a-zA-Z]+).*?>` ) var ( @@ -78,4 +81,7 @@ var ( ethAddressRegex = regexp.MustCompile(ethAddressRegexString) ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) + uRLEncodedRegex = regexp.MustCompile(uRLEncodedRegexString) + hTMLEncodedRegex = regexp.MustCompile(hTMLEncodedRegexString) + hTMLRegex = regexp.MustCompile(hTMLRegexString) ) diff --git a/validator_test.go b/validator_test.go index 0823812..b488cf8 100644 --- a/validator_test.go +++ b/validator_test.go @@ -7758,6 +7758,129 @@ func TestUniqueValidation(t *testing.T) { PanicMatches(t, func() { validate.Var(1.0, "unique") }, "Bad field type float64") } +func TestHTMLValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", true}, + {"", true}, + {"", false}, + {"<123nonsense>", false}, + {"test", false}, + {"&example", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "html") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d html failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d html failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "html" { + t.Fatalf("Index: %d html failed Error: %v", i, errs) + } + } + } + } +} + +func TestHTMLEncodedValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"<", true}, + {"¯", true}, + {"�", true}, + {"ð", true}, + {"<", true}, + {"¯", true}, + {"�", true}, + {"ð", true}, + {"&#ab", true}, + {"<", true}, + {">", true}, + {""", true}, + {"&", true}, + {"#x0a", false}, + {"&x00", false}, + {"z", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "html_encoded") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d html_encoded failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d html_enocded failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "html_encoded" { + t.Fatalf("Index: %d html_encoded failed Error: %v", i, errs) + } + } + } + } +} + +func TestURLEncodedValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"%20", true}, + {"%af", true}, + {"%ff", true}, + {"<%az", false}, + {"%test%", false}, + {"a%b", false}, + {"1%2", false}, + {"%%a%%", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "url_encoded") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d url_encoded failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d url_enocded failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "url_encoded" { + t.Fatalf("Index: %d url_encoded failed Error: %v", i, errs) + } + } + } + } +} + func TestKeys(t *testing.T) { type Test struct { From ce9336f6e200cbb2e7c5672b36095b03f8bf5af7 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Fri, 29 Jun 2018 08:00:13 -0700 Subject: [PATCH 27/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c136ebf..b89466c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.18.0-green.svg) +![Project status](https://img.shields.io/badge/version-9.19.0-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)