parser.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /*
  2. Copyright © The CDI Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package parser
  14. import (
  15. "fmt"
  16. "strings"
  17. )
  18. // QualifiedName returns the qualified name for a device.
  19. // The syntax for a qualified device names is
  20. //
  21. // "<vendor>/<class>=<name>".
  22. //
  23. // A valid vendor and class name may contain the following runes:
  24. //
  25. // 'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
  26. //
  27. // A valid device name may contain the following runes:
  28. //
  29. // 'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':'
  30. func QualifiedName(vendor, class, name string) string {
  31. return vendor + "/" + class + "=" + name
  32. }
  33. // IsQualifiedName tests if a device name is qualified.
  34. func IsQualifiedName(device string) bool {
  35. _, _, _, err := ParseQualifiedName(device)
  36. return err == nil
  37. }
  38. // ParseQualifiedName splits a qualified name into device vendor, class,
  39. // and name. If the device fails to parse as a qualified name, or if any
  40. // of the split components fail to pass syntax validation, vendor and
  41. // class are returned as empty, together with the verbatim input as the
  42. // name and an error describing the reason for failure.
  43. func ParseQualifiedName(device string) (string, string, string, error) {
  44. vendor, class, name := ParseDevice(device)
  45. if vendor == "" {
  46. return "", "", device, fmt.Errorf("unqualified device %q, missing vendor", device)
  47. }
  48. if class == "" {
  49. return "", "", device, fmt.Errorf("unqualified device %q, missing class", device)
  50. }
  51. if name == "" {
  52. return "", "", device, fmt.Errorf("unqualified device %q, missing device name", device)
  53. }
  54. if err := ValidateVendorName(vendor); err != nil {
  55. return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
  56. }
  57. if err := ValidateClassName(class); err != nil {
  58. return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
  59. }
  60. if err := ValidateDeviceName(name); err != nil {
  61. return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
  62. }
  63. return vendor, class, name, nil
  64. }
  65. // ParseDevice tries to split a device name into vendor, class, and name.
  66. // If this fails, for instance in the case of unqualified device names,
  67. // ParseDevice returns an empty vendor and class together with name set
  68. // to the verbatim input.
  69. func ParseDevice(device string) (string, string, string) {
  70. if device == "" || device[0] == '/' {
  71. return "", "", device
  72. }
  73. parts := strings.SplitN(device, "=", 2)
  74. if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
  75. return "", "", device
  76. }
  77. name := parts[1]
  78. vendor, class := ParseQualifier(parts[0])
  79. if vendor == "" {
  80. return "", "", device
  81. }
  82. return vendor, class, name
  83. }
  84. // ParseQualifier splits a device qualifier into vendor and class.
  85. // The syntax for a device qualifier is
  86. //
  87. // "<vendor>/<class>"
  88. //
  89. // If parsing fails, an empty vendor and the class set to the
  90. // verbatim input is returned.
  91. func ParseQualifier(kind string) (string, string) {
  92. parts := strings.SplitN(kind, "/", 2)
  93. if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
  94. return "", kind
  95. }
  96. return parts[0], parts[1]
  97. }
  98. // ValidateVendorName checks the validity of a vendor name.
  99. // A vendor name may contain the following ASCII characters:
  100. // - upper- and lowercase letters ('A'-'Z', 'a'-'z')
  101. // - digits ('0'-'9')
  102. // - underscore, dash, and dot ('_', '-', and '.')
  103. func ValidateVendorName(vendor string) error {
  104. err := validateVendorOrClassName(vendor)
  105. if err != nil {
  106. err = fmt.Errorf("invalid vendor. %w", err)
  107. }
  108. return err
  109. }
  110. // ValidateClassName checks the validity of class name.
  111. // A class name may contain the following ASCII characters:
  112. // - upper- and lowercase letters ('A'-'Z', 'a'-'z')
  113. // - digits ('0'-'9')
  114. // - underscore, dash, and dot ('_', '-', and '.')
  115. func ValidateClassName(class string) error {
  116. err := validateVendorOrClassName(class)
  117. if err != nil {
  118. err = fmt.Errorf("invalid class. %w", err)
  119. }
  120. return err
  121. }
  122. // validateVendorOrClassName checks the validity of vendor or class name.
  123. // A name may contain the following ASCII characters:
  124. // - upper- and lowercase letters ('A'-'Z', 'a'-'z')
  125. // - digits ('0'-'9')
  126. // - underscore, dash, and dot ('_', '-', and '.')
  127. func validateVendorOrClassName(name string) error {
  128. if name == "" {
  129. return fmt.Errorf("empty name")
  130. }
  131. if !IsLetter(rune(name[0])) {
  132. return fmt.Errorf("%q, should start with letter", name)
  133. }
  134. for _, c := range string(name[1 : len(name)-1]) {
  135. switch {
  136. case IsAlphaNumeric(c):
  137. case c == '_' || c == '-' || c == '.':
  138. default:
  139. return fmt.Errorf("invalid character '%c' in name %q",
  140. c, name)
  141. }
  142. }
  143. if !IsAlphaNumeric(rune(name[len(name)-1])) {
  144. return fmt.Errorf("%q, should end with a letter or digit", name)
  145. }
  146. return nil
  147. }
  148. // ValidateDeviceName checks the validity of a device name.
  149. // A device name may contain the following ASCII characters:
  150. // - upper- and lowercase letters ('A'-'Z', 'a'-'z')
  151. // - digits ('0'-'9')
  152. // - underscore, dash, dot, colon ('_', '-', '.', ':')
  153. func ValidateDeviceName(name string) error {
  154. if name == "" {
  155. return fmt.Errorf("invalid (empty) device name")
  156. }
  157. if !IsAlphaNumeric(rune(name[0])) {
  158. return fmt.Errorf("invalid class %q, should start with a letter or digit", name)
  159. }
  160. if len(name) == 1 {
  161. return nil
  162. }
  163. for _, c := range string(name[1 : len(name)-1]) {
  164. switch {
  165. case IsAlphaNumeric(c):
  166. case c == '_' || c == '-' || c == '.' || c == ':':
  167. default:
  168. return fmt.Errorf("invalid character '%c' in device name %q",
  169. c, name)
  170. }
  171. }
  172. if !IsAlphaNumeric(rune(name[len(name)-1])) {
  173. return fmt.Errorf("invalid name %q, should end with a letter or digit", name)
  174. }
  175. return nil
  176. }
  177. // IsLetter reports whether the rune is a letter.
  178. func IsLetter(c rune) bool {
  179. return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
  180. }
  181. // IsDigit reports whether the rune is a digit.
  182. func IsDigit(c rune) bool {
  183. return '0' <= c && c <= '9'
  184. }
  185. // IsAlphaNumeric reports whether the rune is a letter or digit.
  186. func IsAlphaNumeric(c rune) bool {
  187. return IsLetter(c) || IsDigit(c)
  188. }