Test Failed
Pull Request — main (#75)
by Igor
01:48
created

validate.isPrivateIP   B

Complexity

Conditions 7

Size

Total Lines 15
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 6
nop 1
dl 0
loc 15
rs 8
c 0
b 0
f 0
1
package validate
2
3
import (
4
	"errors"
5
	"net"
6
	"net/url"
7
	"regexp"
8
	"strings"
9
)
10
11
var ErrUnexpectedSchema = errors.New("unexpected schema")
12
13
// URL is used to validate that value is a valid URL string. By default, (if no schemas are passed),
14
// the function checks only for the http:// and https:// schemas. Use the schemas' argument
15
// to configure the list of expected schemas. If an empty string is passed as a schema, then
16
// URL value may be treated as relative (without schema, e.g. "//example.com").
17
//
18
// If value is not a valid URL the function will return one of the errors:
19
//	• parsing error from url.Parse method if value cannot be parsed as a URL;
20
//	• ErrUnexpectedSchema if schema is not matching one of the listed schemas;
21
//	• ErrInvalid if value is not matching the regular expression.
22
func URL(value string, schemas ...string) error {
23
	if len(schemas) == 0 {
24
		schemas = []string{"http", "https"}
25
	}
26
	u, err := url.Parse(value)
27
	if err != nil {
28
		return err
29
	}
30
31
	err = validateSchema(u, schemas)
32
	if err != nil {
33
		return err
34
	}
35
36
	if !urlRegex.MatchString(value) {
37
		return ErrInvalid
38
	}
39
40
	return nil
41
}
42
43
func validateSchema(u *url.URL, schemas []string) error {
44
	for _, schema := range schemas {
45
		if schema == u.Scheme {
46
			return nil
47
		}
48
	}
49
50
	return ErrUnexpectedSchema
51
}
52
53
// IPRestriction can be used to limit valid IP address values.
54
type IPRestriction func(ip net.IP) bool
55
56
// DenyPrivateIP denies using of private IPs according to RFC 1918 (IPv4 addresses)
57
// and RFC 4193 (IPv6 addresses).
58
func DenyPrivateIP() IPRestriction {
59
	return func(ip net.IP) bool {
60
		return ip.IsPrivate()
61
	}
62
}
63
64
// IP validates that a value is a valid IP address (IPv4 or IPv6). You can use a list
65
// of restrictions to additionally check for a restricted range of IPs. For example,
66
// you can deny using private IP addresses using DenyPrivateIP function.
67
//
68
// If value is not valid the function will return one of the errors:
69
//	• ErrInvalid on invalid IP address;
70
//	• ErrProhibited on restricted IP address.
71
func IP(value string, restrictions ...IPRestriction) error {
72
	return validateIP(value, restrictions...)
73
}
74
75
// IPv4 validates that a value is a valid IPv4 address. You can use a list
76
// of restrictions to additionally check for a restricted range of IPs. For example,
77
// you can deny using private IP addresses using DenyPrivateIP function.
78
//
79
// If value is not valid the function will return one of the errors:
80
//	• ErrInvalid on invalid IP address or when using IPv6;
81
//	• ErrProhibited on restricted IP address.
82
func IPv4(value string, restrictions ...IPRestriction) error {
83
	err := validateIP(value, restrictions...)
84
	if err != nil {
85
		return err
86
	}
87
	if !strings.Contains(value, ".") || strings.Contains(value, ":") {
88
		return ErrInvalid
89
	}
90
91
	return nil
92
}
93
94
// IPv6 validates that a value is a valid IPv6 address. You can use a list
95
// of restrictions to additionally check for a restricted range of IPs. For example,
96
// you can deny using private IP addresses using DenyPrivateIP function.
97
//
98
// If value is not valid the function will return one of the errors:
99
//	• ErrInvalid on invalid IP address or when using IPv4;
100
//	• ErrProhibited on restricted IP address.
101
func IPv6(value string, restrictions ...IPRestriction) error {
102
	err := validateIP(value, restrictions...)
103
	if err != nil {
104
		return err
105
	}
106
	if !strings.Contains(value, ":") {
107
		return ErrInvalid
108
	}
109
110
	return nil
111
}
112
113
func validateIP(value string, restrictions ...IPRestriction) error {
114
	ip := net.ParseIP(value)
115
	if ip == nil {
116
		return ErrInvalid
117
	}
118
	for _, isProhibited := range restrictions {
119
		if isProhibited(ip) {
120
			return ErrProhibited
121
		}
122
	}
123
124
	return nil
125
}
126
127
const (
128
	urlSchema     = `([a-z]*:)?//`
129
	urlBasicAuth  = `(((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+)@)?`
130
	urlDomainName = `([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?)`
131
	ipv4          = `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`
132
	ipv6          = `\[(?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))\]`
133
	urlHost       = `(` + urlDomainName + `|` + ipv4 + `|` + ipv6 + `)`
134
	urlPort       = `(:[0-9]+)?`
135
	urlPath       = `(?:/(?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*`
136
	urlQuery      = `(?:\?(?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?`
137
	urlFragment   = `(?:\#(?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?`
138
	urlPattern    = `(?i)^` + urlSchema + urlBasicAuth + urlHost + urlPort + urlPath + urlQuery + urlFragment + `$`
139
)
140
141
var urlRegex = regexp.MustCompile(urlPattern)
142