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,79 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The response_content_type extension adds response.content_type
7
+ # and response.content_type= methods for getting and setting the
8
+ # response content-type.
9
+ #
10
+ # When setting the content-type, you can pass either a string, which
11
+ # is used directly:
12
+ #
13
+ # response.content_type = "text/html"
14
+ #
15
+ # Or, if you have registered mime types when loading the plugin:
16
+ #
17
+ # plugin :response_content_type, mime_types: {
18
+ # plain: "text/plain",
19
+ # html: "text/html",
20
+ # pdf: "application/pdf"
21
+ # }
22
+ #
23
+ # You can use a symbol:
24
+ #
25
+ # response.content_type = :html
26
+ #
27
+ # If you would like to load all mime types supported by rack/mime,
28
+ # you can use the <tt>mime_types: :from_rack_mime</tt> option:
29
+ #
30
+ # plugin :response_content_type, mime_types: :from_rack_mime
31
+ #
32
+ # Note that you are unlikely to be using all of these mime types,
33
+ # so doing this will likely result in unnecessary memory usage. It
34
+ # is recommended to use a hash with only the mime types your
35
+ # application actually uses.
36
+ #
37
+ # To prevent silent failures, if you attempt to set the response
38
+ # type with a symbol, and the symbol is not recognized, a KeyError
39
+ # is raised.
40
+ module ResponseContentType
41
+ def self.configure(app, opts=OPTS)
42
+ if mime_types = opts[:mime_types]
43
+ mime_types = if mime_types == :from_rack_mime
44
+ require "rack/mime"
45
+ h = {}
46
+ Rack::Mime::MIME_TYPES.each do |k, v|
47
+ h[k.slice(1,100).to_sym] = v
48
+ end
49
+ h
50
+ else
51
+ mime_types.dup
52
+ end
53
+ app.opts[:repsonse_content_types] = mime_types.freeze
54
+ else
55
+ app.opts[:repsonse_content_types] ||= {}
56
+ end
57
+ end
58
+
59
+ module ResponseMethods
60
+ # Return the content-type of the response. Will be nil if it has
61
+ # not yet been explicitly set.
62
+ def content_type
63
+ @headers[RodaResponseHeaders::CONTENT_TYPE]
64
+ end
65
+
66
+ # Set the content-type of the response. If given a string,
67
+ # it is used directly. If given a symbol, looks up the mime
68
+ # type with the given file extension. If the symbol is not
69
+ # a recognized mime type, raises KeyError.
70
+ def content_type=(mime_type)
71
+ mime_type = roda_class.opts[:repsonse_content_types].fetch(mime_type) if mime_type.is_a?(Symbol)
72
+ @headers[RodaResponseHeaders::CONTENT_TYPE] = mime_type
73
+ end
74
+ end
75
+ end
76
+
77
+ register_plugin(:response_content_type, ResponseContentType)
78
+ end
79
+ end
@@ -0,0 +1,36 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The response_request plugin gives the response access to the
7
+ # related request instance via the #request method.
8
+ #
9
+ # Example:
10
+ #
11
+ # plugin :response_request
12
+ module ResponseRequest
13
+ # This isn't set because it breaks usage with the error_handler/class_level_routing
14
+ # plugins and the shape_friendly plugin, due to those calling RodaResponse#initialize,
15
+ # which would reset @request to nil. It isn't strictly necessary to set this for
16
+ # shape friendliness, as the Roda#initialize sets it directly after creating the
17
+ # RodaRequest, so in normal use, the instance variable will already be set.
18
+ # RESPONSE_INSTANCE_VARIABLES = [:@request].freeze
19
+
20
+ module InstanceMethods
21
+ # Set the response's request to the current request.
22
+ def initialize(env)
23
+ super
24
+ @_response.request = @_request
25
+ end
26
+ end
27
+
28
+ module ResponseMethods
29
+ # The request related to this response.
30
+ attr_accessor :request
31
+ end
32
+ end
33
+
34
+ register_plugin(:response_request, ResponseRequest)
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The route_block_args plugin lets you customize what arguments are passed to
7
+ # the +route+ block. So if you have an application that always needs access
8
+ # to the +response+, the +params+, the +env+, or the +session+, you can use
9
+ # this plugin so that any of those can be arguments to the route block.
10
+ # Example:
11
+ #
12
+ # class App < Roda
13
+ # plugin :route_block_args do
14
+ # [request, request.params, response]
15
+ # end
16
+ #
17
+ # route do |r, params, res|
18
+ # r.post do
19
+ # artist = Artist.create(name: params['name'].to_s)
20
+ # res.status = 201
21
+ # artist.id.to_s
22
+ # end
23
+ # end
24
+ # end
25
+ module RouteBlockArgs
26
+ def self.configure(app, &block)
27
+ app.instance_exec do
28
+ define_roda_method(:_roda_route_block_args, 0, &block)
29
+ route(&@raw_route_block) if @raw_route_block
30
+ end
31
+ end
32
+
33
+ # Override the route block input so that the block
34
+ # given is passed the arguments specified by the
35
+ # block given to the route_block_args plugin.
36
+ module ClassMethods
37
+ private
38
+
39
+ def convert_route_block(block)
40
+ meth = define_roda_method("convert_route_block_args", :any, &super(block))
41
+ lambda do |_|
42
+ send(meth, *_roda_route_block_args)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ register_plugin :route_block_args, RouteBlockArgs
49
+ end
50
+ end
@@ -0,0 +1,388 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'openssl'
4
+ require 'securerandom'
5
+ require 'uri'
6
+ require 'rack/utils'
7
+
8
+ class Roda
9
+ module RodaPlugins
10
+ # The route_csrf plugin is the recommended plugin to use to support
11
+ # CSRF protection in Roda applications. This plugin allows you set
12
+ # where in the routing tree to enforce CSRF protection. Additionally,
13
+ # the route_csrf plugin uses modern security practices.
14
+ #
15
+ # By default, the plugin requires tokens be specific to the request
16
+ # method and request path, so a CSRF token generated for one form will
17
+ # not be usable to submit a different form.
18
+ #
19
+ # This plugin also takes care to not expose the underlying CSRF key
20
+ # (except in the session), so that it is not possible for an attacker
21
+ # to generate valid CSRF tokens specific to an arbitrary request method
22
+ # and request path even if they have access to a token that is not
23
+ # specific to request method and request path. To get this security
24
+ # benefit, you must ensure an attacker does not have access to the
25
+ # session. Rack::Session::Cookie versions shipped with Rack before
26
+ # Rack 3 use signed sessions, not encrypted
27
+ # sessions, so if the attacker has the ability to read cookie data
28
+ # and you are using one of those Rack::Session::Cookie versions,
29
+ # it will still be possible
30
+ # for an attacker to generate valid CSRF tokens specific to arbitrary
31
+ # request method and request path. Roda's session plugin uses
32
+ # encrypted sessions and therefore is safe even if the attacker can
33
+ # read cookie data.
34
+ #
35
+ # == Usage
36
+ #
37
+ # It is recommended to use the plugin defaults, loading the
38
+ # plugin with no options:
39
+ #
40
+ # plugin :route_csrf
41
+ #
42
+ # This plugin supports the following options:
43
+ #
44
+ # :field :: Form input parameter name for CSRF token (default: '_csrf')
45
+ # :formaction_field :: Form input parameter name for path-specific CSRF tokens (used by the
46
+ # +csrf_formaction_tag+ method). If present, this parameter should be
47
+ # submitted as a hash, keyed by path, with CSRF token values.
48
+ # :header :: HTTP header name for CSRF token (default: 'X-CSRF-Token')
49
+ # :key :: Session key for CSRF secret (default: '_roda_csrf_secret')
50
+ # :require_request_specific_tokens :: Whether request-specific tokens are required (default: true).
51
+ # A false value will allow tokens that are not request-specific
52
+ # to also work. You should only set this to false if it is
53
+ # impossible to use request-specific tokens. If you must
54
+ # use non-request-specific tokens in certain cases, it is best
55
+ # to leave this option true by default, and override it on a
56
+ # per call basis in those specific cases.
57
+ # :csrf_failure :: The action to taken if a request fails the CSRF check (default: :raise). Options:
58
+ # :raise :: raise a Roda::RodaPlugins::RouteCsrf::InvalidToken exception
59
+ # :empty_403 :: return a blank 403 page (rack_csrf's default behavior)
60
+ # :clear_session :: Clear the current session
61
+ # Proc :: Treated as a routing block, called with request object
62
+ # :check_header :: Whether the HTTP header should be checked for the token value (default: false).
63
+ # If true, checks the HTTP header after checking for the form input parameter.
64
+ # If :only, only checks the HTTP header and doesn't check the form input parameter.
65
+ # :check_request_methods :: Which request methods require CSRF protection
66
+ # (default: <tt>['POST', 'DELETE', 'PATCH', 'PUT']</tt>)
67
+ # :upgrade_from_rack_csrf_key :: If provided, the session key that should be checked for the
68
+ # rack_csrf raw token. If the session key is present, the value
69
+ # will be checked against the submitted token, and if it matches,
70
+ # the CSRF check will be passed. Should only be set temporarily
71
+ # if upgrading from using rack_csrf to the route_csrf plugin, and
72
+ # should be removed as soon as you are OK with CSRF forms generated
73
+ # before the upgrade not longer being usable. The default rack_csrf
74
+ # key is <tt>'csrf.token'</tt>.
75
+ #
76
+ # The plugin also supports a block, in which case the block will be used
77
+ # as the value of the :csrf_failure option.
78
+ #
79
+ # == Methods
80
+ #
81
+ # This adds the following instance methods:
82
+ #
83
+ # check_csrf!(opts={}) :: Used for checking if the submitted CSRF token is valid.
84
+ # If a block is provided, it is treated as a routing block if the
85
+ # CSRF token is not valid. Otherwise, by default, raises a
86
+ # Roda::RodaPlugins::RouteCsrf::InvalidToken exception if a CSRF
87
+ # token is necessary for the request and there is no token provided
88
+ # or the provided token is not valid. Options can be provided to
89
+ # override any of the plugin options for this specific call.
90
+ # The :token option can be used to specify the provided CSRF token
91
+ # (instead of looking for the token in the submitted parameters).
92
+ # csrf_formaction_tag(path, method='POST') :: An HTML hidden input tag string containing the CSRF token, suitable
93
+ # for placing in an HTML form that has inputs that use formaction
94
+ # attributes to change the endpoint to which the form is submitted.
95
+ # Takes the same arguments as csrf_token.
96
+ # csrf_field :: The field name to use for the hidden tag containing the CSRF token.
97
+ # csrf_path(action) :: This takes an argument that would be the value of the HTML form's
98
+ # action attribute, and returns a path you can pass to csrf_token
99
+ # that should be valid for the form submission. The argument should
100
+ # either be nil or a string representing a relative path, absolute
101
+ # path, or full URL (using appropriate URL encoding).
102
+ # csrf_tag(path=nil, method='POST') :: An HTML hidden input tag string containing the CSRF token, suitable
103
+ # for placing in an HTML form. Takes the same arguments as csrf_token.
104
+ # csrf_token(path=nil, method='POST') :: The value of the csrf token, in case it needs to be accessed
105
+ # directly. It is recommended to call this method with a
106
+ # path, which will create a request-specific token. Calling
107
+ # this method without an argument will create a token that is
108
+ # not specific to the request, but such a token will only
109
+ # work if you set the :require_request_specific_tokens option
110
+ # to false, which is a bad idea from a security standpoint.
111
+ # use_request_specific_csrf_tokens? :: Whether the plugin is configured to only support
112
+ # request-specific tokens, true by default.
113
+ # valid_csrf?(opts={}) :: Returns whether the submitted CSRF token is valid (also true if
114
+ # the request does not require a CSRF token). Takes same option hash
115
+ # as check_csrf!.
116
+ #
117
+ # This plugin also adds the following instance methods for compatibility with the
118
+ # older csrf plugin, but it is not recommended to use these methods in new code:
119
+ #
120
+ # csrf_header :: The header name to use for submitting the CSRF token via an HTTP header
121
+ # (useful for javascript). Note that this plugin will not look in
122
+ # the HTTP header by default, it will only do so if the :check_header
123
+ # option is used.
124
+ # csrf_metatag :: An HTML meta tag string containing the CSRF token, suitable
125
+ # for placing in the page header. It is not recommended to use
126
+ # this method, as the token generated is not request-specific and
127
+ # will not work unless you set the :require_request_specific_tokens option to
128
+ # false, which is a bad idea from a security standpoint.
129
+ #
130
+ # == Token Cryptography
131
+ #
132
+ # route_csrf uses HMAC-SHA-256 to generate all CSRF tokens. It generates a random 32-byte secret,
133
+ # which is stored base64 encoded in the session. For each CSRF token, it generates 31 bytes
134
+ # of random data.
135
+ #
136
+ # For request-specific CSRF tokens, this pseudocode generates the HMAC:
137
+ #
138
+ # hmac = HMAC(secret, method + path + random_data)
139
+ #
140
+ # For CSRF tokens not specific to a request, this pseudocode generates the HMAC:
141
+ #
142
+ # hmac = HMAC(secret, random_data)
143
+ #
144
+ # This pseudocode generates the final CSRF token in both cases:
145
+ #
146
+ # token = Base64Encode(random_data + hmac)
147
+ #
148
+ # Using this construction for generating CSRF tokens means that generating any
149
+ # valid CSRF token without knowledge of the secret is equivalent to a successful generic attack
150
+ # on HMAC-SHA-256.
151
+ #
152
+ # By using an HMAC for tokens not specific to a request, it is not possible to use a
153
+ # valid CSRF token that is not specific to a request to generate a valid request-specific
154
+ # CSRF token.
155
+ #
156
+ # By including random data in the HMAC for all tokens, different tokens are generated
157
+ # each time, mitigating compression ratio attacks such as BREACH.
158
+ module RouteCsrf
159
+ # Default CSRF option values
160
+ DEFAULTS = {
161
+ :field => '_csrf'.freeze,
162
+ :formaction_field => '_csrfs'.freeze,
163
+ :header => 'X-CSRF-Token'.freeze,
164
+ :key => '_roda_csrf_secret'.freeze,
165
+ :require_request_specific_tokens => true,
166
+ :csrf_failure => :raise,
167
+ :check_header => false,
168
+ :check_request_methods => %w'POST DELETE PATCH PUT'.freeze.each(&:freeze)
169
+ }.freeze
170
+
171
+ # Exception class raised when :csrf_failure option is :raise and
172
+ # a valid CSRF token was not provided.
173
+ class InvalidToken < RodaError; end
174
+
175
+ def self.load_dependencies(app, opts=OPTS, &_)
176
+ app.plugin :_base64
177
+ end
178
+
179
+ def self.configure(app, opts=OPTS, &block)
180
+ options = app.opts[:route_csrf] = (app.opts[:route_csrf] || DEFAULTS).merge(opts)
181
+ if block || opts[:csrf_failure].is_a?(Proc)
182
+ if block && opts[:csrf_failure]
183
+ raise RodaError, "Cannot specify both route_csrf plugin block and :csrf_failure option"
184
+ end
185
+ block ||= opts[:csrf_failure]
186
+ options[:csrf_failure] = :csrf_failure_method
187
+ app.define_roda_method(:_roda_route_csrf_failure, 1, &app.send(:convert_route_block, block))
188
+ end
189
+ options[:env_header] = "HTTP_#{options[:header].to_s.tr('-', '_').upcase}".freeze
190
+ options.freeze
191
+ end
192
+
193
+ module InstanceMethods
194
+ # Check that the submitted CSRF token is valid, if the request requires a CSRF token.
195
+ # If the CSRF token is valid or the request does not require a CSRF token, return nil.
196
+ # Otherwise, if a block is given, treat it as a routing block and yield to it, and
197
+ # if a block is not given, use the :csrf_failure option to determine how to handle it.
198
+ def check_csrf!(opts=OPTS, &block)
199
+ if msg = csrf_invalid_message(opts)
200
+ if block
201
+ @_request.on(&block)
202
+ end
203
+
204
+ case failure_action = opts.fetch(:csrf_failure, csrf_options[:csrf_failure])
205
+ when :raise
206
+ raise InvalidToken, msg
207
+ when :empty_403
208
+ @_response.status = 403
209
+ headers = @_response.headers
210
+ headers.clear
211
+ headers[RodaResponseHeaders::CONTENT_TYPE] = 'text/html'
212
+ headers[RodaResponseHeaders::CONTENT_LENGTH] ='0'
213
+ throw :halt, @_response.finish_with_body([])
214
+ when :clear_session
215
+ session.clear
216
+ when :csrf_failure_method
217
+ @_request.on{_roda_route_csrf_failure(@_request)}
218
+ when Proc
219
+ RodaPlugins.warn "Passing a Proc as the :csrf_failure option value to check_csrf! is deprecated"
220
+ @_request.on{instance_exec(@_request, &failure_action)} # Deprecated
221
+ else
222
+ raise RodaError, "Unsupported :csrf_failure option: #{failure_action.inspect}"
223
+ end
224
+ end
225
+ end
226
+
227
+ # The name of the hidden input tag containing the CSRF token. Also used as the name
228
+ # for the meta tag.
229
+ def csrf_field
230
+ csrf_options[:field]
231
+ end
232
+
233
+ # The HTTP header name to use when submitting CSRF tokens in an HTTP header, if
234
+ # such support is enabled (it is not by default).
235
+ def csrf_header
236
+ csrf_options[:header]
237
+ end
238
+
239
+ # An HTML meta tag string containing a CSRF token that is not request-specific.
240
+ # It is not recommended to use this, as it doesn't support request-specific tokens.
241
+ def csrf_metatag
242
+ "<meta name=\"#{csrf_field}\" content=\"#{csrf_token}\" \/>"
243
+ end
244
+
245
+ # Given a form action, return the appropriate path to use for the CSRF token.
246
+ # This makes it easier to generate request-specific tokens without having to
247
+ # worry about the different types of form actions (relative paths, absolute
248
+ # paths, URLs, empty paths).
249
+ def csrf_path(action)
250
+ case action
251
+ when nil, '', /\A[#?]/
252
+ # use current path
253
+ request.path
254
+ when /\A(?:https?:\/)?\//
255
+ # Either full URI or absolute path, extract just the path
256
+ URI.parse(action).path
257
+ else
258
+ # relative path, join to current path
259
+ URI.join(request.url, action).path
260
+ end
261
+ end
262
+
263
+ # An HTML hidden input tag string containing the CSRF token, used for inputs
264
+ # with formaction, so the same form can be used to submit to multiple endpoints
265
+ # depending on which button was clicked. See csrf_token for arguments, but the
266
+ # path argument is required.
267
+ def csrf_formaction_tag(path, *args)
268
+ "<input type=\"hidden\" name=\"#{csrf_options[:formaction_field]}[#{Rack::Utils.escape_html(path)}]\" value=\"#{csrf_token(path, *args)}\" \/>"
269
+ end
270
+
271
+ # An HTML hidden input tag string containing the CSRF token. See csrf_token for
272
+ # arguments.
273
+ def csrf_tag(*args)
274
+ "<input type=\"hidden\" name=\"#{csrf_field}\" value=\"#{csrf_token(*args)}\" \/>"
275
+ end
276
+
277
+ # The value of the csrf token. For a path specific token, provide a path
278
+ # argument. By default, it a path is provided, the POST request method will
279
+ # be assumed. To generate a token for a non-POST request method, pass the
280
+ # method as the second argument.
281
+ def csrf_token(path=nil, method=('POST' if path))
282
+ token = SecureRandom.random_bytes(31)
283
+ token << csrf_hmac(token, method, path)
284
+ [token].pack("m0")
285
+ end
286
+
287
+ # Whether request-specific CSRF tokens should be used by default.
288
+ def use_request_specific_csrf_tokens?
289
+ csrf_options[:require_request_specific_tokens]
290
+ end
291
+
292
+ # Whether the submitted CSRF token is valid for the request. True if the
293
+ # request does not require a CSRF token.
294
+ def valid_csrf?(opts=OPTS)
295
+ csrf_invalid_message(opts).nil?
296
+ end
297
+
298
+ private
299
+
300
+ # Returns error message string if the CSRF token is not valid.
301
+ # Returns nil if the CSRF token is valid.
302
+ def csrf_invalid_message(opts)
303
+ opts = opts.empty? ? csrf_options : csrf_options.merge(opts)
304
+ method = request.request_method
305
+
306
+ unless opts[:check_request_methods].include?(method)
307
+ return
308
+ end
309
+
310
+ path = @_request.path
311
+
312
+ unless encoded_token = opts[:token]
313
+ encoded_token = case opts[:check_header]
314
+ when :only
315
+ env[opts[:env_header]]
316
+ when true
317
+ return (csrf_invalid_message(opts.merge(:check_header=>false)) && csrf_invalid_message(opts.merge(:check_header=>:only)))
318
+ else
319
+ params = @_request.params
320
+ ((formactions = params[opts[:formaction_field]]).is_a?(Hash) && (formactions[path])) || params[opts[:field]]
321
+ end
322
+ end
323
+
324
+ unless encoded_token.is_a?(String)
325
+ return "encoded token is not a string"
326
+ end
327
+
328
+ if (rack_csrf_key = opts[:upgrade_from_rack_csrf_key]) && (rack_csrf_value = session[rack_csrf_key]) && csrf_compare(rack_csrf_value, encoded_token)
329
+ return
330
+ end
331
+
332
+ # 31 byte random initialization vector
333
+ # 32 byte HMAC
334
+ # 63 bytes total
335
+ # 84 bytes when base64 encoded
336
+ unless encoded_token.bytesize == 84
337
+ return "encoded token length is not 84"
338
+ end
339
+
340
+ begin
341
+ submitted_hmac = Base64_.decode64(encoded_token)
342
+ rescue ArgumentError
343
+ return "encoded token is not valid base64"
344
+ end
345
+
346
+ random_data = submitted_hmac.slice!(0...31)
347
+
348
+ if csrf_compare(csrf_hmac(random_data, method, path), submitted_hmac)
349
+ return
350
+ end
351
+
352
+ if opts[:require_request_specific_tokens]
353
+ "decoded token is not valid for request method and path"
354
+ else
355
+ unless csrf_compare(csrf_hmac(random_data, '', ''), submitted_hmac)
356
+ "decoded token is not valid for either request method and path or for blank method and path"
357
+ end
358
+ end
359
+ end
360
+
361
+ # Helper for getting the plugin options.
362
+ def csrf_options
363
+ opts[:route_csrf]
364
+ end
365
+
366
+ # Perform a constant-time comparison of the two strings, returning true if they match and false otherwise.
367
+ def csrf_compare(s1, s2)
368
+ Rack::Utils.secure_compare(s1, s2)
369
+ end
370
+
371
+ # Return the HMAC-SHA-256 for the secret and the given arguments.
372
+ def csrf_hmac(random_data, method, path)
373
+ OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, csrf_secret, "#{method.to_s.upcase}#{path}#{random_data}")
374
+ end
375
+
376
+ # If a secret has not already been specified, generate a random 32-byte
377
+ # secret, stored base64 encoded in the session (to handle cases where
378
+ # JSON is used for session serialization).
379
+ def csrf_secret
380
+ key = session[csrf_options[:key]] ||= SecureRandom.base64(32)
381
+ Base64_.decode64(key)
382
+ end
383
+ end
384
+ end
385
+
386
+ register_plugin(:route_csrf, RouteCsrf)
387
+ end
388
+ end
@@ -0,0 +1,51 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The run_append_slash plugin makes +r.run+ use +/+ as the +PATH_INFO+
7
+ # when calling the rack application if +PATH_INFO+ would be empty.
8
+ # Example:
9
+ #
10
+ # route do |r|
11
+ # r.on "a" do
12
+ # r.run App
13
+ # end
14
+ # end
15
+ #
16
+ # # without run_append_slash:
17
+ # # GET /a => App gets "" as PATH_INFO
18
+ # # GET /a/ => App gets "/" as PATH_INFO
19
+ #
20
+ # # with run_append_slash:
21
+ # # GET /a => App gets "/" as PATH_INFO
22
+ # # GET /a/ => App gets "/" as PATH_INFO
23
+ module RunAppendSlash
24
+ # Set plugin specific options. Options:
25
+ # :use_redirects :: Whether to issue 302 redirects when appending the
26
+ # trailing slash.
27
+ def self.configure(app, opts=OPTS)
28
+ app.opts[:run_append_slash_redirect] = !!opts[:use_redirects]
29
+ end
30
+
31
+ module RequestMethods
32
+ # Calls the given rack app. If the path matches the root of the app but
33
+ # does not contain a trailing slash, a trailing slash is appended to the
34
+ # path internally, or a redirect is issued when configured with
35
+ # <tt>use_redirects: true</tt>.
36
+ def run(*)
37
+ if @remaining_path.empty?
38
+ if scope.opts[:run_append_slash_redirect]
39
+ redirect("#{path}/")
40
+ else
41
+ @remaining_path += '/'
42
+ end
43
+ end
44
+ super
45
+ end
46
+ end
47
+ end
48
+
49
+ register_plugin(:run_append_slash, RunAppendSlash)
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The run_handler plugin allows r.run to take a block, which is yielded
7
+ # the rack response array, before it returns it as a response.
8
+ #
9
+ # Additionally, r.run also takes a options hash, and you can provide a
10
+ # <tt>not_found: :pass</tt> option to keep routing normally if the rack
11
+ # app returns a 404 response.
12
+ #
13
+ #
14
+ # plugin :run_handler
15
+ #
16
+ # route do |r|
17
+ # r.on 'a' do
18
+ # # Keep running code if RackAppFoo doesn't return a result
19
+ # r.run RackAppFoo, not_found: :pass
20
+ #
21
+ # # Change response status codes before returning.
22
+ # r.run(RackAppBar) do |response|
23
+ # response[0] = 200 if response[0] == 201
24
+ # end
25
+ # end
26
+ # end
27
+ module RunHandler
28
+ module RequestMethods
29
+ # If a block is given, yield the rack response array to it. The response can
30
+ # be modified before it is returned by the current app.
31
+ #
32
+ # If the <tt>not_found: :pass</tt> option is given, and the rack response
33
+ # returned by the app is a 404 response, do not return the response, continue
34
+ # routing normally.
35
+ def run(app, opts=OPTS)
36
+ res = catch(:halt){super(app)}
37
+ yield res if defined?(yield)
38
+ if opts[:not_found] == :pass && res[0] == 404
39
+ body = res[2]
40
+ body.close if body.respond_to?(:close)
41
+ nil
42
+ else
43
+ throw(:halt, res)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ register_plugin(:run_handler, RunHandler)
50
+ end
51
+ end