/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go

https://github.com/GoogleContainerTools/skaffold · Go · 368 lines · 219 code · 66 blank · 83 comment · 40 complexity · 4636c924a90e5ba8a175a7f36b6fb53c MD5 · raw file

  1. package gojsonschema
  2. import (
  3. "net"
  4. "net/mail"
  5. "net/url"
  6. "regexp"
  7. "strings"
  8. "sync"
  9. "time"
  10. )
  11. type (
  12. // FormatChecker is the interface all formatters added to FormatCheckerChain must implement
  13. FormatChecker interface {
  14. // IsFormat checks if input has the correct format and type
  15. IsFormat(input interface{}) bool
  16. }
  17. // FormatCheckerChain holds the formatters
  18. FormatCheckerChain struct {
  19. formatters map[string]FormatChecker
  20. }
  21. // EmailFormatChecker verifies email address formats
  22. EmailFormatChecker struct{}
  23. // IPV4FormatChecker verifies IP addresses in the IPv4 format
  24. IPV4FormatChecker struct{}
  25. // IPV6FormatChecker verifies IP addresses in the IPv6 format
  26. IPV6FormatChecker struct{}
  27. // DateTimeFormatChecker verifies date/time formats per RFC3339 5.6
  28. //
  29. // Valid formats:
  30. // Partial Time: HH:MM:SS
  31. // Full Date: YYYY-MM-DD
  32. // Full Time: HH:MM:SSZ-07:00
  33. // Date Time: YYYY-MM-DDTHH:MM:SSZ-0700
  34. //
  35. // Where
  36. // YYYY = 4DIGIT year
  37. // MM = 2DIGIT month ; 01-12
  38. // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
  39. // HH = 2DIGIT hour ; 00-23
  40. // MM = 2DIGIT ; 00-59
  41. // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
  42. // T = Literal
  43. // Z = Literal
  44. //
  45. // Note: Nanoseconds are also suported in all formats
  46. //
  47. // http://tools.ietf.org/html/rfc3339#section-5.6
  48. DateTimeFormatChecker struct{}
  49. // DateFormatChecker verifies date formats
  50. //
  51. // Valid format:
  52. // Full Date: YYYY-MM-DD
  53. //
  54. // Where
  55. // YYYY = 4DIGIT year
  56. // MM = 2DIGIT month ; 01-12
  57. // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
  58. DateFormatChecker struct{}
  59. // TimeFormatChecker verifies time formats
  60. //
  61. // Valid formats:
  62. // Partial Time: HH:MM:SS
  63. // Full Time: HH:MM:SSZ-07:00
  64. //
  65. // Where
  66. // HH = 2DIGIT hour ; 00-23
  67. // MM = 2DIGIT ; 00-59
  68. // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
  69. // T = Literal
  70. // Z = Literal
  71. TimeFormatChecker struct{}
  72. // URIFormatChecker validates a URI with a valid Scheme per RFC3986
  73. URIFormatChecker struct{}
  74. // URIReferenceFormatChecker validates a URI or relative-reference per RFC3986
  75. URIReferenceFormatChecker struct{}
  76. // URITemplateFormatChecker validates a URI template per RFC6570
  77. URITemplateFormatChecker struct{}
  78. // HostnameFormatChecker validates a hostname is in the correct format
  79. HostnameFormatChecker struct{}
  80. // UUIDFormatChecker validates a UUID is in the correct format
  81. UUIDFormatChecker struct{}
  82. // RegexFormatChecker validates a regex is in the correct format
  83. RegexFormatChecker struct{}
  84. // JSONPointerFormatChecker validates a JSON Pointer per RFC6901
  85. JSONPointerFormatChecker struct{}
  86. // RelativeJSONPointerFormatChecker validates a relative JSON Pointer is in the correct format
  87. RelativeJSONPointerFormatChecker struct{}
  88. )
  89. var (
  90. // FormatCheckers holds the valid formatters, and is a public variable
  91. // so library users can add custom formatters
  92. FormatCheckers = FormatCheckerChain{
  93. formatters: map[string]FormatChecker{
  94. "date": DateFormatChecker{},
  95. "time": TimeFormatChecker{},
  96. "date-time": DateTimeFormatChecker{},
  97. "hostname": HostnameFormatChecker{},
  98. "email": EmailFormatChecker{},
  99. "idn-email": EmailFormatChecker{},
  100. "ipv4": IPV4FormatChecker{},
  101. "ipv6": IPV6FormatChecker{},
  102. "uri": URIFormatChecker{},
  103. "uri-reference": URIReferenceFormatChecker{},
  104. "iri": URIFormatChecker{},
  105. "iri-reference": URIReferenceFormatChecker{},
  106. "uri-template": URITemplateFormatChecker{},
  107. "uuid": UUIDFormatChecker{},
  108. "regex": RegexFormatChecker{},
  109. "json-pointer": JSONPointerFormatChecker{},
  110. "relative-json-pointer": RelativeJSONPointerFormatChecker{},
  111. },
  112. }
  113. // Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname
  114. 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]))*$`)
  115. // Use a regex to make sure curly brackets are balanced properly after validating it as a AURI
  116. rxURITemplate = regexp.MustCompile("^([^{]*({[^}]*})?)*$")
  117. rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
  118. rxJSONPointer = regexp.MustCompile("^(?:/(?:[^~/]|~0|~1)*)*$")
  119. rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$")
  120. lock = new(sync.RWMutex)
  121. )
  122. // Add adds a FormatChecker to the FormatCheckerChain
  123. // The name used will be the value used for the format key in your json schema
  124. func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain {
  125. lock.Lock()
  126. c.formatters[name] = f
  127. lock.Unlock()
  128. return c
  129. }
  130. // Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
  131. func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
  132. lock.Lock()
  133. delete(c.formatters, name)
  134. lock.Unlock()
  135. return c
  136. }
  137. // Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
  138. func (c *FormatCheckerChain) Has(name string) bool {
  139. lock.RLock()
  140. _, ok := c.formatters[name]
  141. lock.RUnlock()
  142. return ok
  143. }
  144. // IsFormat will check an input against a FormatChecker with the given name
  145. // to see if it is the correct format
  146. func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool {
  147. lock.RLock()
  148. f, ok := c.formatters[name]
  149. lock.RUnlock()
  150. // If a format is unrecognized it should always pass validation
  151. if !ok {
  152. return true
  153. }
  154. return f.IsFormat(input)
  155. }
  156. // IsFormat checks if input is a correctly formatted e-mail address
  157. func (f EmailFormatChecker) IsFormat(input interface{}) bool {
  158. asString, ok := input.(string)
  159. if !ok {
  160. return false
  161. }
  162. _, err := mail.ParseAddress(asString)
  163. return err == nil
  164. }
  165. // IsFormat checks if input is a correctly formatted IPv4-address
  166. func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
  167. asString, ok := input.(string)
  168. if !ok {
  169. return false
  170. }
  171. // Credit: https://github.com/asaskevich/govalidator
  172. ip := net.ParseIP(asString)
  173. return ip != nil && strings.Contains(asString, ".")
  174. }
  175. // IsFormat checks if input is a correctly formatted IPv6=address
  176. func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
  177. asString, ok := input.(string)
  178. if !ok {
  179. return false
  180. }
  181. // Credit: https://github.com/asaskevich/govalidator
  182. ip := net.ParseIP(asString)
  183. return ip != nil && strings.Contains(asString, ":")
  184. }
  185. // IsFormat checks if input is a correctly formatted date/time per RFC3339 5.6
  186. func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {
  187. asString, ok := input.(string)
  188. if !ok {
  189. return false
  190. }
  191. formats := []string{
  192. "15:04:05",
  193. "15:04:05Z07:00",
  194. "2006-01-02",
  195. time.RFC3339,
  196. time.RFC3339Nano,
  197. }
  198. for _, format := range formats {
  199. if _, err := time.Parse(format, asString); err == nil {
  200. return true
  201. }
  202. }
  203. return false
  204. }
  205. // IsFormat checks if input is a correctly formatted date (YYYY-MM-DD)
  206. func (f DateFormatChecker) IsFormat(input interface{}) bool {
  207. asString, ok := input.(string)
  208. if !ok {
  209. return false
  210. }
  211. _, err := time.Parse("2006-01-02", asString)
  212. return err == nil
  213. }
  214. // IsFormat checks if input correctly formatted time (HH:MM:SS or HH:MM:SSZ-07:00)
  215. func (f TimeFormatChecker) IsFormat(input interface{}) bool {
  216. asString, ok := input.(string)
  217. if !ok {
  218. return false
  219. }
  220. if _, err := time.Parse("15:04:05Z07:00", asString); err == nil {
  221. return true
  222. }
  223. _, err := time.Parse("15:04:05", asString)
  224. return err == nil
  225. }
  226. // IsFormat checks if input is correctly formatted URI with a valid Scheme per RFC3986
  227. func (f URIFormatChecker) IsFormat(input interface{}) bool {
  228. asString, ok := input.(string)
  229. if !ok {
  230. return false
  231. }
  232. u, err := url.Parse(asString)
  233. if err != nil || u.Scheme == "" {
  234. return false
  235. }
  236. return !strings.Contains(asString, `\`)
  237. }
  238. // IsFormat checks if input is a correctly formatted URI or relative-reference per RFC3986
  239. func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
  240. asString, ok := input.(string)
  241. if !ok {
  242. return false
  243. }
  244. _, err := url.Parse(asString)
  245. return err == nil && !strings.Contains(asString, `\`)
  246. }
  247. // IsFormat checks if input is a correctly formatted URI template per RFC6570
  248. func (f URITemplateFormatChecker) IsFormat(input interface{}) bool {
  249. asString, ok := input.(string)
  250. if !ok {
  251. return false
  252. }
  253. u, err := url.Parse(asString)
  254. if err != nil || strings.Contains(asString, `\`) {
  255. return false
  256. }
  257. return rxURITemplate.MatchString(u.Path)
  258. }
  259. // IsFormat checks if input is a correctly formatted hostname
  260. func (f HostnameFormatChecker) IsFormat(input interface{}) bool {
  261. asString, ok := input.(string)
  262. if !ok {
  263. return false
  264. }
  265. return rxHostname.MatchString(asString) && len(asString) < 256
  266. }
  267. // IsFormat checks if input is a correctly formatted UUID
  268. func (f UUIDFormatChecker) IsFormat(input interface{}) bool {
  269. asString, ok := input.(string)
  270. if !ok {
  271. return false
  272. }
  273. return rxUUID.MatchString(asString)
  274. }
  275. // IsFormat checks if input is a correctly formatted regular expression
  276. func (f RegexFormatChecker) IsFormat(input interface{}) bool {
  277. asString, ok := input.(string)
  278. if !ok {
  279. return false
  280. }
  281. if asString == "" {
  282. return true
  283. }
  284. _, err := regexp.Compile(asString)
  285. return err == nil
  286. }
  287. // IsFormat checks if input is a correctly formatted JSON Pointer per RFC6901
  288. func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool {
  289. asString, ok := input.(string)
  290. if !ok {
  291. return false
  292. }
  293. return rxJSONPointer.MatchString(asString)
  294. }
  295. // IsFormat checks if input is a correctly formatted relative JSON Pointer
  296. func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool {
  297. asString, ok := input.(string)
  298. if !ok {
  299. return false
  300. }
  301. return rxRelJSONPointer.MatchString(asString)
  302. }