You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

324 lines
10 KiB

  1. // Copyright 2015 go-swagger maintainers
  2. //
  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. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package validate
  15. // TODO: define this as package validate/internal
  16. // This must be done while keeping CI intact with all tests and test coverage
  17. import (
  18. "reflect"
  19. "strconv"
  20. "strings"
  21. "github.com/go-openapi/errors"
  22. "github.com/go-openapi/spec"
  23. )
  24. const (
  25. swaggerBody = "body"
  26. swaggerExample = "example"
  27. swaggerExamples = "examples"
  28. )
  29. const (
  30. objectType = "object"
  31. arrayType = "array"
  32. stringType = "string"
  33. integerType = "integer"
  34. numberType = "number"
  35. booleanType = "boolean"
  36. fileType = "file"
  37. nullType = "null"
  38. )
  39. const (
  40. jsonProperties = "properties"
  41. jsonItems = "items"
  42. jsonType = "type"
  43. //jsonSchema = "schema"
  44. jsonDefault = "default"
  45. )
  46. const (
  47. stringFormatDate = "date"
  48. stringFormatDateTime = "date-time"
  49. stringFormatPassword = "password"
  50. stringFormatByte = "byte"
  51. //stringFormatBinary = "binary"
  52. stringFormatCreditCard = "creditcard"
  53. stringFormatDuration = "duration"
  54. stringFormatEmail = "email"
  55. stringFormatHexColor = "hexcolor"
  56. stringFormatHostname = "hostname"
  57. stringFormatIPv4 = "ipv4"
  58. stringFormatIPv6 = "ipv6"
  59. stringFormatISBN = "isbn"
  60. stringFormatISBN10 = "isbn10"
  61. stringFormatISBN13 = "isbn13"
  62. stringFormatMAC = "mac"
  63. stringFormatBSONObjectID = "bsonobjectid"
  64. stringFormatRGBColor = "rgbcolor"
  65. stringFormatSSN = "ssn"
  66. stringFormatURI = "uri"
  67. stringFormatUUID = "uuid"
  68. stringFormatUUID3 = "uuid3"
  69. stringFormatUUID4 = "uuid4"
  70. stringFormatUUID5 = "uuid5"
  71. integerFormatInt32 = "int32"
  72. integerFormatInt64 = "int64"
  73. integerFormatUInt32 = "uint32"
  74. integerFormatUInt64 = "uint64"
  75. numberFormatFloat32 = "float32"
  76. numberFormatFloat64 = "float64"
  77. numberFormatFloat = "float"
  78. numberFormatDouble = "double"
  79. )
  80. // Helpers available at the package level
  81. var (
  82. pathHelp *pathHelper
  83. valueHelp *valueHelper
  84. errorHelp *errorHelper
  85. paramHelp *paramHelper
  86. responseHelp *responseHelper
  87. )
  88. type errorHelper struct {
  89. // A collection of unexported helpers for error construction
  90. }
  91. func (h *errorHelper) sErr(err errors.Error) *Result {
  92. // Builds a Result from standard errors.Error
  93. return &Result{Errors: []error{err}}
  94. }
  95. func (h *errorHelper) addPointerError(res *Result, err error, ref string, fromPath string) *Result {
  96. // Provides more context on error messages
  97. // reported by the jsoinpointer package by altering the passed Result
  98. if err != nil {
  99. res.AddErrors(cannotResolveRefMsg(fromPath, ref, err))
  100. }
  101. return res
  102. }
  103. type pathHelper struct {
  104. // A collection of unexported helpers for path validation
  105. }
  106. func (h *pathHelper) stripParametersInPath(path string) string {
  107. // Returns a path stripped from all path parameters, with multiple or trailing slashes removed.
  108. //
  109. // Stripping is performed on a slash-separated basis, e.g '/a{/b}' remains a{/b} and not /a.
  110. // - Trailing "/" make a difference, e.g. /a/ !~ /a (ex: canary/bitbucket.org/swagger.json)
  111. // - presence or absence of a parameter makes a difference, e.g. /a/{log} !~ /a/ (ex: canary/kubernetes/swagger.json)
  112. // Regexp to extract parameters from path, with surrounding {}.
  113. // NOTE: important non-greedy modifier
  114. rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
  115. strippedSegments := []string{}
  116. for _, segment := range strings.Split(path, "/") {
  117. strippedSegments = append(strippedSegments, rexParsePathParam.ReplaceAllString(segment, "X"))
  118. }
  119. return strings.Join(strippedSegments, "/")
  120. }
  121. func (h *pathHelper) extractPathParams(path string) (params []string) {
  122. // Extracts all params from a path, with surrounding "{}"
  123. rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
  124. for _, segment := range strings.Split(path, "/") {
  125. for _, v := range rexParsePathParam.FindAllStringSubmatch(segment, -1) {
  126. params = append(params, v...)
  127. }
  128. }
  129. return
  130. }
  131. type valueHelper struct {
  132. // A collection of unexported helpers for value validation
  133. }
  134. func (h *valueHelper) asInt64(val interface{}) int64 {
  135. // Number conversion function for int64, without error checking
  136. // (implements an implicit type upgrade).
  137. v := reflect.ValueOf(val)
  138. switch v.Kind() {
  139. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  140. return v.Int()
  141. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  142. return int64(v.Uint())
  143. case reflect.Float32, reflect.Float64:
  144. return int64(v.Float())
  145. default:
  146. //panic("Non numeric value in asInt64()")
  147. return 0
  148. }
  149. }
  150. func (h *valueHelper) asUint64(val interface{}) uint64 {
  151. // Number conversion function for uint64, without error checking
  152. // (implements an implicit type upgrade).
  153. v := reflect.ValueOf(val)
  154. switch v.Kind() {
  155. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  156. return uint64(v.Int())
  157. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  158. return v.Uint()
  159. case reflect.Float32, reflect.Float64:
  160. return uint64(v.Float())
  161. default:
  162. //panic("Non numeric value in asUint64()")
  163. return 0
  164. }
  165. }
  166. // Same for unsigned floats
  167. func (h *valueHelper) asFloat64(val interface{}) float64 {
  168. // Number conversion function for float64, without error checking
  169. // (implements an implicit type upgrade).
  170. v := reflect.ValueOf(val)
  171. switch v.Kind() {
  172. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  173. return float64(v.Int())
  174. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  175. return float64(v.Uint())
  176. case reflect.Float32, reflect.Float64:
  177. return v.Float()
  178. default:
  179. //panic("Non numeric value in asFloat64()")
  180. return 0
  181. }
  182. }
  183. type paramHelper struct {
  184. // A collection of unexported helpers for parameters resolution
  185. }
  186. func (h *paramHelper) safeExpandedParamsFor(path, method, operationID string, res *Result, s *SpecValidator) (params []spec.Parameter) {
  187. operation, ok := s.analyzer.OperationFor(method, path)
  188. if ok {
  189. // expand parameters first if necessary
  190. resolvedParams := []spec.Parameter{}
  191. for _, ppr := range operation.Parameters {
  192. resolvedParam, red := h.resolveParam(path, method, operationID, &ppr, s)
  193. res.Merge(red)
  194. if resolvedParam != nil {
  195. resolvedParams = append(resolvedParams, *resolvedParam)
  196. }
  197. }
  198. // remove params with invalid expansion from Slice
  199. operation.Parameters = resolvedParams
  200. for _, ppr := range s.analyzer.SafeParamsFor(method, path,
  201. func(p spec.Parameter, err error) bool {
  202. // since params have already been expanded, there are few causes for error
  203. res.AddErrors(someParametersBrokenMsg(path, method, operationID))
  204. // original error from analyzer
  205. res.AddErrors(err)
  206. return true
  207. }) {
  208. params = append(params, ppr)
  209. }
  210. }
  211. return
  212. }
  213. func (h *paramHelper) resolveParam(path, method, operationID string, param *spec.Parameter, s *SpecValidator) (*spec.Parameter, *Result) {
  214. // Ensure parameter is expanded
  215. var err error
  216. res := new(Result)
  217. isRef := param.Ref.String() != ""
  218. if s.spec.SpecFilePath() == "" {
  219. err = spec.ExpandParameterWithRoot(param, s.spec.Spec(), nil)
  220. } else {
  221. err = spec.ExpandParameter(param, s.spec.SpecFilePath())
  222. }
  223. if err != nil { // Safeguard
  224. // NOTE: we may enter enter here when the whole parameter is an unresolved $ref
  225. refPath := strings.Join([]string{"\"" + path + "\"", method}, ".")
  226. errorHelp.addPointerError(res, err, param.Ref.String(), refPath)
  227. return nil, res
  228. }
  229. res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, isRef))
  230. return param, res
  231. }
  232. func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation string, isRef bool) *Result {
  233. // Secure parameter structure after $ref resolution
  234. res := new(Result)
  235. simpleZero := spec.SimpleSchema{}
  236. // Try to explain why... best guess
  237. switch {
  238. case pr.In == swaggerBody && (pr.SimpleSchema != simpleZero && pr.SimpleSchema.Type != objectType):
  239. if isRef {
  240. // Most likely, a $ref with a sibling is an unwanted situation: in itself this is a warning...
  241. // but we detect it because of the following error:
  242. // schema took over Parameter for an unexplained reason
  243. res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
  244. }
  245. res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
  246. case pr.In != swaggerBody && pr.Schema != nil:
  247. if isRef {
  248. res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
  249. }
  250. res.AddErrors(invalidParameterDefinitionAsSchemaMsg(path, in, operation))
  251. case (pr.In == swaggerBody && pr.Schema == nil) || (pr.In != swaggerBody && pr.SimpleSchema == simpleZero):
  252. // Other unexpected mishaps
  253. res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
  254. }
  255. return res
  256. }
  257. type responseHelper struct {
  258. // A collection of unexported helpers for response resolution
  259. }
  260. func (r *responseHelper) expandResponseRef(
  261. response *spec.Response,
  262. path string, s *SpecValidator) (*spec.Response, *Result) {
  263. // Ensure response is expanded
  264. var err error
  265. res := new(Result)
  266. if s.spec.SpecFilePath() == "" {
  267. // there is no physical document to resolve $ref in response
  268. err = spec.ExpandResponseWithRoot(response, s.spec.Spec(), nil)
  269. } else {
  270. err = spec.ExpandResponse(response, s.spec.SpecFilePath())
  271. }
  272. if err != nil { // Safeguard
  273. // NOTE: we may enter here when the whole response is an unresolved $ref.
  274. errorHelp.addPointerError(res, err, response.Ref.String(), path)
  275. return nil, res
  276. }
  277. return response, res
  278. }
  279. func (r *responseHelper) responseMsgVariants(
  280. responseType string,
  281. responseCode int) (responseName, responseCodeAsStr string) {
  282. // Path variants for messages
  283. if responseType == jsonDefault {
  284. responseCodeAsStr = jsonDefault
  285. responseName = "default response"
  286. } else {
  287. responseCodeAsStr = strconv.Itoa(responseCode)
  288. responseName = "response " + responseCodeAsStr
  289. }
  290. return
  291. }