ultra-max-sys 0.0.1

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.

Potentially problematic release.


This version of ultra-max-sys might be problematic. Click here for more details.

Files changed (163) hide show
  1. checksums.yaml +7 -0
  2. data/roda-3.106.0/MIT-LICENSE +21 -0
  3. data/roda-3.106.0/lib/roda/cache.rb +42 -0
  4. data/roda-3.106.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
  5. data/roda-3.106.0/lib/roda/plugins/_after_hook.rb +11 -0
  6. data/roda-3.106.0/lib/roda/plugins/_base64.rb +34 -0
  7. data/roda-3.106.0/lib/roda/plugins/_before_hook.rb +41 -0
  8. data/roda-3.106.0/lib/roda/plugins/_optimized_matching.rb +227 -0
  9. data/roda-3.106.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
  10. data/roda-3.106.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
  11. data/roda-3.106.0/lib/roda/plugins/additional_render_engines.rb +61 -0
  12. data/roda-3.106.0/lib/roda/plugins/additional_view_directories.rb +66 -0
  13. data/roda-3.106.0/lib/roda/plugins/all_verbs.rb +50 -0
  14. data/roda-3.106.0/lib/roda/plugins/assets.rb +902 -0
  15. data/roda-3.106.0/lib/roda/plugins/assets_preloading.rb +89 -0
  16. data/roda-3.106.0/lib/roda/plugins/assume_ssl.rb +28 -0
  17. data/roda-3.106.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
  18. data/roda-3.106.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
  19. data/roda-3.106.0/lib/roda/plugins/backtracking_array.rb +88 -0
  20. data/roda-3.106.0/lib/roda/plugins/bearer_token.rb +28 -0
  21. data/roda-3.106.0/lib/roda/plugins/branch_locals.rb +76 -0
  22. data/roda-3.106.0/lib/roda/plugins/break.rb +43 -0
  23. data/roda-3.106.0/lib/roda/plugins/caching.rb +206 -0
  24. data/roda-3.106.0/lib/roda/plugins/capture_erb.rb +94 -0
  25. data/roda-3.106.0/lib/roda/plugins/chunked.rb +333 -0
  26. data/roda-3.106.0/lib/roda/plugins/class_level_routing.rb +111 -0
  27. data/roda-3.106.0/lib/roda/plugins/class_matchers.rb +155 -0
  28. data/roda-3.106.0/lib/roda/plugins/common_logger.rb +82 -0
  29. data/roda-3.106.0/lib/roda/plugins/conditional_sessions.rb +67 -0
  30. data/roda-3.106.0/lib/roda/plugins/content_for.rb +105 -0
  31. data/roda-3.106.0/lib/roda/plugins/content_security_policy.rb +333 -0
  32. data/roda-3.106.0/lib/roda/plugins/cookie_flags.rb +157 -0
  33. data/roda-3.106.0/lib/roda/plugins/cookies.rb +51 -0
  34. data/roda-3.106.0/lib/roda/plugins/csrf.rb +81 -0
  35. data/roda-3.106.0/lib/roda/plugins/custom_block_results.rb +85 -0
  36. data/roda-3.106.0/lib/roda/plugins/custom_matchers.rb +89 -0
  37. data/roda-3.106.0/lib/roda/plugins/default_headers.rb +64 -0
  38. data/roda-3.106.0/lib/roda/plugins/default_status.rb +35 -0
  39. data/roda-3.106.0/lib/roda/plugins/delay_build.rb +18 -0
  40. data/roda-3.106.0/lib/roda/plugins/delegate.rb +77 -0
  41. data/roda-3.106.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
  42. data/roda-3.106.0/lib/roda/plugins/direct_call.rb +38 -0
  43. data/roda-3.106.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
  44. data/roda-3.106.0/lib/roda/plugins/drop_body.rb +43 -0
  45. data/roda-3.106.0/lib/roda/plugins/each_part.rb +87 -0
  46. data/roda-3.106.0/lib/roda/plugins/early_hints.rb +25 -0
  47. data/roda-3.106.0/lib/roda/plugins/empty_root.rb +49 -0
  48. data/roda-3.106.0/lib/roda/plugins/environments.rb +79 -0
  49. data/roda-3.106.0/lib/roda/plugins/erb_h.rb +43 -0
  50. data/roda-3.106.0/lib/roda/plugins/error_email.rb +152 -0
  51. data/roda-3.106.0/lib/roda/plugins/error_handler.rb +133 -0
  52. data/roda-3.106.0/lib/roda/plugins/error_mail.rb +149 -0
  53. data/roda-3.106.0/lib/roda/plugins/exception_page.rb +445 -0
  54. data/roda-3.106.0/lib/roda/plugins/filter_common_logger.rb +46 -0
  55. data/roda-3.106.0/lib/roda/plugins/flash.rb +119 -0
  56. data/roda-3.106.0/lib/roda/plugins/h.rb +59 -0
  57. data/roda-3.106.0/lib/roda/plugins/halt.rb +108 -0
  58. data/roda-3.106.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
  59. data/roda-3.106.0/lib/roda/plugins/hash_branches.rb +145 -0
  60. data/roda-3.106.0/lib/roda/plugins/hash_matcher.rb +39 -0
  61. data/roda-3.106.0/lib/roda/plugins/hash_paths.rb +128 -0
  62. data/roda-3.106.0/lib/roda/plugins/hash_public.rb +125 -0
  63. data/roda-3.106.0/lib/roda/plugins/hash_public_cache.rb +91 -0
  64. data/roda-3.106.0/lib/roda/plugins/hash_routes.rb +304 -0
  65. data/roda-3.106.0/lib/roda/plugins/head.rb +99 -0
  66. data/roda-3.106.0/lib/roda/plugins/header_matchers.rb +88 -0
  67. data/roda-3.106.0/lib/roda/plugins/heartbeat.rb +41 -0
  68. data/roda-3.106.0/lib/roda/plugins/hmac_paths.rb +413 -0
  69. data/roda-3.106.0/lib/roda/plugins/hooks.rb +92 -0
  70. data/roda-3.106.0/lib/roda/plugins/host_authorization.rb +155 -0
  71. data/roda-3.106.0/lib/roda/plugins/host_routing.rb +194 -0
  72. data/roda-3.106.0/lib/roda/plugins/hsts.rb +35 -0
  73. data/roda-3.106.0/lib/roda/plugins/indifferent_params.rb +126 -0
  74. data/roda-3.106.0/lib/roda/plugins/inject_erb.rb +33 -0
  75. data/roda-3.106.0/lib/roda/plugins/invalid_request_body.rb +107 -0
  76. data/roda-3.106.0/lib/roda/plugins/ip_from_header.rb +34 -0
  77. data/roda-3.106.0/lib/roda/plugins/json.rb +119 -0
  78. data/roda-3.106.0/lib/roda/plugins/json_parser.rb +107 -0
  79. data/roda-3.106.0/lib/roda/plugins/link_to.rb +83 -0
  80. data/roda-3.106.0/lib/roda/plugins/mail_processor.rb +629 -0
  81. data/roda-3.106.0/lib/roda/plugins/mailer.rb +285 -0
  82. data/roda-3.106.0/lib/roda/plugins/map_matcher.rb +48 -0
  83. data/roda-3.106.0/lib/roda/plugins/match_affix.rb +64 -0
  84. data/roda-3.106.0/lib/roda/plugins/match_hook.rb +34 -0
  85. data/roda-3.106.0/lib/roda/plugins/match_hook_args.rb +93 -0
  86. data/roda-3.106.0/lib/roda/plugins/middleware.rb +225 -0
  87. data/roda-3.106.0/lib/roda/plugins/middleware_stack.rb +101 -0
  88. data/roda-3.106.0/lib/roda/plugins/module_include.rb +96 -0
  89. data/roda-3.106.0/lib/roda/plugins/multi_public.rb +95 -0
  90. data/roda-3.106.0/lib/roda/plugins/multi_route.rb +156 -0
  91. data/roda-3.106.0/lib/roda/plugins/multi_run.rb +144 -0
  92. data/roda-3.106.0/lib/roda/plugins/multi_view.rb +83 -0
  93. data/roda-3.106.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
  94. data/roda-3.106.0/lib/roda/plugins/named_routes.rb +167 -0
  95. data/roda-3.106.0/lib/roda/plugins/named_templates.rb +102 -0
  96. data/roda-3.106.0/lib/roda/plugins/not_allowed.rb +151 -0
  97. data/roda-3.106.0/lib/roda/plugins/not_found.rb +55 -0
  98. data/roda-3.106.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
  99. data/roda-3.106.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
  100. data/roda-3.106.0/lib/roda/plugins/padrino_render.rb +42 -0
  101. data/roda-3.106.0/lib/roda/plugins/param_matchers.rb +83 -0
  102. data/roda-3.106.0/lib/roda/plugins/params_capturing.rb +118 -0
  103. data/roda-3.106.0/lib/roda/plugins/part.rb +76 -0
  104. data/roda-3.106.0/lib/roda/plugins/partials.rb +74 -0
  105. data/roda-3.106.0/lib/roda/plugins/pass.rb +47 -0
  106. data/roda-3.106.0/lib/roda/plugins/path.rb +255 -0
  107. data/roda-3.106.0/lib/roda/plugins/path_matchers.rb +54 -0
  108. data/roda-3.106.0/lib/roda/plugins/path_rewriter.rb +111 -0
  109. data/roda-3.106.0/lib/roda/plugins/permissions_policy.rb +340 -0
  110. data/roda-3.106.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
  111. data/roda-3.106.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
  112. data/roda-3.106.0/lib/roda/plugins/precompile_templates.rb +156 -0
  113. data/roda-3.106.0/lib/roda/plugins/public.rb +182 -0
  114. data/roda-3.106.0/lib/roda/plugins/r.rb +35 -0
  115. data/roda-3.106.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
  116. data/roda-3.106.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
  117. data/roda-3.106.0/lib/roda/plugins/redirect_path.rb +59 -0
  118. data/roda-3.106.0/lib/roda/plugins/relative_path.rb +76 -0
  119. data/roda-3.106.0/lib/roda/plugins/render.rb +998 -0
  120. data/roda-3.106.0/lib/roda/plugins/render_coverage.rb +105 -0
  121. data/roda-3.106.0/lib/roda/plugins/render_each.rb +224 -0
  122. data/roda-3.106.0/lib/roda/plugins/render_locals.rb +102 -0
  123. data/roda-3.106.0/lib/roda/plugins/request_aref.rb +77 -0
  124. data/roda-3.106.0/lib/roda/plugins/request_headers.rb +83 -0
  125. data/roda-3.106.0/lib/roda/plugins/response_attachment.rb +94 -0
  126. data/roda-3.106.0/lib/roda/plugins/response_content_type.rb +79 -0
  127. data/roda-3.106.0/lib/roda/plugins/response_request.rb +36 -0
  128. data/roda-3.106.0/lib/roda/plugins/route_block_args.rb +50 -0
  129. data/roda-3.106.0/lib/roda/plugins/route_csrf.rb +388 -0
  130. data/roda-3.106.0/lib/roda/plugins/run_append_slash.rb +51 -0
  131. data/roda-3.106.0/lib/roda/plugins/run_handler.rb +51 -0
  132. data/roda-3.106.0/lib/roda/plugins/run_require_slash.rb +46 -0
  133. data/roda-3.106.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
  134. data/roda-3.106.0/lib/roda/plugins/send_file.rb +127 -0
  135. data/roda-3.106.0/lib/roda/plugins/sessions.rb +537 -0
  136. data/roda-3.106.0/lib/roda/plugins/shape_friendly.rb +232 -0
  137. data/roda-3.106.0/lib/roda/plugins/shared_vars.rb +86 -0
  138. data/roda-3.106.0/lib/roda/plugins/sinatra_helpers.rb +444 -0
  139. data/roda-3.106.0/lib/roda/plugins/slash_path_empty.rb +26 -0
  140. data/roda-3.106.0/lib/roda/plugins/static.rb +42 -0
  141. data/roda-3.106.0/lib/roda/plugins/static_routing.rb +89 -0
  142. data/roda-3.106.0/lib/roda/plugins/status_303.rb +34 -0
  143. data/roda-3.106.0/lib/roda/plugins/status_handler.rb +86 -0
  144. data/roda-3.106.0/lib/roda/plugins/streaming.rb +170 -0
  145. data/roda-3.106.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
  146. data/roda-3.106.0/lib/roda/plugins/symbol_matchers.rb +188 -0
  147. data/roda-3.106.0/lib/roda/plugins/symbol_status.rb +32 -0
  148. data/roda-3.106.0/lib/roda/plugins/symbol_views.rb +37 -0
  149. data/roda-3.106.0/lib/roda/plugins/timestamp_public.rb +78 -0
  150. data/roda-3.106.0/lib/roda/plugins/type_routing.rb +217 -0
  151. data/roda-3.106.0/lib/roda/plugins/typecast_params.rb +1185 -0
  152. data/roda-3.106.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
  153. data/roda-3.106.0/lib/roda/plugins/unescape_path.rb +41 -0
  154. data/roda-3.106.0/lib/roda/plugins/view_options.rb +203 -0
  155. data/roda-3.106.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
  156. data/roda-3.106.0/lib/roda/plugins.rb +68 -0
  157. data/roda-3.106.0/lib/roda/request.rb +722 -0
  158. data/roda-3.106.0/lib/roda/response.rb +221 -0
  159. data/roda-3.106.0/lib/roda/session_middleware.rb +184 -0
  160. data/roda-3.106.0/lib/roda/version.rb +18 -0
  161. data/roda-3.106.0/lib/roda.rb +741 -0
  162. data/ultra-max-sys.gemspec +12 -0
  163. metadata +202 -0
@@ -0,0 +1,722 @@
1
+ # frozen-string-literal: true
2
+
3
+ begin
4
+ require "rack/version"
5
+ rescue LoadError
6
+ require "rack"
7
+ else
8
+ if Rack.release >= '3'
9
+ require "rack/request"
10
+ else
11
+ require "rack"
12
+ end
13
+ end
14
+
15
+ require 'set'
16
+ require_relative "cache"
17
+
18
+ class Roda
19
+ # Base class used for Roda requests. The instance methods for this
20
+ # class are added by Roda::RodaPlugins::Base::RequestMethods, the
21
+ # class methods are added by Roda::RodaPlugins::Base::RequestClassMethods.
22
+ class RodaRequest < ::Rack::Request
23
+ @roda_class = ::Roda
24
+ @match_pattern_cache = ::Roda::RodaCache.new
25
+ end
26
+
27
+ module RodaPlugins
28
+ module Base
29
+ # Class methods for RodaRequest
30
+ module RequestClassMethods
31
+ # Reference to the Roda class related to this request class.
32
+ attr_accessor :roda_class
33
+
34
+ # The cache to use for match patterns for this request class.
35
+ attr_accessor :match_pattern_cache
36
+
37
+ # Return the cached pattern for the given object. If the object is
38
+ # not already cached, yield to get the basic pattern, and convert the
39
+ # basic pattern to a pattern that does not match partial segments.
40
+ def cached_matcher(obj)
41
+ cache = @match_pattern_cache
42
+
43
+ unless pattern = cache[obj]
44
+ pattern = cache[obj] = consume_pattern(yield)
45
+ end
46
+
47
+ pattern
48
+ end
49
+
50
+ # Since RodaRequest is anonymously subclassed when Roda is subclassed,
51
+ # and then assigned to a constant of the Roda subclass, make inspect
52
+ # reflect the likely name for the class.
53
+ def inspect
54
+ "#{roda_class.inspect}::RodaRequest"
55
+ end
56
+
57
+ private
58
+
59
+ # The pattern to use for consuming, based on the given argument. The returned
60
+ # pattern requires the path starts with a string and does not match partial
61
+ # segments.
62
+ def consume_pattern(pattern)
63
+ /\A\/(?:#{pattern})(?=\/|\z)/
64
+ end
65
+ end
66
+
67
+ # Instance methods for RodaRequest, mostly related to handling routing
68
+ # for the request.
69
+ module RequestMethods
70
+ TERM = Object.new
71
+ def TERM.inspect
72
+ "TERM"
73
+ end
74
+ TERM.freeze
75
+
76
+ # The current captures for the request. This gets modified as routing
77
+ # occurs.
78
+ attr_reader :captures
79
+
80
+ # The Roda instance related to this request object. Useful if routing
81
+ # methods need access to the scope of the Roda route block.
82
+ attr_reader :scope
83
+
84
+ # Store the roda instance and environment.
85
+ def initialize(scope, env)
86
+ @scope = scope
87
+ @captures = []
88
+ @remaining_path = _remaining_path(env)
89
+ @env = env
90
+ end
91
+
92
+ # Handle match block return values. By default, if a string is given
93
+ # and the response is empty, use the string as the response body.
94
+ def block_result(result)
95
+ res = response
96
+ if res.empty? && (body = block_result_body(result))
97
+ res.write(body)
98
+ end
99
+ end
100
+
101
+ # Match GET requests. If no arguments are provided, matches all GET
102
+ # requests, otherwise, matches only GET requests where the arguments
103
+ # given fully consume the path.
104
+ def get(*args, &block)
105
+ _verb(args, &block) if is_get?
106
+ end
107
+
108
+ # Immediately stop execution of the route block and return the given
109
+ # rack response array of status, headers, and body. If no argument
110
+ # is given, uses the current response.
111
+ #
112
+ # r.halt [200, {'Content-Type'=>'text/html'}, ['Hello World!']]
113
+ #
114
+ # response.status = 200
115
+ # response['Content-Type'] = 'text/html'
116
+ # response.write 'Hello World!'
117
+ # r.halt
118
+ def halt(res=response.finish)
119
+ throw :halt, res
120
+ end
121
+
122
+ # Show information about current request, including request class,
123
+ # request method and full path.
124
+ #
125
+ # r.inspect
126
+ # # => '#<Roda::RodaRequest GET /foo/bar>'
127
+ def inspect
128
+ "#<#{self.class.inspect} #{@env["REQUEST_METHOD"]} #{path}>"
129
+ end
130
+
131
+ if Rack.release >= '3'
132
+ def http_version
133
+ # Prefer SERVER_PROTOCOL as it is required in Rack 3.
134
+ # Still fall back to HTTP_VERSION if SERVER_PROTOCOL
135
+ # is not set, in case the server in use is not Rack 3
136
+ # compliant.
137
+ @env['SERVER_PROTOCOL'] || @env['HTTP_VERSION']
138
+ end
139
+ else
140
+ # What HTTP version the request was submitted with.
141
+ def http_version
142
+ # Prefer HTTP_VERSION as it is backwards compatible
143
+ # with previous Roda versions. Fallback to
144
+ # SERVER_PROTOCOL for servers that do not set
145
+ # HTTP_VERSION.
146
+ @env['HTTP_VERSION'] || @env['SERVER_PROTOCOL']
147
+ end
148
+ end
149
+
150
+ # Does a terminal match on the current path, matching only if the arguments
151
+ # have fully matched the path. If it matches, the match block is
152
+ # executed, and when the match block returns, the rack response is
153
+ # returned.
154
+ #
155
+ # r.remaining_path
156
+ # # => "/foo/bar"
157
+ #
158
+ # r.is 'foo' do
159
+ # # does not match, as path isn't fully matched (/bar remaining)
160
+ # end
161
+ #
162
+ # r.is 'foo/bar' do
163
+ # # matches as path is empty after matching
164
+ # end
165
+ #
166
+ # If no arguments are given, matches if the path is already fully matched.
167
+ #
168
+ # r.on 'foo/bar' do
169
+ # r.is do
170
+ # # matches as path is already empty
171
+ # end
172
+ # end
173
+ #
174
+ # Note that this matches only if the path after matching the arguments
175
+ # is empty, not if it still contains a trailing slash:
176
+ #
177
+ # r.remaining_path
178
+ # # => "/foo/bar/"
179
+ #
180
+ # r.is 'foo/bar' do
181
+ # # does not match, as path isn't fully matched (/ remaining)
182
+ # end
183
+ #
184
+ # r.is 'foo/bar/' do
185
+ # # matches as path is empty after matching
186
+ # end
187
+ #
188
+ # r.on 'foo/bar' do
189
+ # r.is "" do
190
+ # # matches as path is empty after matching
191
+ # end
192
+ # end
193
+ def is(*args, &block)
194
+ if args.empty?
195
+ if empty_path?
196
+ always(&block)
197
+ end
198
+ else
199
+ args << TERM
200
+ if_match(args, &block)
201
+ end
202
+ end
203
+
204
+ # Optimized method for whether this request is a +GET+ request.
205
+ # Similar to the default Rack::Request get? method, but can be
206
+ # overridden without changing rack's behavior.
207
+ def is_get?
208
+ @env["REQUEST_METHOD"] == 'GET'
209
+ end
210
+
211
+ # Does a match on the path, matching only if the arguments
212
+ # have matched the path. Because this doesn't fully match the
213
+ # path, this is usually used to setup branches of the routing tree,
214
+ # not for final handling of the request.
215
+ #
216
+ # r.remaining_path
217
+ # # => "/foo/bar"
218
+ #
219
+ # r.on 'foo' do
220
+ # # matches, path is /bar after matching
221
+ # end
222
+ #
223
+ # r.on 'bar' do
224
+ # # does not match
225
+ # end
226
+ #
227
+ # Like other routing methods, If it matches, the match block is
228
+ # executed, and when the match block returns, the rack response is
229
+ # returned. However, in general you will call another routing method
230
+ # inside the match block that fully matches the path and does the
231
+ # final handling for the request:
232
+ #
233
+ # r.on 'foo' do
234
+ # r.is 'bar' do
235
+ # # handle /foo/bar request
236
+ # end
237
+ # end
238
+ def on(*args, &block)
239
+ if args.empty?
240
+ always(&block)
241
+ else
242
+ if_match(args, &block)
243
+ end
244
+ end
245
+
246
+ # The already matched part of the path, including the original SCRIPT_NAME.
247
+ def matched_path
248
+ e = @env
249
+ e["SCRIPT_NAME"] + e["PATH_INFO"].chomp(@remaining_path)
250
+ end
251
+
252
+ # This an an optimized version of Rack::Request#path.
253
+ #
254
+ # r.env['SCRIPT_NAME'] = '/foo'
255
+ # r.env['PATH_INFO'] = '/bar'
256
+ # r.path
257
+ # # => '/foo/bar'
258
+ def path
259
+ e = @env
260
+ "#{e["SCRIPT_NAME"]}#{e["PATH_INFO"]}"
261
+ end
262
+
263
+ # The current path to match requests against.
264
+ attr_reader :remaining_path
265
+
266
+ # An alias of remaining_path. If a plugin changes remaining_path then
267
+ # it should override this method to return the untouched original.
268
+ alias real_remaining_path remaining_path
269
+
270
+ # Match POST requests. If no arguments are provided, matches all POST
271
+ # requests, otherwise, matches only POST requests where the arguments
272
+ # given fully consume the path.
273
+ def post(*args, &block)
274
+ _verb(args, &block) if post?
275
+ end
276
+
277
+ # Immediately redirect to the path using the status code. This ends
278
+ # the processing of the request:
279
+ #
280
+ # r.redirect '/page1', 301 if r['param'] == 'value1'
281
+ # r.redirect '/page2' # uses 302 status code
282
+ # response.status = 404 # not reached
283
+ #
284
+ # If you do not provide a path, by default it will redirect to the same
285
+ # path if the request is not a +GET+ request. This is designed to make
286
+ # it easy to use where a +POST+ request to a URL changes state, +GET+
287
+ # returns the current state, and you want to show the current state
288
+ # after changing:
289
+ #
290
+ # r.is "foo" do
291
+ # r.get do
292
+ # # show state
293
+ # end
294
+ #
295
+ # r.post do
296
+ # # change state
297
+ # r.redirect
298
+ # end
299
+ # end
300
+ def redirect(path=default_redirect_path, status=default_redirect_status)
301
+ response.redirect(path, status)
302
+ throw :halt, response.finish
303
+ end
304
+
305
+ # The response related to the current request. See ResponseMethods for
306
+ # instance methods for the response, but in general the most common usage
307
+ # is to override the response status and headers:
308
+ #
309
+ # response.status = 200
310
+ # response['Header-Name'] = 'Header value'
311
+ def response
312
+ @scope.response
313
+ end
314
+
315
+ # Return the Roda class related to this request.
316
+ def roda_class
317
+ self.class.roda_class
318
+ end
319
+
320
+ # Match method that only matches +GET+ requests where the current
321
+ # path is +/+. If it matches, the match block is executed, and when
322
+ # the match block returns, the rack response is returned.
323
+ #
324
+ # [r.request_method, r.remaining_path]
325
+ # # => ['GET', '/']
326
+ #
327
+ # r.root do
328
+ # # matches
329
+ # end
330
+ #
331
+ # This is usuable inside other match blocks:
332
+ #
333
+ # [r.request_method, r.remaining_path]
334
+ # # => ['GET', '/foo/']
335
+ #
336
+ # r.on 'foo' do
337
+ # r.root do
338
+ # # matches
339
+ # end
340
+ # end
341
+ #
342
+ # Note that this does not match non-+GET+ requests:
343
+ #
344
+ # [r.request_method, r.remaining_path]
345
+ # # => ['POST', '/']
346
+ #
347
+ # r.root do
348
+ # # does not match
349
+ # end
350
+ #
351
+ # Use <tt>r.post ""</tt> for +POST+ requests where the current path
352
+ # is +/+.
353
+ #
354
+ # Nor does it match empty paths:
355
+ #
356
+ # [r.request_method, r.remaining_path]
357
+ # # => ['GET', '/foo']
358
+ #
359
+ # r.on 'foo' do
360
+ # r.root do
361
+ # # does not match
362
+ # end
363
+ # end
364
+ #
365
+ # Use <tt>r.get true</tt> to handle +GET+ requests where the current
366
+ # path is empty.
367
+ def root(&block)
368
+ if @remaining_path == "/" && is_get?
369
+ always(&block)
370
+ end
371
+ end
372
+
373
+ # Call the given rack app with the environment and return the response
374
+ # from the rack app as the response for this request. This ends
375
+ # the processing of the request:
376
+ #
377
+ # r.run(proc{[403, {}, []]}) unless r['letmein'] == '1'
378
+ # r.run(proc{[404, {}, []]})
379
+ # response.status = 404 # not reached
380
+ #
381
+ # This updates SCRIPT_NAME/PATH_INFO based on the current remaining_path
382
+ # before dispatching to another rack app, so the app still works as
383
+ # a URL mapper.
384
+ def run(app)
385
+ e = @env
386
+ path = real_remaining_path
387
+ sn = "SCRIPT_NAME"
388
+ pi = "PATH_INFO"
389
+ script_name = e[sn]
390
+ path_info = e[pi]
391
+ begin
392
+ e[sn] += path_info.chomp(path)
393
+ e[pi] = path
394
+ throw :halt, app.call(e)
395
+ ensure
396
+ e[sn] = script_name
397
+ e[pi] = path_info
398
+ end
399
+ end
400
+
401
+ # The session for the current request. Raises a RodaError if
402
+ # a session handler has not been loaded.
403
+ def session
404
+ @env['rack.session'] || raise(RodaError, "You're missing a session handler, try using the sessions plugin.")
405
+ end
406
+
407
+ private
408
+
409
+ # Match any of the elements in the given array. Return at the
410
+ # first match without evaluating future matches. Returns false
411
+ # if no elements in the array match.
412
+ def _match_array(matcher)
413
+ matcher.any? do |m|
414
+ if matched = match(m)
415
+ if m.is_a?(String)
416
+ @captures.push(m)
417
+ end
418
+ end
419
+
420
+ matched
421
+ end
422
+ end
423
+
424
+ # Match the given class. Currently, the following classes
425
+ # are supported by default:
426
+ # Integer :: Match an integer segment, yielding result to block as an integer
427
+ # String :: Match any non-empty segment, yielding result to block as a string
428
+ def _match_class(klass)
429
+ meth = :"_match_class_#{klass}"
430
+ if respond_to?(meth, true)
431
+ # Allow calling private methods, as match methods are generally private
432
+ send(meth)
433
+ else
434
+ unsupported_matcher(klass)
435
+ end
436
+ end
437
+
438
+ # Match the given hash if all hash matchers match.
439
+ def _match_hash(hash)
440
+ # Allow calling private methods, as match methods are generally private
441
+ hash.all?{|k,v| send("match_#{k}", v)}
442
+ end
443
+
444
+ # Match integer segment of up to 100 decimal characters, and yield resulting value as an
445
+ # integer.
446
+ def _match_class_Integer
447
+ _consume_single_segment(/\A\/(\d{1,100})(?=\/|\z)/, :_convert_class_Integer)
448
+ end
449
+
450
+ # Match only if all of the arguments in the given array match.
451
+ # Match the given regexp exactly if it matches a full segment.
452
+ def _match_regexp(re)
453
+ consume(self.class.cached_matcher(re){re})
454
+ end
455
+
456
+ # Match the given string to the request path. Matches only if the
457
+ # request path ends with the string or if the next character in the
458
+ # request path is a slash (indicating a new segment).
459
+ def _match_string(str)
460
+ rp = @remaining_path
461
+ length = str.length
462
+
463
+ match = case rp.rindex(str, length)
464
+ when nil
465
+ # segment does not match, most common case
466
+ return
467
+ when 1
468
+ # segment matches, check first character is /
469
+ rp.getbyte(0) == 47
470
+ else # must be 0
471
+ # segment matches at first character, only a match if
472
+ # empty string given and first character is /
473
+ length == 0 && rp.getbyte(0) == 47
474
+ end
475
+
476
+ if match
477
+ length += 1
478
+ case rp.getbyte(length)
479
+ when 47
480
+ # next character is /, update remaining path to rest of string
481
+ @remaining_path = rp[length, 100000000]
482
+ when nil
483
+ # end of string, so remaining path is empty
484
+ @remaining_path = ""
485
+ # else
486
+ # Any other value means this was partial segment match,
487
+ # so we return nil in that case without updating the
488
+ # remaining_path. No need for explicit else clause.
489
+ end
490
+ end
491
+ end
492
+
493
+ # Match only if the next segment in the path is one of the
494
+ # set's elements, and yield the next segment.
495
+ def _match_set(set)
496
+ rp = @remaining_path
497
+ if key = _match_class_String
498
+ if set.include?(@captures[-1])
499
+ true
500
+ else
501
+ @remaining_path = rp
502
+ @captures.pop
503
+ false
504
+ end
505
+ end
506
+ end
507
+
508
+ # Match the given symbol if any segment matches.
509
+ def _match_symbol(sym=nil)
510
+ rp = @remaining_path
511
+ if rp.getbyte(0) == 47
512
+ if last = rp.index('/', 1)
513
+ @captures << rp[1, last-1]
514
+ @remaining_path = rp[last, rp.length]
515
+ elsif (len = rp.length) > 1
516
+ @captures << rp[1, len]
517
+ @remaining_path = ""
518
+ end
519
+ end
520
+ end
521
+
522
+ # Match any nonempty segment. This should be called without an argument.
523
+ alias _match_class_String _match_symbol
524
+
525
+ # The base remaining path to use.
526
+ def _remaining_path(env)
527
+ env["PATH_INFO"]
528
+ end
529
+
530
+ # Backbone of the verb method support, using a terminal match if
531
+ # args is not empty, or a regular match if it is empty.
532
+ def _verb(args, &block)
533
+ if args.empty?
534
+ always(&block)
535
+ else
536
+ args << TERM
537
+ if_match(args, &block)
538
+ end
539
+ end
540
+
541
+ # Yield to the match block and return rack response after the block returns.
542
+ def always
543
+ block_result(yield)
544
+ throw :halt, response.finish
545
+ end
546
+
547
+ # The body to use for the response if the response does not already have
548
+ # a body. By default, a String is returned directly, and nil is
549
+ # returned otherwise.
550
+ def block_result_body(result)
551
+ case result
552
+ when String
553
+ result
554
+ when nil, false
555
+ # nothing
556
+ else
557
+ unsupported_block_result(result)
558
+ end
559
+ end
560
+
561
+ # Attempts to match the pattern to the current path. If there is no
562
+ # match, returns false without changes. Otherwise, modifies
563
+ # SCRIPT_NAME to include the matched path, removes the matched
564
+ # path from PATH_INFO, and updates captures with any regex captures.
565
+ def consume(pattern, meth=nil)
566
+ if matchdata = pattern.match(@remaining_path)
567
+ captures = matchdata.captures
568
+
569
+ if meth
570
+ return unless captures = scope.send(meth, *captures)
571
+ # :nocov:
572
+ elsif defined?(yield)
573
+ # RODA4: Remove
574
+ return unless captures = yield(*captures)
575
+ # :nocov:
576
+ end
577
+
578
+ @remaining_path = matchdata.post_match
579
+
580
+ if captures.is_a?(Array)
581
+ @captures.concat(captures)
582
+ else
583
+ @captures << captures
584
+ end
585
+ end
586
+ end
587
+
588
+ if RUBY_VERSION >= "2.4"
589
+ # A faster version of consume that can be used if you are sure the regexp
590
+ # will match a single segment if it matches, capturing the entire segment.
591
+ def _consume_single_segment(regexp, meth=nil)
592
+ rp = @remaining_path
593
+ if regexp.match?(rp)
594
+ if last = rp.index('/', 1)
595
+ val = rp[1, last-1]
596
+ @remaining_path = rp[last, rp.length]
597
+ else
598
+ val = rp[1, rp.length]
599
+ @remaining_path = ""
600
+ end
601
+
602
+ if meth
603
+ if captures = scope.send(meth, val)
604
+ if captures.is_a?(Array)
605
+ @captures.concat(captures)
606
+ else
607
+ @captures << captures
608
+ end
609
+ end
610
+ else
611
+ @captures << val
612
+ end
613
+ end
614
+ end
615
+ # :nocov:
616
+ else
617
+ alias _consume_single_segment consume
618
+ # :nocov:
619
+ end
620
+
621
+ # The default path to use for redirects when a path is not given.
622
+ # For non-GET requests, redirects to the current path, which will
623
+ # trigger a GET request. This is to make the common case where
624
+ # a POST request will redirect to a GET request at the same location
625
+ # will work fine.
626
+ #
627
+ # If the current request is a GET request, raise an error, as otherwise
628
+ # it is easy to create an infinite redirect.
629
+ def default_redirect_path
630
+ raise RodaError, "must provide path argument to redirect for get requests" if is_get?
631
+ path
632
+ end
633
+
634
+ # The default status to use for redirects if a status is not provided,
635
+ # 302 by default.
636
+ def default_redirect_status
637
+ 302
638
+ end
639
+
640
+ # Whether the current path is considered empty.
641
+ def empty_path?
642
+ @remaining_path.empty?
643
+ end
644
+
645
+ # If all of the arguments match, yields to the match block and
646
+ # returns the rack response when the block returns. If any of
647
+ # the match arguments doesn't match, does nothing.
648
+ def if_match(args)
649
+ path = @remaining_path
650
+ # For every block, we make sure to reset captures so that
651
+ # nesting matchers won't mess with each other's captures.
652
+ captures = @captures.clear
653
+
654
+ if match_all(args)
655
+ block_result(yield(*captures))
656
+ throw :halt, response.finish
657
+ else
658
+ @remaining_path = path
659
+ false
660
+ end
661
+ end
662
+
663
+ # Attempt to match the argument to the given request, handling
664
+ # common ruby types.
665
+ def match(matcher)
666
+ case matcher
667
+ when String
668
+ _match_string(matcher)
669
+ when Class
670
+ _match_class(matcher)
671
+ when TERM
672
+ empty_path?
673
+ when Regexp
674
+ _match_regexp(matcher)
675
+ when true
676
+ matcher
677
+ when Array
678
+ _match_array(matcher)
679
+ when Hash
680
+ _match_hash(matcher)
681
+ when Set
682
+ _match_set(matcher)
683
+ when Symbol
684
+ _match_symbol(matcher)
685
+ when false, nil
686
+ matcher
687
+ when Proc
688
+ matcher.call
689
+ else
690
+ unsupported_matcher(matcher)
691
+ end
692
+ end
693
+
694
+ # Match only if all of the arguments in the given array match.
695
+ def match_all(args)
696
+ args.all?{|arg| match(arg)}
697
+ end
698
+
699
+ # Match by request method. This can be an array if you want
700
+ # to match on multiple methods.
701
+ def match_method(type)
702
+ if type.is_a?(Array)
703
+ type.any?{|t| match_method(t)}
704
+ else
705
+ type.to_s.upcase == @env["REQUEST_METHOD"]
706
+ end
707
+ end
708
+
709
+ # How to handle block results that are not nil, false, or a String.
710
+ # By default raises an exception.
711
+ def unsupported_block_result(result)
712
+ raise RodaError, "unsupported block result: #{result.inspect}"
713
+ end
714
+
715
+ # Handle an unsupported matcher.
716
+ def unsupported_matcher(matcher)
717
+ raise RodaError, "unsupported matcher: #{matcher.inspect}"
718
+ end
719
+ end
720
+ end
721
+ end
722
+ end