regexp.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. package reference
  2. import "regexp"
  3. var (
  4. // alphaNumericRegexp defines the alpha numeric atom, typically a
  5. // component of names. This only allows lower case characters and digits.
  6. alphaNumericRegexp = match(`[a-z0-9]+`)
  7. // separatorRegexp defines the separators allowed to be embedded in name
  8. // components. This allow one period, one or two underscore and multiple
  9. // dashes.
  10. separatorRegexp = match(`(?:[._]|__|[-]*)`)
  11. // nameComponentRegexp restricts registry path component names to start
  12. // with at least one letter or number, with following parts able to be
  13. // separated by one period, one or two underscore and multiple dashes.
  14. nameComponentRegexp = expression(
  15. alphaNumericRegexp,
  16. optional(repeated(separatorRegexp, alphaNumericRegexp)))
  17. // hostnameComponentRegexp restricts the registry hostname component of a
  18. // repository name to start with a component as defined by hostnameRegexp
  19. // and followed by an optional port.
  20. hostnameComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
  21. // hostnameRegexp defines the structure of potential hostname components
  22. // that may be part of image names. This is purposely a subset of what is
  23. // allowed by DNS to ensure backwards compatibility with Docker image
  24. // names.
  25. hostnameRegexp = expression(
  26. hostnameComponentRegexp,
  27. optional(repeated(literal(`.`), hostnameComponentRegexp)),
  28. optional(literal(`:`), match(`[0-9]+`)))
  29. // TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
  30. TagRegexp = match(`[\w][\w.-]{0,127}`)
  31. // anchoredTagRegexp matches valid tag names, anchored at the start and
  32. // end of the matched string.
  33. anchoredTagRegexp = anchored(TagRegexp)
  34. // DigestRegexp matches valid digests.
  35. DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
  36. // anchoredDigestRegexp matches valid digests, anchored at the start and
  37. // end of the matched string.
  38. anchoredDigestRegexp = anchored(DigestRegexp)
  39. // NameRegexp is the format for the name component of references. The
  40. // regexp has capturing groups for the hostname and name part omitting
  41. // the separating forward slash from either.
  42. NameRegexp = expression(
  43. optional(hostnameRegexp, literal(`/`)),
  44. nameComponentRegexp,
  45. optional(repeated(literal(`/`), nameComponentRegexp)))
  46. // anchoredNameRegexp is used to parse a name value, capturing the
  47. // hostname and trailing components.
  48. anchoredNameRegexp = anchored(
  49. optional(capture(hostnameRegexp), literal(`/`)),
  50. capture(nameComponentRegexp,
  51. optional(repeated(literal(`/`), nameComponentRegexp))))
  52. // ReferenceRegexp is the full supported format of a reference. The regexp
  53. // is anchored and has capturing groups for name, tag, and digest
  54. // components.
  55. ReferenceRegexp = anchored(capture(NameRegexp),
  56. optional(literal(":"), capture(TagRegexp)),
  57. optional(literal("@"), capture(DigestRegexp)))
  58. )
  59. // match compiles the string to a regular expression.
  60. var match = regexp.MustCompile
  61. // literal compiles s into a literal regular expression, escaping any regexp
  62. // reserved characters.
  63. func literal(s string) *regexp.Regexp {
  64. re := match(regexp.QuoteMeta(s))
  65. if _, complete := re.LiteralPrefix(); !complete {
  66. panic("must be a literal")
  67. }
  68. return re
  69. }
  70. // expression defines a full expression, where each regular expression must
  71. // follow the previous.
  72. func expression(res ...*regexp.Regexp) *regexp.Regexp {
  73. var s string
  74. for _, re := range res {
  75. s += re.String()
  76. }
  77. return match(s)
  78. }
  79. // optional wraps the expression in a non-capturing group and makes the
  80. // production optional.
  81. func optional(res ...*regexp.Regexp) *regexp.Regexp {
  82. return match(group(expression(res...)).String() + `?`)
  83. }
  84. // repeated wraps the regexp in a non-capturing group to get one or more
  85. // matches.
  86. func repeated(res ...*regexp.Regexp) *regexp.Regexp {
  87. return match(group(expression(res...)).String() + `+`)
  88. }
  89. // group wraps the regexp in a non-capturing group.
  90. func group(res ...*regexp.Regexp) *regexp.Regexp {
  91. return match(`(?:` + expression(res...).String() + `)`)
  92. }
  93. // capture wraps the expression in a capturing group.
  94. func capture(res ...*regexp.Regexp) *regexp.Regexp {
  95. return match(`(` + expression(res...).String() + `)`)
  96. }
  97. // anchored anchors the regular expression by adding start and end delimiters.
  98. func anchored(res ...*regexp.Regexp) *regexp.Regexp {
  99. return match(`^` + expression(res...).String() + `$`)
  100. }