审查视图

vendor/github.com/xeipuuv/gojsonschema/format_checkers.go 9.7 KB
tangxvhui authored
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
package gojsonschema

import (
	"net"
	"net/mail"
	"net/url"
	"regexp"
	"strings"
	"sync"
	"time"
)

type (
	// FormatChecker is the interface all formatters added to FormatCheckerChain must implement
	FormatChecker interface {
		// IsFormat checks if input has the correct format and type
		IsFormat(input interface{}) bool
	}

	// FormatCheckerChain holds the formatters
	FormatCheckerChain struct {
		formatters map[string]FormatChecker
	}

	// EmailFormatChecker verifies email address formats
	EmailFormatChecker struct{}

	// IPV4FormatChecker verifies IP addresses in the IPv4 format
	IPV4FormatChecker struct{}

	// IPV6FormatChecker verifies IP addresses in the IPv6 format
	IPV6FormatChecker struct{}

	// DateTimeFormatChecker verifies date/time formats per RFC3339 5.6
	//
	// Valid formats:
	// 		Partial Time: HH:MM:SS
	//		Full Date: YYYY-MM-DD
	// 		Full Time: HH:MM:SSZ-07:00
	//		Date Time: YYYY-MM-DDTHH:MM:SSZ-0700
	//
	// 	Where
	//		YYYY = 4DIGIT year
	//		MM = 2DIGIT month ; 01-12
	//		DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
	//		HH = 2DIGIT hour ; 00-23
	//		MM = 2DIGIT ; 00-59
	//		SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
	//		T = Literal
	//		Z = Literal
	//
	//	Note: Nanoseconds are also suported in all formats
	//
	// http://tools.ietf.org/html/rfc3339#section-5.6
	DateTimeFormatChecker struct{}

	// DateFormatChecker verifies date formats
	//
	// Valid format:
	//		Full Date: YYYY-MM-DD
	//
	// 	Where
	//		YYYY = 4DIGIT year
	//		MM = 2DIGIT month ; 01-12
	//		DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
	DateFormatChecker struct{}

	// TimeFormatChecker verifies time formats
	//
	// Valid formats:
	// 		Partial Time: HH:MM:SS
	// 		Full Time: HH:MM:SSZ-07:00
	//
	// 	Where
	//		HH = 2DIGIT hour ; 00-23
	//		MM = 2DIGIT ; 00-59
	//		SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
	//		T = Literal
	//		Z = Literal
	TimeFormatChecker struct{}

	// URIFormatChecker validates a URI with a valid Scheme per RFC3986
	URIFormatChecker struct{}

	// URIReferenceFormatChecker validates a URI or relative-reference per RFC3986
	URIReferenceFormatChecker struct{}

	// URITemplateFormatChecker validates a URI template per RFC6570
	URITemplateFormatChecker struct{}

	// HostnameFormatChecker validates a hostname is in the correct format
	HostnameFormatChecker struct{}

	// UUIDFormatChecker validates a UUID is in the correct format
	UUIDFormatChecker struct{}

	// RegexFormatChecker validates a regex is in the correct format
	RegexFormatChecker struct{}

	// JSONPointerFormatChecker validates a JSON Pointer per RFC6901
	JSONPointerFormatChecker struct{}

	// RelativeJSONPointerFormatChecker validates a relative JSON Pointer is in the correct format
	RelativeJSONPointerFormatChecker struct{}
)

var (
	// FormatCheckers holds the valid formatters, and is a public variable
	// so library users can add custom formatters
	FormatCheckers = FormatCheckerChain{
		formatters: map[string]FormatChecker{
			"date":                  DateFormatChecker{},
			"time":                  TimeFormatChecker{},
			"date-time":             DateTimeFormatChecker{},
			"hostname":              HostnameFormatChecker{},
			"email":                 EmailFormatChecker{},
			"idn-email":             EmailFormatChecker{},
			"ipv4":                  IPV4FormatChecker{},
			"ipv6":                  IPV6FormatChecker{},
			"uri":                   URIFormatChecker{},
			"uri-reference":         URIReferenceFormatChecker{},
			"iri":                   URIFormatChecker{},
			"iri-reference":         URIReferenceFormatChecker{},
			"uri-template":          URITemplateFormatChecker{},
			"uuid":                  UUIDFormatChecker{},
			"regex":                 RegexFormatChecker{},
			"json-pointer":          JSONPointerFormatChecker{},
			"relative-json-pointer": RelativeJSONPointerFormatChecker{},
		},
	}

	// Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname
	rxHostname = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`)

	// Use a regex to make sure curly brackets are balanced properly after validating it as a AURI
	rxURITemplate = regexp.MustCompile("^([^{]*({[^}]*})?)*$")

	rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")

	rxJSONPointer = regexp.MustCompile("^(?:/(?:[^~/]|~0|~1)*)*$")

	rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$")

	lock = new(sync.RWMutex)
)

// Add adds a FormatChecker to the FormatCheckerChain
// The name used will be the value used for the format key in your json schema
func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain {
	lock.Lock()
	c.formatters[name] = f
	lock.Unlock()

	return c
}

// Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
	lock.Lock()
	delete(c.formatters, name)
	lock.Unlock()

	return c
}

// Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
func (c *FormatCheckerChain) Has(name string) bool {
	lock.RLock()
	_, ok := c.formatters[name]
	lock.RUnlock()

	return ok
}

// IsFormat will check an input against a FormatChecker with the given name
// to see if it is the correct format
func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool {
	lock.RLock()
	f, ok := c.formatters[name]
	lock.RUnlock()

	// If a format is unrecognized it should always pass validation
	if !ok {
		return true
	}

	return f.IsFormat(input)
}

// IsFormat checks if input is a correctly formatted e-mail address
func (f EmailFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	_, err := mail.ParseAddress(asString)
	return err == nil
}

// IsFormat checks if input is a correctly formatted IPv4-address
func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	// Credit: https://github.com/asaskevich/govalidator
	ip := net.ParseIP(asString)
	return ip != nil && strings.Contains(asString, ".")
}

// IsFormat checks if input is a correctly formatted IPv6=address
func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	// Credit: https://github.com/asaskevich/govalidator
	ip := net.ParseIP(asString)
	return ip != nil && strings.Contains(asString, ":")
}

// IsFormat checks if input is a correctly formatted  date/time per RFC3339 5.6
func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	formats := []string{
		"15:04:05",
		"15:04:05Z07:00",
		"2006-01-02",
		time.RFC3339,
		time.RFC3339Nano,
	}

	for _, format := range formats {
		if _, err := time.Parse(format, asString); err == nil {
			return true
		}
	}

	return false
}

// IsFormat checks if input is a correctly formatted  date (YYYY-MM-DD)
func (f DateFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}
	_, err := time.Parse("2006-01-02", asString)
	return err == nil
}

// IsFormat checks if input correctly formatted time (HH:MM:SS or HH:MM:SSZ-07:00)
func (f TimeFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	if _, err := time.Parse("15:04:05Z07:00", asString); err == nil {
		return true
	}

	_, err := time.Parse("15:04:05", asString)
	return err == nil
}

// IsFormat checks if input is correctly formatted  URI with a valid Scheme per RFC3986
func (f URIFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	u, err := url.Parse(asString)

	if err != nil || u.Scheme == "" {
		return false
	}

	return !strings.Contains(asString, `\`)
}

// IsFormat checks if input is a correctly formatted URI or relative-reference per RFC3986
func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	_, err := url.Parse(asString)
	return err == nil && !strings.Contains(asString, `\`)
}

// IsFormat checks if input is a correctly formatted URI template per RFC6570
func (f URITemplateFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	u, err := url.Parse(asString)
	if err != nil || strings.Contains(asString, `\`) {
		return false
	}

	return rxURITemplate.MatchString(u.Path)
}

// IsFormat checks if input is a correctly formatted hostname
func (f HostnameFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	return rxHostname.MatchString(asString) && len(asString) < 256
}

// IsFormat checks if input is a correctly formatted UUID
func (f UUIDFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	return rxUUID.MatchString(asString)
}

// IsFormat checks if input is a correctly formatted regular expression
func (f RegexFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	if asString == "" {
		return true
	}
	_, err := regexp.Compile(asString)
	return err == nil
}

// IsFormat checks if input is a correctly formatted JSON Pointer per RFC6901
func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	return rxJSONPointer.MatchString(asString)
}

// IsFormat checks if input is a correctly formatted relative JSON Pointer
func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool {
	asString, ok := input.(string)
	if !ok {
		return false
	}

	return rxRelJSONPointer.MatchString(asString)
}