@@ -813,9 +813,11 @@ func (u *httpsURLValidator) Do(localizer *i18n.Localizer, target any) error {
813813}
814814
815815var (
816- urlEncodedRegexp = regexp .MustCompile (`^(?:[^%]|%[0-9A-Fa-f]{2})*$` )
817- dataURIRegex = regexp .MustCompile (dataURIRegexPattern )
818- fqdnLabelRegexp = regexp .MustCompile (`^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$` )
816+ urlEncodedRegexp = regexp .MustCompile (`^(?:[^%]|%[0-9A-Fa-f]{2})*$` )
817+ dataURIRegex = regexp .MustCompile (dataURIRegexPattern )
818+ fqdnLabelRegexp = regexp .MustCompile (`^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$` )
819+ hostnameRFC952LabelRegexp = regexp .MustCompile (`^[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$` )
820+ hostnameRFC1123LabelRegexp = regexp .MustCompile (`^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$` )
819821)
820822
821823// urlEncodedValidator validates URL-encoded strings (no invalid % escapes).
@@ -840,6 +842,26 @@ func newFQDNValidator() *fqdnValidator {
840842 return & fqdnValidator {}
841843}
842844
845+ type hostnameValidator struct {
846+ labelRegexp * regexp.Regexp
847+ errID string
848+ }
849+
850+ // newHostnameValidator returns a new hostnameValidator with the given label regex and error id.
851+ func newHostnameValidator (labelRegexp * regexp.Regexp , errID string ) * hostnameValidator {
852+ return & hostnameValidator {
853+ labelRegexp : labelRegexp ,
854+ errID : errID ,
855+ }
856+ }
857+
858+ type hostnamePortValidator struct {}
859+
860+ // newHostnamePortValidator returns a new hostnamePortValidator.
861+ func newHostnamePortValidator () * hostnamePortValidator {
862+ return & hostnamePortValidator {}
863+ }
864+
843865// Do validates the target is URL encoded.
844866func (u * urlEncodedValidator ) Do (localizer * i18n.Localizer , target any ) error {
845867 v , ok := target .(string )
@@ -907,6 +929,71 @@ func (f *fqdnValidator) Do(localizer *i18n.Localizer, target any) error {
907929 return nil
908930}
909931
932+ // Do validates the target is a hostname according to the provided label regexp.
933+ func (h * hostnameValidator ) Do (localizer * i18n.Localizer , target any ) error {
934+ v , ok := target .(string )
935+ if ! ok {
936+ return NewError (localizer , h .errID , fmt .Sprintf ("value=%v" , target ))
937+ }
938+
939+ if strings .HasPrefix (v , "." ) || strings .HasSuffix (v , "." ) {
940+ return NewError (localizer , h .errID , fmt .Sprintf ("value=%v" , target ))
941+ }
942+
943+ labels := strings .Split (v , "." )
944+ if len (labels ) < 1 {
945+ return NewError (localizer , h .errID , fmt .Sprintf ("value=%v" , target ))
946+ }
947+
948+ totalLen := 0
949+ for _ , label := range labels {
950+ totalLen += len (label ) + 1 // include dot later
951+ if ! h .labelRegexp .MatchString (label ) {
952+ return NewError (localizer , h .errID , fmt .Sprintf ("value=%v" , target ))
953+ }
954+ }
955+ if totalLen - 1 > 253 {
956+ return NewError (localizer , h .errID , fmt .Sprintf ("value=%v" , target ))
957+ }
958+
959+ return nil
960+ }
961+
962+ // Do validates the target is a hostname:port where host is IP or RFC1123 hostname.
963+ func (h * hostnamePortValidator ) Do (localizer * i18n.Localizer , target any ) error {
964+ v , ok := target .(string )
965+ if ! ok {
966+ return NewError (localizer , ErrHostnamePortID , fmt .Sprintf ("value=%v" , target ))
967+ }
968+
969+ host , portStr , err := net .SplitHostPort (v )
970+ if err != nil {
971+ return NewError (localizer , ErrHostnamePortID , fmt .Sprintf ("value=%v" , target ))
972+ }
973+
974+ p , err := strconv .Atoi (portStr )
975+ if err != nil || p < 1 || p > 65535 {
976+ return NewError (localizer , ErrHostnamePortID , fmt .Sprintf ("value=%v" , target ))
977+ }
978+
979+ if strings .HasPrefix (host , "[" ) && strings .HasSuffix (host , "]" ) {
980+ if ip := net .ParseIP (strings .Trim (host , "[]" )); ip != nil {
981+ return nil
982+ }
983+ return NewError (localizer , ErrHostnamePortID , fmt .Sprintf ("value=%v" , target ))
984+ }
985+
986+ if ip := net .ParseIP (host ); ip != nil {
987+ return nil
988+ }
989+
990+ if err := newHostnameValidator (hostnameRFC1123LabelRegexp , ErrHostnamePortID ).Do (localizer , host ); err != nil {
991+ return NewError (localizer , ErrHostnamePortID , fmt .Sprintf ("value=%v" , target ))
992+ }
993+
994+ return nil
995+ }
996+
910997// emailValidator is a struct that contains the validation rules for an email column.
911998type emailValidator struct {
912999 regexp * regexp.Regexp
0 commit comments