openapi_first 4.0.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc1e6456e7c11e5079255b24e6407f61d9e94677fe07bef54000c26fe1174e05
4
- data.tar.gz: 313d87080b433ca45defd7a47a811f6f2a91b93804a6655b9121b86d95c2e2f7
3
+ metadata.gz: 70cf5324252fd4c36421cb732127b220e45c3eefbafba7ed63ee513aea2d5b84
4
+ data.tar.gz: 43358cb067c765b59920160f589eb929216a9721b806a4a37b24ab039f536599
5
5
  SHA512:
6
- metadata.gz: 6a04433e95586c8cc5ad34f791e1a2fc598584be5e23c7e7518c5eb9bd04d77c96fb3b1fe9330a16e4a9060b119b835e42863a11a1abb77c8f5b2cbba246fdcc
7
- data.tar.gz: bb8a46ea7aaf083819cac30ede78fe6f560f339f2d8282d727e111b3e504dab42aa0a7b82d1372bd060a5217818f694cadad11f71085e15baed7a10c48f5578e
6
+ metadata.gz: d1b97b74dcdf98616c0f51c3e6a2c53cedb3685518f9697491eadca4110d39f12f68c54769535e6d171bf401d8d47085279124d70534fb5959c47cdf3b4d4eda
7
+ data.tar.gz: a27158cbd2760fa99de1f26193ce5450f5d2ef409c4a780da94ab102c24439dd1d149d6824a99cc8287617cd999cccc9de4989a33edaaaad2debfd7045a3892e
data/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 4.2.0
6
+
7
+ - Added `config.regexp_resolver` configuration option, `'ruby'` (default) or `'ecma'` (also as Symbol). The value is passed to json_schemer's `regexp_resolver` configuration. In OpenAPI, the pattern keyword specifies a regular expression that must conform to the ECMA-262 standard. So using `'ecma'` is recommended.
8
+
9
+ ## 4.1.0
10
+
11
+ - Query and cookie parameter values with invalid encoding (like `?search=%C3`) produce a 400 response now. Before such a value passed validation or raised an `ArgumentError`. This applies to documented and undocumented query parameter, but only to documented cookie parameters.
12
+ - A query string with more parameters than Rack accepts (4096 by default) produces a 400 response now instead of raising `Rack::QueryParser::QueryLimitError`. See the [rack documentation](https://rack.github.io/rack/main/index.html#configuration) about how to configure this.
13
+
5
14
  ## 4.0.1
6
15
 
7
16
  - Fix: Reduced memory usage with large API descriptions. Schemas no longer each deep-copy (stringify keys) the whole OAD to build a root schema. This is a json_schemer specific optimization.
@@ -27,9 +27,9 @@ module OpenapiFirst
27
27
  end
28
28
 
29
29
  def initialize(contents, filepath:, config:)
30
+ @config = config
30
31
  meta_schema = detect_meta_schema(contents, filepath)
31
32
  @schemer_configuration = build_schemer_config(filepath:, meta_schema:)
32
- @config = config
33
33
  @file_loader = FileLoader.new
34
34
  ref_resolver = RefResolver.new(file_loader:)
35
35
  @contents = ref_resolver.for(contents, filepath:)
@@ -47,6 +47,7 @@ module OpenapiFirst
47
47
  end
48
48
  result.meta_schema = meta_schema
49
49
  result.insert_property_defaults = true
50
+ result.regexp_resolver = config.regexp_resolver if config.regexp_resolver
50
51
  result
51
52
  end
52
53
 
@@ -126,10 +127,10 @@ module OpenapiFirst
126
127
  cookie = build_parameters(grouped[:cookie])
127
128
  ParsedParameters.new(
128
129
  all: [*path, *query, *header, *cookie].freeze,
129
- path_parser: grouped[:path] && ParametersParser.new(path),
130
+ path_parser: grouped[:path] && ParametersParser.new(path, check_encoding: false),
130
131
  query_parser: QueryStringParser.new(query),
131
- header_parser: grouped[:header] && ParametersParser.new(header),
132
- cookie_parser: grouped[:cookie] && ParametersParser.new(cookie),
132
+ header_parser: grouped[:header] && ParametersParser.new(header, check_encoding: false),
133
+ cookie_parser: grouped[:cookie] && ParametersParser.new(cookie, check_encoding: true),
133
134
  path_schema: build_parameter_schema(grouped[:path]),
134
135
  query_schema: build_parameter_schema(grouped[:query]),
135
136
  header_schema: build_parameter_schema(grouped[:header]),
@@ -8,6 +8,7 @@ module OpenapiFirst
8
8
  @parent = parent
9
9
  @request_validation_error_response = parent.request_validation_error_response
10
10
  @path = parent.path
11
+ @regexp_resolver = parent.regexp_resolver
11
12
  end
12
13
 
13
14
  private attr_reader :parent
@@ -16,13 +16,14 @@ module OpenapiFirst
16
16
  @request_validation_error_response = OpenapiFirst.find_error_response(:default)
17
17
  @hooks = HOOKS.to_h { [_1, Set.new] }
18
18
  @path = nil
19
+ @regexp_resolver = nil
19
20
  end
20
21
 
21
22
  def register(path_or_definition, as: :default)
22
23
  OpenapiFirst.register(path_or_definition, as:)
23
24
  end
24
25
 
25
- attr_reader :hooks, :request_validation_error_response
26
+ attr_reader :hooks, :request_validation_error_response, :regexp_resolver
26
27
  attr_accessor :path
27
28
 
28
29
  # Return a child configuration that still receives updates of global hooks.
@@ -49,6 +50,10 @@ module OpenapiFirst
49
50
  Plugins.load(name).configure(self, **)
50
51
  end
51
52
 
53
+ def regexp_resolver=(resolver)
54
+ @regexp_resolver = resolver.is_a?(Symbol) ? resolver.to_s : resolver
55
+ end
56
+
52
57
  def request_validation_error_response=(mod)
53
58
  @request_validation_error_response = if mod.is_a?(Symbol)
54
59
  OpenapiFirst.find_error_response(mod)
@@ -5,8 +5,10 @@ module OpenapiFirst
5
5
  # @visibility private
6
6
  class ParametersParser
7
7
  # @param parameters [Array<Parameter>]
8
- def initialize(parameters)
8
+ # @param check_encoding [Boolean] Raise Rack::Utils::InvalidParameterError if a value has an invalid encoding.
9
+ def initialize(parameters, check_encoding:)
9
10
  @parameters = parameters
11
+ @check_encoding = check_encoding
10
12
  end
11
13
 
12
14
  attr_reader :parameters
@@ -16,8 +18,18 @@ module OpenapiFirst
16
18
  parameters.each_with_object({}) do |parameter, result|
17
19
  next unless parameters_hash.key?(parameter.name)
18
20
 
19
- result[parameter.name] = parameter.unpack_and_convert(parameters_hash[parameter.name])
21
+ value = parameters_hash[parameter.name]
22
+ check_encoding!(value) if @check_encoding
23
+ result[parameter.name] = parameter.unpack_and_convert(value)
20
24
  end
21
25
  end
26
+
27
+ private
28
+
29
+ def check_encoding!(value)
30
+ return if !value.is_a?(::String) || value.valid_encoding?
31
+
32
+ raise Rack::Utils::InvalidParameterError, "invalid encoding (#{value.inspect})"
33
+ end
22
34
  end
23
35
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rack'
4
+ require 'rack/query_parser'
4
5
  require_relative 'parameters_parser'
5
6
 
6
7
  module OpenapiFirst
@@ -10,11 +11,15 @@ module OpenapiFirst
10
11
  DEEP_PROP = '\[([\w-]+)\]$'
11
12
  private_constant :DEEP_PROP
12
13
 
14
+ # Rack < 3.1 does not know about Rack::BadRequest
15
+ BAD_REQUEST = defined?(Rack::BadRequest) ? Rack::BadRequest : Rack::QueryParser::QueryLimitError
16
+ private_constant :BAD_REQUEST
17
+
13
18
  # @param parameters [Array<Parameter>]
14
19
  def initialize(parameters)
15
20
  @parameters = parameters
16
21
  @deep_object_parameters, flat_parameters = parameters.partition(&:deep_object?)
17
- @flat_parser = ParametersParser.new(flat_parameters)
22
+ @flat_parser = ParametersParser.new(flat_parameters, check_encoding: false)
18
23
  @deep_object_properties = {}
19
24
  @deep_object_regex = {}
20
25
  @deep_object_parameters.each do |parameter|
@@ -54,16 +59,23 @@ module OpenapiFirst
54
59
  return if unknown.empty?
55
60
 
56
61
  unknown
62
+ rescue Rack::Utils::InvalidParameterError
63
+ nil
57
64
  end
58
65
 
59
66
  private
60
67
 
61
68
  def parse_query(query_string)
62
69
  Rack::Utils.parse_query(query_string) do |string|
63
- Rack::Utils.unescape(string)
70
+ unescaped = Rack::Utils.unescape(string)
71
+ next unescaped if unescaped.valid_encoding?
72
+
73
+ raise Rack::Utils::InvalidParameterError, "invalid encoding (#{string})"
64
74
  rescue ArgumentError => e
65
75
  raise Rack::Utils::InvalidParameterError, e.message
66
76
  end
77
+ rescue BAD_REQUEST => e
78
+ raise Rack::Utils::InvalidParameterError, e.message
67
79
  end
68
80
 
69
81
  def parse_deep_object(parameter, parsed_query)
@@ -61,6 +61,9 @@ module OpenapiFirst
61
61
  query, query_error = parse_query(request.env[Rack::QUERY_STRING])
62
62
  return [nil, query_error] if query_error
63
63
 
64
+ cookies = parse_cookies(request.env[Rack::HTTP_COOKIE])
65
+ return [nil, cookies] if cookies.is_a?(Failure)
66
+
64
67
  body = @body_parsers&.call(request)
65
68
  return [nil, body] if body.is_a?(Failure)
66
69
 
@@ -68,7 +71,7 @@ module OpenapiFirst
68
71
  path: @path_parser&.unpack(route_params),
69
72
  query:,
70
73
  headers: @header_parser&.unpack(RequestHeaders.new(request.env)),
71
- cookies: @cookie_parser&.unpack(Rack::Utils.parse_cookies_header(request.env[Rack::HTTP_COOKIE])),
74
+ cookies:,
72
75
  body:
73
76
  ), nil]
74
77
  end
@@ -79,6 +82,12 @@ module OpenapiFirst
79
82
  [nil, Failure.new(:invalid_query, message: 'Invalid query parameter.')]
80
83
  end
81
84
 
85
+ def parse_cookies(cookie_header)
86
+ @cookie_parser&.unpack(Rack::Utils.parse_cookies_header(cookie_header))
87
+ rescue Rack::Utils::InvalidParameterError
88
+ Failure.new(:invalid_cookie, message: 'Invalid cookie value.')
89
+ end
90
+
82
91
  def build_body_parser(content_type, encoding)
83
92
  RequestBodyParsers[content_type, { encoding: encoding || {} }]
84
93
  end
@@ -40,7 +40,7 @@ module OpenapiFirst
40
40
  def build_headers_parser(headers)
41
41
  return unless headers&.any?
42
42
 
43
- ParametersParser.new(headers.map(&:parameter))
43
+ ParametersParser.new(headers.map(&:parameter), check_encoding: false)
44
44
  end
45
45
  end
46
46
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- VERSION = '4.0.1'
4
+ VERSION = '4.2.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi_first
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Haller