actionpack 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +311 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +58 -0
  5. data/lib/abstract_controller.rb +27 -0
  6. data/lib/abstract_controller/asset_paths.rb +12 -0
  7. data/lib/abstract_controller/base.rb +267 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/caching/fragments.rb +150 -0
  10. data/lib/abstract_controller/callbacks.rb +224 -0
  11. data/lib/abstract_controller/collector.rb +43 -0
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +194 -0
  14. data/lib/abstract_controller/logger.rb +14 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
  16. data/lib/abstract_controller/rendering.rb +127 -0
  17. data/lib/abstract_controller/translation.rb +32 -0
  18. data/lib/abstract_controller/url_for.rb +35 -0
  19. data/lib/action_controller.rb +67 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +271 -0
  23. data/lib/action_controller/caching.rb +46 -0
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +81 -0
  26. data/lib/action_controller/metal.rb +256 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +280 -0
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +16 -0
  31. data/lib/action_controller/metal/data_streaming.rb +151 -0
  32. data/lib/action_controller/metal/default_headers.rb +17 -0
  33. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  34. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  35. data/lib/action_controller/metal/exceptions.rb +74 -0
  36. data/lib/action_controller/metal/flash.rb +61 -0
  37. data/lib/action_controller/metal/force_ssl.rb +58 -0
  38. data/lib/action_controller/metal/head.rb +60 -0
  39. data/lib/action_controller/metal/helpers.rb +122 -0
  40. data/lib/action_controller/metal/http_authentication.rb +518 -0
  41. data/lib/action_controller/metal/implicit_render.rb +63 -0
  42. data/lib/action_controller/metal/instrumentation.rb +105 -0
  43. data/lib/action_controller/metal/live.rb +314 -0
  44. data/lib/action_controller/metal/mime_responds.rb +324 -0
  45. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  46. data/lib/action_controller/metal/params_wrapper.rb +297 -0
  47. data/lib/action_controller/metal/redirecting.rb +133 -0
  48. data/lib/action_controller/metal/renderers.rb +181 -0
  49. data/lib/action_controller/metal/rendering.rb +122 -0
  50. data/lib/action_controller/metal/request_forgery_protection.rb +456 -0
  51. data/lib/action_controller/metal/rescue.rb +28 -0
  52. data/lib/action_controller/metal/streaming.rb +223 -0
  53. data/lib/action_controller/metal/strong_parameters.rb +1105 -0
  54. data/lib/action_controller/metal/testing.rb +16 -0
  55. data/lib/action_controller/metal/url_for.rb +58 -0
  56. data/lib/action_controller/railtie.rb +89 -0
  57. data/lib/action_controller/railties/helpers.rb +24 -0
  58. data/lib/action_controller/renderer.rb +130 -0
  59. data/lib/action_controller/template_assertions.rb +11 -0
  60. data/lib/action_controller/test_case.rb +626 -0
  61. data/lib/action_dispatch.rb +114 -0
  62. data/lib/action_dispatch/http/cache.rb +226 -0
  63. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  64. data/lib/action_dispatch/http/content_security_policy.rb +284 -0
  65. data/lib/action_dispatch/http/filter_parameters.rb +86 -0
  66. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  67. data/lib/action_dispatch/http/headers.rb +132 -0
  68. data/lib/action_dispatch/http/mime_negotiation.rb +177 -0
  69. data/lib/action_dispatch/http/mime_type.rb +350 -0
  70. data/lib/action_dispatch/http/mime_types.rb +50 -0
  71. data/lib/action_dispatch/http/parameter_filter.rb +12 -0
  72. data/lib/action_dispatch/http/parameters.rb +136 -0
  73. data/lib/action_dispatch/http/rack_cache.rb +63 -0
  74. data/lib/action_dispatch/http/request.rb +427 -0
  75. data/lib/action_dispatch/http/response.rb +534 -0
  76. data/lib/action_dispatch/http/upload.rb +92 -0
  77. data/lib/action_dispatch/http/url.rb +350 -0
  78. data/lib/action_dispatch/journey.rb +7 -0
  79. data/lib/action_dispatch/journey/formatter.rb +189 -0
  80. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  81. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  82. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  83. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  84. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  85. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  86. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  87. data/lib/action_dispatch/journey/nodes/node.rb +141 -0
  88. data/lib/action_dispatch/journey/parser.rb +199 -0
  89. data/lib/action_dispatch/journey/parser.y +50 -0
  90. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +203 -0
  92. data/lib/action_dispatch/journey/route.rb +204 -0
  93. data/lib/action_dispatch/journey/router.rb +153 -0
  94. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  95. data/lib/action_dispatch/journey/routes.rb +81 -0
  96. data/lib/action_dispatch/journey/scanner.rb +71 -0
  97. data/lib/action_dispatch/journey/visitors.rb +268 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/middleware/actionable_exceptions.rb +39 -0
  102. data/lib/action_dispatch/middleware/callbacks.rb +34 -0
  103. data/lib/action_dispatch/middleware/cookies.rb +663 -0
  104. data/lib/action_dispatch/middleware/debug_exceptions.rb +185 -0
  105. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  106. data/lib/action_dispatch/middleware/debug_view.rb +68 -0
  107. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -0
  108. data/lib/action_dispatch/middleware/executor.rb +21 -0
  109. data/lib/action_dispatch/middleware/flash.rb +300 -0
  110. data/lib/action_dispatch/middleware/host_authorization.rb +103 -0
  111. data/lib/action_dispatch/middleware/public_exceptions.rb +61 -0
  112. data/lib/action_dispatch/middleware/reloader.rb +12 -0
  113. data/lib/action_dispatch/middleware/remote_ip.rb +181 -0
  114. data/lib/action_dispatch/middleware/request_id.rb +43 -0
  115. data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
  116. data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
  117. data/lib/action_dispatch/middleware/session/cookie_store.rb +113 -0
  118. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
  119. data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
  120. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  121. data/lib/action_dispatch/middleware/stack.rb +148 -0
  122. data/lib/action_dispatch/middleware/static.rb +129 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +24 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +29 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +38 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +165 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  148. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  149. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +203 -0
  150. data/lib/action_dispatch/railtie.rb +58 -0
  151. data/lib/action_dispatch/request/session.rb +242 -0
  152. data/lib/action_dispatch/request/utils.rb +78 -0
  153. data/lib/action_dispatch/routing.rb +261 -0
  154. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  155. data/lib/action_dispatch/routing/inspector.rb +274 -0
  156. data/lib/action_dispatch/routing/mapper.rb +2289 -0
  157. data/lib/action_dispatch/routing/polymorphic_routes.rb +351 -0
  158. data/lib/action_dispatch/routing/redirection.rb +201 -0
  159. data/lib/action_dispatch/routing/route_set.rb +887 -0
  160. data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
  161. data/lib/action_dispatch/routing/url_for.rb +237 -0
  162. data/lib/action_dispatch/system_test_case.rb +168 -0
  163. data/lib/action_dispatch/system_testing/browser.rb +80 -0
  164. data/lib/action_dispatch/system_testing/driver.rb +68 -0
  165. data/lib/action_dispatch/system_testing/server.rb +31 -0
  166. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +97 -0
  167. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +33 -0
  168. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  169. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  170. data/lib/action_dispatch/testing/assertions.rb +24 -0
  171. data/lib/action_dispatch/testing/assertions/response.rb +106 -0
  172. data/lib/action_dispatch/testing/assertions/routing.rb +234 -0
  173. data/lib/action_dispatch/testing/integration.rb +659 -0
  174. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  175. data/lib/action_dispatch/testing/test_process.rb +50 -0
  176. data/lib/action_dispatch/testing/test_request.rb +71 -0
  177. data/lib/action_dispatch/testing/test_response.rb +25 -0
  178. data/lib/action_pack.rb +26 -0
  179. data/lib/action_pack/gem_version.rb +17 -0
  180. data/lib/action_pack/version.rb +10 -0
  181. metadata +329 -0
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/journey/router/utils"
4
+ require "action_dispatch/journey/routes"
5
+ require "action_dispatch/journey/formatter"
6
+
7
+ before = $-w
8
+ $-w = false
9
+ require "action_dispatch/journey/parser"
10
+ $-w = before
11
+
12
+ require "action_dispatch/journey/route"
13
+ require "action_dispatch/journey/path/pattern"
14
+
15
+ module ActionDispatch
16
+ module Journey # :nodoc:
17
+ class Router # :nodoc:
18
+ attr_accessor :routes
19
+
20
+ def initialize(routes)
21
+ @routes = routes
22
+ end
23
+
24
+ def eager_load!
25
+ # Eagerly trigger the simulator's initialization so
26
+ # it doesn't happen during a request cycle.
27
+ simulator
28
+ nil
29
+ end
30
+
31
+ def serve(req)
32
+ find_routes(req).each do |match, parameters, route|
33
+ set_params = req.path_parameters
34
+ path_info = req.path_info
35
+ script_name = req.script_name
36
+
37
+ unless route.path.anchored
38
+ req.script_name = (script_name.to_s + match.to_s).chomp("/")
39
+ req.path_info = match.post_match
40
+ req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
41
+ end
42
+
43
+ parameters = route.defaults.merge parameters.transform_values { |val|
44
+ val.dup.force_encoding(::Encoding::UTF_8)
45
+ }
46
+
47
+ req.path_parameters = set_params.merge parameters
48
+
49
+ status, headers, body = route.app.serve(req)
50
+
51
+ if "pass" == headers["X-Cascade"]
52
+ req.script_name = script_name
53
+ req.path_info = path_info
54
+ req.path_parameters = set_params
55
+ next
56
+ end
57
+
58
+ return [status, headers, body]
59
+ end
60
+
61
+ [404, { "X-Cascade" => "pass" }, ["Not Found"]]
62
+ end
63
+
64
+ def recognize(rails_req)
65
+ find_routes(rails_req).each do |match, parameters, route|
66
+ unless route.path.anchored
67
+ rails_req.script_name = match.to_s
68
+ rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1')
69
+ end
70
+
71
+ parameters = route.defaults.merge parameters
72
+ yield(route, parameters)
73
+ end
74
+ end
75
+
76
+ def visualizer
77
+ tt = GTG::Builder.new(ast).transition_table
78
+ groups = partitioned_routes.first.map(&:ast).group_by(&:to_s)
79
+ asts = groups.values.map(&:first)
80
+ tt.visualizer(asts)
81
+ end
82
+
83
+ private
84
+
85
+ def partitioned_routes
86
+ routes.partition { |r|
87
+ r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
88
+ }
89
+ end
90
+
91
+ def ast
92
+ routes.ast
93
+ end
94
+
95
+ def simulator
96
+ routes.simulator
97
+ end
98
+
99
+ def custom_routes
100
+ routes.custom_routes
101
+ end
102
+
103
+ def filter_routes(path)
104
+ return [] unless ast
105
+ simulator.memos(path) { [] }
106
+ end
107
+
108
+ def find_routes(req)
109
+ routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
110
+ r.path.match(req.path_info)
111
+ }
112
+
113
+ routes =
114
+ if req.head?
115
+ match_head_routes(routes, req)
116
+ else
117
+ match_routes(routes, req)
118
+ end
119
+
120
+ routes.sort_by!(&:precedence)
121
+
122
+ routes.map! { |r|
123
+ match_data = r.path.match(req.path_info)
124
+ path_parameters = {}
125
+ match_data.names.zip(match_data.captures) { |name, val|
126
+ path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
127
+ }
128
+ [match_data, path_parameters, r]
129
+ }
130
+ end
131
+
132
+ def match_head_routes(routes, req)
133
+ verb_specific_routes = routes.select(&:requires_matching_verb?)
134
+ head_routes = match_routes(verb_specific_routes, req)
135
+
136
+ if head_routes.empty?
137
+ begin
138
+ req.request_method = "GET"
139
+ match_routes(routes, req)
140
+ ensure
141
+ req.request_method = "HEAD"
142
+ end
143
+ else
144
+ head_routes
145
+ end
146
+ end
147
+
148
+ def match_routes(routes, req)
149
+ routes.select { |r| r.matches?(req) }
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Journey # :nodoc:
5
+ class Router # :nodoc:
6
+ class Utils # :nodoc:
7
+ # Normalizes URI path.
8
+ #
9
+ # Strips off trailing slash and ensures there is a leading slash.
10
+ # Also converts downcase URL encoded string to uppercase.
11
+ #
12
+ # normalize_path("/foo") # => "/foo"
13
+ # normalize_path("/foo/") # => "/foo"
14
+ # normalize_path("foo") # => "/foo"
15
+ # normalize_path("") # => "/"
16
+ # normalize_path("/%ab") # => "/%AB"
17
+ def self.normalize_path(path)
18
+ path ||= ""
19
+ encoding = path.encoding
20
+ path = +"/#{path}"
21
+ path.squeeze!("/")
22
+ path.sub!(%r{/+\Z}, "")
23
+ path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
24
+ path = +"/" if path == ""
25
+ path.force_encoding(encoding)
26
+ path
27
+ end
28
+
29
+ # URI path and fragment escaping
30
+ # https://tools.ietf.org/html/rfc3986
31
+ class UriEncoder # :nodoc:
32
+ ENCODE = "%%%02X"
33
+ US_ASCII = Encoding::US_ASCII
34
+ UTF_8 = Encoding::UTF_8
35
+ EMPTY = (+"").force_encoding(US_ASCII).freeze
36
+ DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
37
+
38
+ ALPHA = "a-zA-Z"
39
+ DIGIT = "0-9"
40
+ UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~"
41
+ SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;="
42
+
43
+ ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
44
+
45
+ FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze
46
+ SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze
47
+ PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze
48
+
49
+ def escape_fragment(fragment)
50
+ escape(fragment, FRAGMENT)
51
+ end
52
+
53
+ def escape_path(path)
54
+ escape(path, PATH)
55
+ end
56
+
57
+ def escape_segment(segment)
58
+ escape(segment, SEGMENT)
59
+ end
60
+
61
+ def unescape_uri(uri)
62
+ encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
63
+ uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding)
64
+ end
65
+
66
+ private
67
+ def escape(component, pattern)
68
+ component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
69
+ end
70
+
71
+ def percent_encode(unsafe)
72
+ safe = EMPTY.dup
73
+ unsafe.each_byte { |b| safe << DEC2HEX[b] }
74
+ safe
75
+ end
76
+ end
77
+
78
+ ENCODER = UriEncoder.new
79
+
80
+ def self.escape_path(path)
81
+ ENCODER.escape_path(path.to_s)
82
+ end
83
+
84
+ def self.escape_segment(segment)
85
+ ENCODER.escape_segment(segment.to_s)
86
+ end
87
+
88
+ def self.escape_fragment(fragment)
89
+ ENCODER.escape_fragment(fragment.to_s)
90
+ end
91
+
92
+ # Replaces any escaped sequences with their unescaped representations.
93
+ #
94
+ # uri = "/topics?title=Ruby%20on%20Rails"
95
+ # unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
96
+ def self.unescape_uri(uri)
97
+ ENCODER.unescape_uri(uri)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Journey # :nodoc:
5
+ # The Routing table. Contains all routes for a system. Routes can be
6
+ # added to the table by calling Routes#add_route.
7
+ class Routes # :nodoc:
8
+ include Enumerable
9
+
10
+ attr_reader :routes, :custom_routes, :anchored_routes
11
+
12
+ def initialize
13
+ @routes = []
14
+ @ast = nil
15
+ @anchored_routes = []
16
+ @custom_routes = []
17
+ @simulator = nil
18
+ end
19
+
20
+ def empty?
21
+ routes.empty?
22
+ end
23
+
24
+ def length
25
+ routes.length
26
+ end
27
+ alias :size :length
28
+
29
+ def last
30
+ routes.last
31
+ end
32
+
33
+ def each(&block)
34
+ routes.each(&block)
35
+ end
36
+
37
+ def clear
38
+ routes.clear
39
+ anchored_routes.clear
40
+ custom_routes.clear
41
+ end
42
+
43
+ def partition_route(route)
44
+ if route.path.anchored && route.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
45
+ anchored_routes << route
46
+ else
47
+ custom_routes << route
48
+ end
49
+ end
50
+
51
+ def ast
52
+ @ast ||= begin
53
+ asts = anchored_routes.map(&:ast)
54
+ Nodes::Or.new(asts)
55
+ end
56
+ end
57
+
58
+ def simulator
59
+ @simulator ||= begin
60
+ gtg = GTG::Builder.new(ast).transition_table
61
+ GTG::Simulator.new(gtg)
62
+ end
63
+ end
64
+
65
+ def add_route(name, mapping)
66
+ route = mapping.make_route name, routes.length
67
+ routes << route
68
+ partition_route(route)
69
+ clear_cache!
70
+ route
71
+ end
72
+
73
+ private
74
+
75
+ def clear_cache!
76
+ @ast = nil
77
+ @simulator = nil
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module ActionDispatch
6
+ module Journey # :nodoc:
7
+ class Scanner # :nodoc:
8
+ def initialize
9
+ @ss = nil
10
+ end
11
+
12
+ def scan_setup(str)
13
+ @ss = StringScanner.new(str)
14
+ end
15
+
16
+ def eos?
17
+ @ss.eos?
18
+ end
19
+
20
+ def pos
21
+ @ss.pos
22
+ end
23
+
24
+ def pre_match
25
+ @ss.pre_match
26
+ end
27
+
28
+ def next_token
29
+ return if @ss.eos?
30
+
31
+ until token = scan || @ss.eos?; end
32
+ token
33
+ end
34
+
35
+ private
36
+
37
+ # takes advantage of String @- deduping capabilities in Ruby 2.5 upwards
38
+ # see: https://bugs.ruby-lang.org/issues/13077
39
+ def dedup_scan(regex)
40
+ r = @ss.scan(regex)
41
+ r ? -r : nil
42
+ end
43
+
44
+ def scan
45
+ case
46
+ # /
47
+ when @ss.skip(/\//)
48
+ [:SLASH, "/"]
49
+ when @ss.skip(/\(/)
50
+ [:LPAREN, "("]
51
+ when @ss.skip(/\)/)
52
+ [:RPAREN, ")"]
53
+ when @ss.skip(/\|/)
54
+ [:OR, "|"]
55
+ when @ss.skip(/\./)
56
+ [:DOT, "."]
57
+ when text = dedup_scan(/:\w+/)
58
+ [:SYMBOL, text]
59
+ when text = dedup_scan(/\*\w+/)
60
+ [:STAR, text]
61
+ when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
62
+ text.tr! "\\", ""
63
+ [:LITERAL, -text]
64
+ # any char
65
+ when text = dedup_scan(/./)
66
+ [:LITERAL, text]
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ # :stopdoc:
5
+ module Journey
6
+ class Format
7
+ ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) }
8
+ ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
9
+
10
+ Parameter = Struct.new(:name, :escaper) do
11
+ def escape(value); escaper.call value; end
12
+ end
13
+
14
+ def self.required_path(symbol)
15
+ Parameter.new symbol, ESCAPE_PATH
16
+ end
17
+
18
+ def self.required_segment(symbol)
19
+ Parameter.new symbol, ESCAPE_SEGMENT
20
+ end
21
+
22
+ def initialize(parts)
23
+ @parts = parts
24
+ @children = []
25
+ @parameters = []
26
+
27
+ parts.each_with_index do |object, i|
28
+ case object
29
+ when Journey::Format
30
+ @children << i
31
+ when Parameter
32
+ @parameters << i
33
+ end
34
+ end
35
+ end
36
+
37
+ def evaluate(hash)
38
+ parts = @parts.dup
39
+
40
+ @parameters.each do |index|
41
+ param = parts[index]
42
+ value = hash[param.name]
43
+ return "" unless value
44
+ parts[index] = param.escape value
45
+ end
46
+
47
+ @children.each { |index| parts[index] = parts[index].evaluate(hash) }
48
+
49
+ parts.join
50
+ end
51
+ end
52
+
53
+ module Visitors # :nodoc:
54
+ class Visitor # :nodoc:
55
+ DISPATCH_CACHE = {}
56
+
57
+ def accept(node)
58
+ visit(node)
59
+ end
60
+
61
+ private
62
+
63
+ def visit(node)
64
+ send(DISPATCH_CACHE[node.type], node)
65
+ end
66
+
67
+ def binary(node)
68
+ visit(node.left)
69
+ visit(node.right)
70
+ end
71
+ def visit_CAT(n); binary(n); end
72
+
73
+ def nary(node)
74
+ node.children.each { |c| visit(c) }
75
+ end
76
+ def visit_OR(n); nary(n); end
77
+
78
+ def unary(node)
79
+ visit(node.left)
80
+ end
81
+ def visit_GROUP(n); unary(n); end
82
+ def visit_STAR(n); unary(n); end
83
+
84
+ def terminal(node); end
85
+ def visit_LITERAL(n); terminal(n); end
86
+ def visit_SYMBOL(n); terminal(n); end
87
+ def visit_SLASH(n); terminal(n); end
88
+ def visit_DOT(n); terminal(n); end
89
+
90
+ private_instance_methods(false).each do |pim|
91
+ next unless pim =~ /^visit_(.*)$/
92
+ DISPATCH_CACHE[$1.to_sym] = pim
93
+ end
94
+ end
95
+
96
+ class FunctionalVisitor # :nodoc:
97
+ DISPATCH_CACHE = {}
98
+
99
+ def accept(node, seed)
100
+ visit(node, seed)
101
+ end
102
+
103
+ def visit(node, seed)
104
+ send(DISPATCH_CACHE[node.type], node, seed)
105
+ end
106
+
107
+ def binary(node, seed)
108
+ visit(node.right, visit(node.left, seed))
109
+ end
110
+ def visit_CAT(n, seed); binary(n, seed); end
111
+
112
+ def nary(node, seed)
113
+ node.children.inject(seed) { |s, c| visit(c, s) }
114
+ end
115
+ def visit_OR(n, seed); nary(n, seed); end
116
+
117
+ def unary(node, seed)
118
+ visit(node.left, seed)
119
+ end
120
+ def visit_GROUP(n, seed); unary(n, seed); end
121
+ def visit_STAR(n, seed); unary(n, seed); end
122
+
123
+ def terminal(node, seed); seed; end
124
+ def visit_LITERAL(n, seed); terminal(n, seed); end
125
+ def visit_SYMBOL(n, seed); terminal(n, seed); end
126
+ def visit_SLASH(n, seed); terminal(n, seed); end
127
+ def visit_DOT(n, seed); terminal(n, seed); end
128
+
129
+ instance_methods(false).each do |pim|
130
+ next unless pim =~ /^visit_(.*)$/
131
+ DISPATCH_CACHE[$1.to_sym] = pim
132
+ end
133
+ end
134
+
135
+ class FormatBuilder < Visitor # :nodoc:
136
+ def accept(node); Journey::Format.new(super); end
137
+ def terminal(node); [node.left]; end
138
+
139
+ def binary(node)
140
+ visit(node.left) + visit(node.right)
141
+ end
142
+
143
+ def visit_GROUP(n); [Journey::Format.new(unary(n))]; end
144
+
145
+ def visit_STAR(n)
146
+ [Journey::Format.required_path(n.left.to_sym)]
147
+ end
148
+
149
+ def visit_SYMBOL(n)
150
+ symbol = n.to_sym
151
+ if symbol == :controller
152
+ [Journey::Format.required_path(symbol)]
153
+ else
154
+ [Journey::Format.required_segment(symbol)]
155
+ end
156
+ end
157
+ end
158
+
159
+ # Loop through the requirements AST.
160
+ class Each < FunctionalVisitor # :nodoc:
161
+ def visit(node, block)
162
+ block.call(node)
163
+ super
164
+ end
165
+
166
+ INSTANCE = new
167
+ end
168
+
169
+ class String < FunctionalVisitor # :nodoc:
170
+ private
171
+
172
+ def binary(node, seed)
173
+ visit(node.right, visit(node.left, seed))
174
+ end
175
+
176
+ def nary(node, seed)
177
+ last_child = node.children.last
178
+ node.children.inject(seed) { |s, c|
179
+ string = visit(c, s)
180
+ string << "|" unless last_child == c
181
+ string
182
+ }
183
+ end
184
+
185
+ def terminal(node, seed)
186
+ seed + node.left
187
+ end
188
+
189
+ def visit_GROUP(node, seed)
190
+ visit(node.left, seed.dup << "(") << ")"
191
+ end
192
+
193
+ INSTANCE = new
194
+ end
195
+
196
+ class Dot < FunctionalVisitor # :nodoc:
197
+ def initialize
198
+ @nodes = []
199
+ @edges = []
200
+ end
201
+
202
+ def accept(node, seed = [[], []])
203
+ super
204
+ nodes, edges = seed
205
+ <<-eodot
206
+ digraph parse_tree {
207
+ size="8,5"
208
+ node [shape = none];
209
+ edge [dir = none];
210
+ #{nodes.join "\n"}
211
+ #{edges.join("\n")}
212
+ }
213
+ eodot
214
+ end
215
+
216
+ private
217
+
218
+ def binary(node, seed)
219
+ seed.last.concat node.children.map { |c|
220
+ "#{node.object_id} -> #{c.object_id};"
221
+ }
222
+ super
223
+ end
224
+
225
+ def nary(node, seed)
226
+ seed.last.concat node.children.map { |c|
227
+ "#{node.object_id} -> #{c.object_id};"
228
+ }
229
+ super
230
+ end
231
+
232
+ def unary(node, seed)
233
+ seed.last << "#{node.object_id} -> #{node.left.object_id};"
234
+ super
235
+ end
236
+
237
+ def visit_GROUP(node, seed)
238
+ seed.first << "#{node.object_id} [label=\"()\"];"
239
+ super
240
+ end
241
+
242
+ def visit_CAT(node, seed)
243
+ seed.first << "#{node.object_id} [label=\"○\"];"
244
+ super
245
+ end
246
+
247
+ def visit_STAR(node, seed)
248
+ seed.first << "#{node.object_id} [label=\"*\"];"
249
+ super
250
+ end
251
+
252
+ def visit_OR(node, seed)
253
+ seed.first << "#{node.object_id} [label=\"|\"];"
254
+ super
255
+ end
256
+
257
+ def terminal(node, seed)
258
+ value = node.left
259
+
260
+ seed.first << "#{node.object_id} [label=\"#{value}\"];"
261
+ seed
262
+ end
263
+ INSTANCE = new
264
+ end
265
+ end
266
+ end
267
+ # :startdoc:
268
+ end