actionpack 6.1.7.5 → 7.0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +323 -399
  3. data/MIT-LICENSE +1 -0
  4. data/README.rdoc +4 -5
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +13 -26
  7. data/lib/abstract_controller/caching/fragments.rb +2 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +21 -7
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +17 -12
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/rendering.rb +9 -11
  16. data/lib/abstract_controller/translation.rb +27 -4
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/action_controller/api.rb +7 -7
  19. data/lib/action_controller/base.rb +5 -4
  20. data/lib/action_controller/form_builder.rb +2 -2
  21. data/lib/action_controller/log_subscriber.rb +4 -3
  22. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  23. data/lib/action_controller/metal/conditional_get.rb +137 -102
  24. data/lib/action_controller/metal/content_security_policy.rb +36 -2
  25. data/lib/action_controller/metal/cookies.rb +1 -1
  26. data/lib/action_controller/metal/data_streaming.rb +23 -31
  27. data/lib/action_controller/metal/etag_with_flash.rb +1 -1
  28. data/lib/action_controller/metal/exceptions.rb +19 -30
  29. data/lib/action_controller/metal/flash.rb +6 -2
  30. data/lib/action_controller/metal/head.rb +1 -1
  31. data/lib/action_controller/metal/helpers.rb +2 -2
  32. data/lib/action_controller/metal/http_authentication.rb +66 -39
  33. data/lib/action_controller/metal/instrumentation.rb +57 -52
  34. data/lib/action_controller/metal/live.rb +43 -2
  35. data/lib/action_controller/metal/mime_responds.rb +3 -3
  36. data/lib/action_controller/metal/params_wrapper.rb +20 -11
  37. data/lib/action_controller/metal/permissions_policy.rb +19 -28
  38. data/lib/action_controller/metal/redirecting.rb +95 -22
  39. data/lib/action_controller/metal/renderers.rb +12 -13
  40. data/lib/action_controller/metal/rendering.rb +121 -9
  41. data/lib/action_controller/metal/request_forgery_protection.rb +83 -32
  42. data/lib/action_controller/metal/rescue.rb +5 -4
  43. data/lib/action_controller/metal/streaming.rb +7 -9
  44. data/lib/action_controller/metal/strong_parameters.rb +138 -115
  45. data/lib/action_controller/metal/testing.rb +9 -2
  46. data/lib/action_controller/metal/url_for.rb +3 -5
  47. data/lib/action_controller/metal.rb +10 -13
  48. data/lib/action_controller/railtie.rb +50 -6
  49. data/lib/action_controller/renderer.rb +1 -20
  50. data/lib/action_controller/test_case.rb +28 -7
  51. data/lib/action_controller.rb +2 -5
  52. data/lib/action_dispatch/http/cache.rb +20 -13
  53. data/lib/action_dispatch/http/content_security_policy.rb +113 -36
  54. data/lib/action_dispatch/http/filter_parameters.rb +4 -19
  55. data/lib/action_dispatch/http/headers.rb +1 -1
  56. data/lib/action_dispatch/http/mime_negotiation.rb +15 -5
  57. data/lib/action_dispatch/http/mime_type.rb +9 -11
  58. data/lib/action_dispatch/http/parameters.rb +5 -5
  59. data/lib/action_dispatch/http/permissions_policy.rb +17 -1
  60. data/lib/action_dispatch/http/request.rb +27 -37
  61. data/lib/action_dispatch/http/response.rb +3 -20
  62. data/lib/action_dispatch/http/upload.rb +13 -2
  63. data/lib/action_dispatch/http/url.rb +11 -19
  64. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  65. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  66. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  67. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  68. data/lib/action_dispatch/journey/path/pattern.rb +22 -13
  69. data/lib/action_dispatch/journey/route.rb +6 -13
  70. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  71. data/lib/action_dispatch/journey/router.rb +1 -1
  72. data/lib/action_dispatch/journey/routes.rb +3 -3
  73. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  74. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  75. data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
  76. data/lib/action_dispatch/middleware/cookies.rb +20 -13
  77. data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
  78. data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
  79. data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
  80. data/lib/action_dispatch/middleware/executor.rb +3 -0
  81. data/lib/action_dispatch/middleware/flash.rb +17 -18
  82. data/lib/action_dispatch/middleware/host_authorization.rb +13 -17
  83. data/lib/action_dispatch/middleware/remote_ip.rb +20 -8
  84. data/lib/action_dispatch/middleware/request_id.rb +3 -3
  85. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
  87. data/lib/action_dispatch/middleware/session/cookie_store.rb +9 -9
  88. data/lib/action_dispatch/middleware/show_exceptions.rb +17 -16
  89. data/lib/action_dispatch/middleware/stack.rb +27 -9
  90. data/lib/action_dispatch/middleware/static.rb +5 -9
  91. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
  92. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  93. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  94. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
  95. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
  96. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
  97. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  98. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
  99. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
  101. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  102. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  103. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
  104. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +22 -22
  105. data/lib/action_dispatch/railtie.rb +8 -2
  106. data/lib/action_dispatch/request/session.rb +43 -13
  107. data/lib/action_dispatch/routing/inspector.rb +1 -1
  108. data/lib/action_dispatch/routing/mapper.rb +82 -83
  109. data/lib/action_dispatch/routing/redirection.rb +5 -2
  110. data/lib/action_dispatch/routing/route_set.rb +17 -7
  111. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  112. data/lib/action_dispatch/routing/url_for.rb +24 -25
  113. data/lib/action_dispatch/routing.rb +5 -6
  114. data/lib/action_dispatch/system_test_case.rb +5 -5
  115. data/lib/action_dispatch/system_testing/browser.rb +3 -13
  116. data/lib/action_dispatch/system_testing/driver.rb +34 -10
  117. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +11 -7
  118. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  119. data/lib/action_dispatch/testing/assertions/response.rb +1 -1
  120. data/lib/action_dispatch/testing/assertions/routing.rb +3 -2
  121. data/lib/action_dispatch/testing/assertions.rb +2 -5
  122. data/lib/action_dispatch/testing/integration.rb +6 -8
  123. data/lib/action_dispatch/testing/test_process.rb +3 -29
  124. data/lib/action_dispatch/testing/test_response.rb +20 -2
  125. data/lib/action_dispatch.rb +1 -0
  126. data/lib/action_pack/gem_version.rb +5 -5
  127. data/lib/action_pack/version.rb +1 -1
  128. metadata +16 -15
@@ -10,11 +10,15 @@ module ActionDispatch
10
10
 
11
11
  attr_reader :memos
12
12
 
13
+ DEFAULT_EXP = /[^.\/?]+/
14
+ DEFAULT_EXP_ANCHORED = /\A#{DEFAULT_EXP}\Z/
15
+
13
16
  def initialize
14
- @regexp_states = {}
15
- @string_states = {}
16
- @accepting = {}
17
- @memos = Hash.new { |h, k| h[k] = [] }
17
+ @stdparam_states = {}
18
+ @regexp_states = {}
19
+ @string_states = {}
20
+ @accepting = {}
21
+ @memos = Hash.new { |h, k| h[k] = [] }
18
22
  end
19
23
 
20
24
  def add_accepting(state)
@@ -41,22 +45,54 @@ module ActionDispatch
41
45
  Array(t)
42
46
  end
43
47
 
44
- def move(t, a)
48
+ def move(t, full_string, start_index, end_index)
45
49
  return [] if t.empty?
46
50
 
47
- regexps = []
48
- strings = []
51
+ next_states = []
49
52
 
50
- t.each { |s|
51
- if states = @regexp_states[s]
52
- states.each { |re, v| regexps << v if re.match?(a) && !v.nil? }
53
+ tok = full_string.slice(start_index, end_index - start_index)
54
+ token_matches_default_component = DEFAULT_EXP_ANCHORED.match?(tok)
55
+
56
+ t.each { |s, previous_start|
57
+ if previous_start.nil?
58
+ # In the simple case of a "default" param regex do this fast-path
59
+ # and add all next states.
60
+ if token_matches_default_component && states = @stdparam_states[s]
61
+ states.each { |re, v| next_states << [v, nil].freeze if !v.nil? }
62
+ end
63
+
64
+ # When we have a literal string, we can just pull the next state
65
+ if states = @string_states[s]
66
+ next_states << [states[tok], nil].freeze unless states[tok].nil?
67
+ end
53
68
  end
54
69
 
55
- if states = @string_states[s]
56
- strings << states[a] unless states[a].nil?
70
+ # For regexes that aren't the "default" style, they may potentially
71
+ # not be terminated by the first "token" [./?], so we need to continue
72
+ # to attempt to match this regexp as well as any successful paths that
73
+ # continue out of it. both paths could be valid.
74
+ if states = @regexp_states[s]
75
+ slice_start = if previous_start.nil?
76
+ start_index
77
+ else
78
+ previous_start
79
+ end
80
+
81
+ slice_length = end_index - slice_start
82
+ curr_slice = full_string.slice(slice_start, slice_length)
83
+
84
+ states.each { |re, v|
85
+ # if we match, we can try moving past this
86
+ next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice)
87
+ }
88
+
89
+ # and regardless, we must continue accepting tokens and retrying this regexp.
90
+ # we need to remember where we started as well so we can take bigger slices.
91
+ next_states << [s, slice_start].freeze
57
92
  end
58
93
  }
59
- strings.concat regexps
94
+
95
+ next_states
60
96
  end
61
97
 
62
98
  def as_json(options = nil)
@@ -69,9 +105,10 @@ module ActionDispatch
69
105
  end
70
106
 
71
107
  {
72
- regexp_states: simple_regexp,
73
- string_states: @string_states,
74
- accepting: @accepting
108
+ regexp_states: simple_regexp,
109
+ string_states: @string_states,
110
+ stdparam_states: @stdparam_states,
111
+ accepting: @accepting
75
112
  }
76
113
  end
77
114
 
@@ -93,7 +130,7 @@ module ActionDispatch
93
130
  states = "function tt() { return #{to_json}; }"
94
131
 
95
132
  fun_routes = paths.sample(3).map do |ast|
96
- ast.map { |n|
133
+ ast.filter_map { |n|
97
134
  case n
98
135
  when Nodes::Symbol
99
136
  case n.left
@@ -106,7 +143,7 @@ module ActionDispatch
106
143
  else
107
144
  nil
108
145
  end
109
- }.compact.join
146
+ }.join
110
147
  end
111
148
 
112
149
  stylesheets = [fsm_css]
@@ -125,18 +162,33 @@ module ActionDispatch
125
162
 
126
163
  def []=(from, to, sym)
127
164
  to_mappings = states_hash_for(sym)[from] ||= {}
165
+ case sym
166
+ when Regexp
167
+ # we must match the whole string to a token boundary
168
+ if sym == DEFAULT_EXP
169
+ sym = DEFAULT_EXP_ANCHORED
170
+ else
171
+ sym = /\A#{sym}\Z/
172
+ end
173
+ when Symbol
174
+ # account for symbols in the constraints the same as strings
175
+ sym = sym.to_s
176
+ end
128
177
  to_mappings[sym] = to
129
178
  end
130
179
 
131
180
  def states
132
181
  ss = @string_states.keys + @string_states.values.flat_map(&:values)
182
+ ps = @stdparam_states.keys + @stdparam_states.values.flat_map(&:values)
133
183
  rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
134
- (ss + rs).uniq
184
+ (ss + ps + rs).uniq
135
185
  end
136
186
 
137
187
  def transitions
138
188
  @string_states.flat_map { |from, hash|
139
189
  hash.map { |s, to| [from, s, to] }
190
+ } + @stdparam_states.flat_map { |from, hash|
191
+ hash.map { |s, to| [from, s, to] }
140
192
  } + @regexp_states.flat_map { |from, hash|
141
193
  hash.map { |s, to| [from, s, to] }
142
194
  }
@@ -145,10 +197,14 @@ module ActionDispatch
145
197
  private
146
198
  def states_hash_for(sym)
147
199
  case sym
148
- when String
200
+ when String, Symbol
149
201
  @string_states
150
202
  when Regexp
151
- @regexp_states
203
+ if sym == DEFAULT_EXP
204
+ @stdparam_states
205
+ else
206
+ @regexp_states
207
+ end
152
208
  else
153
209
  raise ArgumentError, "unknown symbol: %s" % sym.class
154
210
  end
@@ -4,6 +4,66 @@ require "action_dispatch/journey/visitors"
4
4
 
5
5
  module ActionDispatch
6
6
  module Journey # :nodoc:
7
+ class Ast # :nodoc:
8
+ attr_reader :names, :path_params, :tree, :wildcard_options, :terminals
9
+ alias :root :tree
10
+
11
+ def initialize(tree, formatted)
12
+ @tree = tree
13
+ @path_params = []
14
+ @names = []
15
+ @symbols = []
16
+ @stars = []
17
+ @terminals = []
18
+ @wildcard_options = {}
19
+
20
+ visit_tree(formatted)
21
+ end
22
+
23
+ def requirements=(requirements)
24
+ # inject any regexp requirements for `star` nodes so they can be
25
+ # determined nullable, which requires knowing if the regex accepts an
26
+ # empty string.
27
+ (symbols + stars).each do |node|
28
+ re = requirements[node.to_sym]
29
+ node.regexp = re if re
30
+ end
31
+ end
32
+
33
+ def route=(route)
34
+ terminals.each { |n| n.memo = route }
35
+ end
36
+
37
+ def glob?
38
+ stars.any?
39
+ end
40
+
41
+ private
42
+ attr_reader :symbols, :stars
43
+
44
+ def visit_tree(formatted)
45
+ tree.each do |node|
46
+ if node.symbol?
47
+ path_params << node.to_sym
48
+ names << node.name
49
+ symbols << node
50
+ elsif node.star?
51
+ stars << node
52
+
53
+ if formatted != false
54
+ # Add a constraint for wildcard route to make it non-greedy and
55
+ # match the optional format part of the route by default.
56
+ wildcard_options[node.name.to_sym] ||= /.+?/m
57
+ end
58
+ end
59
+
60
+ if node.terminal?
61
+ terminals << node
62
+ end
63
+ end
64
+ end
65
+ end
66
+
7
67
  module Nodes # :nodoc:
8
68
  class Node # :nodoc:
9
69
  include Enumerable
@@ -78,7 +138,7 @@ module ActionDispatch
78
138
  alias :symbol :regexp
79
139
  attr_reader :name
80
140
 
81
- DEFAULT_EXP = /[^\.\/\?]+/
141
+ DEFAULT_EXP = /[^.\/?]+/
82
142
  GREEDY_EXP = /(.+)/
83
143
  def initialize(left, regexp = DEFAULT_EXP)
84
144
  super(left)
@@ -86,10 +146,6 @@ module ActionDispatch
86
146
  @name = -left.tr("*:", "")
87
147
  end
88
148
 
89
- def default_regexp?
90
- regexp == DEFAULT_EXP
91
- end
92
-
93
149
  def type; :SYMBOL; end
94
150
  def symbol?; true; end
95
151
  end
@@ -104,6 +160,15 @@ module ActionDispatch
104
160
  end
105
161
 
106
162
  class Star < Unary # :nodoc:
163
+ attr_accessor :regexp
164
+
165
+ def initialize(left)
166
+ super(left)
167
+
168
+ # By default wildcard routes are non-greedy and must match something.
169
+ @regexp = /.+?/m
170
+ end
171
+
107
172
  def star?; true; end
108
173
  def type; :STAR; end
109
174
 
@@ -4,15 +4,16 @@ module ActionDispatch
4
4
  module Journey # :nodoc:
5
5
  module Path # :nodoc:
6
6
  class Pattern # :nodoc:
7
- attr_reader :spec, :requirements, :anchored
7
+ attr_reader :ast, :names, :requirements, :anchored, :spec
8
8
 
9
9
  def initialize(ast, requirements, separators, anchored)
10
- @spec = ast
10
+ @ast = ast
11
+ @spec = ast.root
11
12
  @requirements = requirements
12
13
  @separators = separators
13
14
  @anchored = anchored
14
15
 
15
- @names = nil
16
+ @names = ast.names
16
17
  @optional_names = nil
17
18
  @required_names = nil
18
19
  @re = nil
@@ -27,20 +28,28 @@ module ActionDispatch
27
28
  required_names
28
29
  offsets
29
30
  to_regexp
30
- nil
31
+ @ast = nil
31
32
  end
32
33
 
33
- def ast
34
- @spec.find_all(&:symbol?).each do |node|
35
- re = @requirements[node.to_sym]
36
- node.regexp = re if re
37
- end
34
+ def requirements_anchored?
35
+ # each required param must not be surrounded by a literal, otherwise it isn't simple to chunk-match the url piecemeal
36
+ terminals = ast.terminals
38
37
 
39
- @spec
40
- end
38
+ terminals.each_with_index { |s, index|
39
+ next if index < 1
40
+ next if s.type == :DOT || s.type == :SLASH
41
+
42
+ back = terminals[index - 1]
43
+ fwd = terminals[index + 1]
44
+
45
+ # we also don't support this yet, constraints must be regexps
46
+ return false if s.symbol? && s.regexp.is_a?(Array)
47
+
48
+ return false if back.literal?
49
+ return false if !fwd.nil? && fwd.literal?
50
+ }
41
51
 
42
- def names
43
- @names ||= spec.find_all(&:symbol?).map(&:name)
52
+ true
44
53
  end
45
54
 
46
55
  def required_names
@@ -5,7 +5,7 @@ module ActionDispatch
5
5
  module Journey
6
6
  class Route
7
7
  attr_reader :app, :path, :defaults, :name, :precedence, :constraints,
8
- :internal, :scope_options
8
+ :internal, :scope_options, :ast
9
9
 
10
10
  alias :conditions :constraints
11
11
 
@@ -65,29 +65,22 @@ module ActionDispatch
65
65
  @_required_defaults = required_defaults
66
66
  @required_parts = nil
67
67
  @parts = nil
68
- @decorated_ast = nil
69
68
  @precedence = precedence
70
69
  @path_formatter = @path.build_formatter
71
70
  @scope_options = scope_options
72
71
  @internal = internal
72
+
73
+ @ast = @path.ast.root
74
+ @path.ast.route = self
73
75
  end
74
76
 
75
77
  def eager_load!
76
78
  path.eager_load!
77
- ast
78
79
  parts
79
80
  required_defaults
80
81
  nil
81
82
  end
82
83
 
83
- def ast
84
- @decorated_ast ||= begin
85
- decorated_ast = path.ast
86
- decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
87
- decorated_ast
88
- end
89
- end
90
-
91
84
  # Needed for `bin/rails routes`. Picks up succinctly defined requirements
92
85
  # for a route, for example route
93
86
  #
@@ -98,7 +91,7 @@ module ActionDispatch
98
91
  # as requirements.
99
92
  def requirements
100
93
  @defaults.merge(path.requirements).delete_if { |_, v|
101
- /.+?/ == v
94
+ /.+?/m == v
102
95
  }
103
96
  end
104
97
 
@@ -142,7 +135,7 @@ module ActionDispatch
142
135
  end
143
136
 
144
137
  def glob?
145
- path.spec.any?(Nodes::Star)
138
+ path.ast.glob?
146
139
  end
147
140
 
148
141
  def dispatcher?
@@ -35,7 +35,7 @@ module ActionDispatch
35
35
  US_ASCII = Encoding::US_ASCII
36
36
  UTF_8 = Encoding::UTF_8
37
37
  EMPTY = (+"").force_encoding(US_ASCII).freeze
38
- DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
38
+ DEC2HEX = (0..255).map { |i| (ENCODE % i).force_encoding(US_ASCII) }
39
39
 
40
40
  ALPHA = "a-zA-Z"
41
41
  DIGIT = "0-9"
@@ -44,7 +44,7 @@ module ActionDispatch
44
44
 
45
45
  ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
46
46
 
47
- FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze
47
+ FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/?]/.freeze
48
48
  SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze
49
49
  PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze
50
50
 
@@ -85,7 +85,7 @@ module ActionDispatch
85
85
  private
86
86
  def partitioned_routes
87
87
  routes.partition { |r|
88
- r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
88
+ r.path.anchored && r.path.requirements_anchored?
89
89
  }
90
90
  end
91
91
 
@@ -41,7 +41,7 @@ module ActionDispatch
41
41
  end
42
42
 
43
43
  def partition_route(route)
44
- if route.path.anchored && route.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
44
+ if route.path.anchored && route.path.requirements_anchored?
45
45
  anchored_routes << route
46
46
  else
47
47
  custom_routes << route
@@ -50,8 +50,8 @@ module ActionDispatch
50
50
 
51
51
  def ast
52
52
  @ast ||= begin
53
- asts = anchored_routes.map(&:ast)
54
- Nodes::Or.new(asts)
53
+ nodes = anchored_routes.map(&:ast)
54
+ Nodes::Or.new(nodes)
55
55
  end
56
56
  end
57
57
 
@@ -68,7 +68,7 @@ function highlight_state(index, color) {
68
68
  }
69
69
 
70
70
  function highlight_finish(index) {
71
- svg_nodes[index].select('polygon')
71
+ svg_nodes[index].select('ellipse')
72
72
  .style("fill", "while")
73
73
  .transition().duration(500)
74
74
  .style("fill", "blue");
@@ -76,54 +76,79 @@ function highlight_finish(index) {
76
76
 
77
77
  function match(input) {
78
78
  reset_graph();
79
- var table = tt();
80
- var states = [0];
81
- var regexp_states = table['regexp_states'];
82
- var string_states = table['string_states'];
83
- var accepting = table['accepting'];
79
+ var table = tt();
80
+ var states = [[0, null]];
81
+ var regexp_states = table['regexp_states'];
82
+ var string_states = table['string_states'];
83
+ var stdparam_states = table['stdparam_states'];
84
+ var accepting = table['accepting'];
85
+ var default_re = new RegExp("^[^.\/?]+$");
86
+ var start_index = 0;
84
87
 
85
88
  highlight_state(0);
86
89
 
87
90
  tokenize(input, function(token) {
91
+ var end_index = start_index + token.length;
92
+
88
93
  var new_states = [];
89
94
  for(var key in states) {
90
- var state = states[key];
95
+ var state_parts = states[key];
96
+ var state = state_parts[0];
97
+ var previous_start = state_parts[1];
98
+
99
+ if(previous_start == null) {
100
+ if(string_states[state] && string_states[state][token]) {
101
+ var new_state = string_states[state][token];
102
+ highlight_edge(state, new_state);
103
+ highlight_state(new_state);
104
+ new_states.push([new_state, null]);
105
+ }
91
106
 
92
- if(string_states[state] && string_states[state][token]) {
93
- var new_state = string_states[state][token];
94
- highlight_edge(state, new_state);
95
- highlight_state(new_state);
96
- new_states.push(new_state);
107
+ if(stdparam_states[state] && default_re.test(token)) {
108
+ for(var key in stdparam_states[state]) {
109
+ var new_state = stdparam_states[state][key];
110
+ highlight_edge(state, new_state);
111
+ highlight_state(new_state);
112
+ new_states.push([new_state, null]);
113
+ }
114
+ }
97
115
  }
98
116
 
99
117
  if(regexp_states[state]) {
118
+ var slice_start = previous_start != null ? previous_start : start_index;
119
+
100
120
  for(var key in regexp_states[state]) {
101
121
  var re = new RegExp("^" + key + "$");
102
- if(re.test(token)) {
122
+
123
+ var accumulation = input.slice(slice_start, end_index);
124
+
125
+ if(re.test(accumulation)) {
103
126
  var new_state = regexp_states[state][key];
104
127
  highlight_edge(state, new_state);
105
128
  highlight_state(new_state);
106
- new_states.push(new_state);
129
+ new_states.push([new_state, null]);
107
130
  }
131
+
132
+ // retry the same regexp with the accumulated data either way
133
+ new_states.push([state, slice_start]);
108
134
  }
109
135
  }
110
136
  }
111
137
 
112
- if(new_states.length == 0) {
113
- return;
114
- }
115
138
  states = new_states;
139
+ start_index = end_index;
116
140
  });
117
141
 
118
142
  for(var key in states) {
119
- var state = states[key];
143
+ var state_parts = states[key];
144
+ var state = state_parts[0];
145
+ var slice_start = state_parts[1];
146
+
147
+ // we must ignore ones that are still accepting more data
148
+ if (slice_start != null) continue;
149
+
120
150
  if(accepting[state]) {
121
- for(var mkey in svg_edges[state]) {
122
- if(!regexp_states[mkey] && !string_states[mkey]) {
123
- highlight_edge(state, mkey);
124
- highlight_finish(mkey);
125
- }
126
- }
151
+ highlight_finish(state);
127
152
  } else {
128
153
  highlight_state(state, "red");
129
154
  }
@@ -8,7 +8,7 @@
8
8
  <%= style %>
9
9
  <% end %>
10
10
  </style>
11
- <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js" type="text/javascript"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"></script>
12
12
  </head>
13
13
  <body>
14
14
  <div id="wrapper">
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "erb"
4
4
  require "uri"
5
- require "action_dispatch/http/request"
6
5
  require "active_support/actionable_error"
7
6
 
8
7
  module ActionDispatch
@@ -7,7 +7,7 @@ require "active_support/json"
7
7
  require "rack/utils"
8
8
 
9
9
  module ActionDispatch
10
- class Request
10
+ module RequestCookieMethods
11
11
  def cookie_jar
12
12
  fetch_header("action_dispatch.cookies") do
13
13
  self.cookie_jar = Cookies::CookieJar.build(self, cookies)
@@ -88,10 +88,14 @@ module ActionDispatch
88
88
  # :startdoc:
89
89
  end
90
90
 
91
- # Read and write data to cookies through ActionController#cookies.
91
+ ActiveSupport.on_load(:action_dispatch_request) do
92
+ include RequestCookieMethods
93
+ end
94
+
95
+ # Read and write data to cookies through ActionController::Base#cookies.
92
96
  #
93
97
  # When reading cookie data, the data is read from the HTTP request header, Cookie.
94
- # When writing cookie data, the data is sent out in the HTTP response header, Set-Cookie.
98
+ # When writing cookie data, the data is sent out in the HTTP response header, +Set-Cookie+.
95
99
  #
96
100
  # Examples of writing:
97
101
  #
@@ -99,7 +103,7 @@ module ActionDispatch
99
103
  # # This cookie will be deleted when the user's browser is closed.
100
104
  # cookies[:user_name] = "david"
101
105
  #
102
- # # Cookie values are String based. Other data types need to be serialized.
106
+ # # Cookie values are String-based. Other data types need to be serialized.
103
107
  # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
104
108
  #
105
109
  # # Sets a cookie that expires in 1 hour.
@@ -135,7 +139,7 @@ module ActionDispatch
135
139
  #
136
140
  # cookies.delete :user_name
137
141
  #
138
- # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
142
+ # Please note that if you specify a +:domain+ when setting a cookie, you must also specify the domain when deleting the cookie:
139
143
  #
140
144
  # cookies[:name] = {
141
145
  # value: 'a yummy cookie',
@@ -172,6 +176,9 @@ module ActionDispatch
172
176
  # Default is +false+.
173
177
  # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
174
178
  # only HTTP. Defaults to +false+.
179
+ # * <tt>:same_site</tt> - The value of the +SameSite+ cookie attribute, which
180
+ # determines how this cookie should be restricted in cross-site contexts.
181
+ # Possible values are +:none+, +:lax+, and +:strict+. Defaults to +:lax+.
175
182
  class Cookies
176
183
  HTTP_HEADER = "Set-Cookie"
177
184
  GENERATOR_KEY = "action_dispatch.key_generator"
@@ -195,7 +202,7 @@ module ActionDispatch
195
202
  # Raised when storing more than 4K of session data.
196
203
  CookieOverflow = Class.new StandardError
197
204
 
198
- # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed.
205
+ # Include in a cookie jar to allow chaining, e.g. +cookies.permanent.signed+.
199
206
  module ChainedCookieJars
200
207
  # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
201
208
  #
@@ -280,7 +287,7 @@ module ActionDispatch
280
287
  end
281
288
  end
282
289
 
283
- class CookieJar #:nodoc:
290
+ class CookieJar # :nodoc:
284
291
  include Enumerable, ChainedCookieJars
285
292
 
286
293
  def self.build(req, cookies)
@@ -421,7 +428,7 @@ module ActionDispatch
421
428
  end
422
429
 
423
430
  def write_cookie?(cookie)
424
- request.ssl? || !cookie[:secure] || always_write_cookie
431
+ request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
425
432
  end
426
433
 
427
434
  def handle_options(options)
@@ -436,7 +443,7 @@ module ActionDispatch
436
443
 
437
444
  if options[:domain] == :all || options[:domain] == "all"
438
445
  cookie_domain = ""
439
- dot_splitted_host = request.host.split('.', -1)
446
+ dot_splitted_host = request.host.split(".", -1)
440
447
 
441
448
  # Case where request.host is not an IP address or it's an invalid domain
442
449
  # (ip confirms to the domain structure we expect so we explicitly check for ip)
@@ -446,10 +453,10 @@ module ActionDispatch
446
453
  end
447
454
 
448
455
  # If there is a provided tld length then we use it otherwise default domain.
449
- if options[:tld_length].present?
456
+ if options[:tld_length].present?
450
457
  # Case where the tld_length provided is valid
451
458
  if dot_splitted_host.length >= options[:tld_length]
452
- cookie_domain = dot_splitted_host.last(options[:tld_length]).join('.')
459
+ cookie_domain = dot_splitted_host.last(options[:tld_length]).join(".")
453
460
  end
454
461
  # Case where tld_length is not provided
455
462
  else
@@ -458,7 +465,7 @@ module ActionDispatch
458
465
  cookie_domain = dot_splitted_host.last(2).join(".")
459
466
  # **.**, ***.** style TLDs like co.uk and com.au
460
467
  else
461
- cookie_domain = dot_splitted_host.last(3).join('.')
468
+ cookie_domain = dot_splitted_host.last(3).join(".")
462
469
  end
463
470
  end
464
471
 
@@ -676,7 +683,7 @@ module ActionDispatch
676
683
  deserialize(name) do |rotate|
677
684
  @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
678
685
  end
679
- rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
686
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature, JSON::ParserError
680
687
  nil
681
688
  end
682
689