ultra-lite-hub 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.
Files changed (162) hide show
  1. checksums.yaml +7 -0
  2. data/roda-3.105.0/MIT-LICENSE +21 -0
  3. data/roda-3.105.0/lib/roda/cache.rb +42 -0
  4. data/roda-3.105.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
  5. data/roda-3.105.0/lib/roda/plugins/_after_hook.rb +11 -0
  6. data/roda-3.105.0/lib/roda/plugins/_base64.rb +34 -0
  7. data/roda-3.105.0/lib/roda/plugins/_before_hook.rb +41 -0
  8. data/roda-3.105.0/lib/roda/plugins/_optimized_matching.rb +227 -0
  9. data/roda-3.105.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
  10. data/roda-3.105.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
  11. data/roda-3.105.0/lib/roda/plugins/additional_render_engines.rb +61 -0
  12. data/roda-3.105.0/lib/roda/plugins/additional_view_directories.rb +66 -0
  13. data/roda-3.105.0/lib/roda/plugins/all_verbs.rb +50 -0
  14. data/roda-3.105.0/lib/roda/plugins/assets.rb +902 -0
  15. data/roda-3.105.0/lib/roda/plugins/assets_preloading.rb +89 -0
  16. data/roda-3.105.0/lib/roda/plugins/assume_ssl.rb +28 -0
  17. data/roda-3.105.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
  18. data/roda-3.105.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
  19. data/roda-3.105.0/lib/roda/plugins/backtracking_array.rb +88 -0
  20. data/roda-3.105.0/lib/roda/plugins/bearer_token.rb +28 -0
  21. data/roda-3.105.0/lib/roda/plugins/branch_locals.rb +74 -0
  22. data/roda-3.105.0/lib/roda/plugins/break.rb +43 -0
  23. data/roda-3.105.0/lib/roda/plugins/caching.rb +206 -0
  24. data/roda-3.105.0/lib/roda/plugins/capture_erb.rb +94 -0
  25. data/roda-3.105.0/lib/roda/plugins/chunked.rb +331 -0
  26. data/roda-3.105.0/lib/roda/plugins/class_level_routing.rb +111 -0
  27. data/roda-3.105.0/lib/roda/plugins/class_matchers.rb +155 -0
  28. data/roda-3.105.0/lib/roda/plugins/common_logger.rb +80 -0
  29. data/roda-3.105.0/lib/roda/plugins/conditional_sessions.rb +67 -0
  30. data/roda-3.105.0/lib/roda/plugins/content_for.rb +103 -0
  31. data/roda-3.105.0/lib/roda/plugins/content_security_policy.rb +329 -0
  32. data/roda-3.105.0/lib/roda/plugins/cookie_flags.rb +157 -0
  33. data/roda-3.105.0/lib/roda/plugins/cookies.rb +51 -0
  34. data/roda-3.105.0/lib/roda/plugins/csrf.rb +81 -0
  35. data/roda-3.105.0/lib/roda/plugins/custom_block_results.rb +85 -0
  36. data/roda-3.105.0/lib/roda/plugins/custom_matchers.rb +89 -0
  37. data/roda-3.105.0/lib/roda/plugins/default_headers.rb +64 -0
  38. data/roda-3.105.0/lib/roda/plugins/default_status.rb +35 -0
  39. data/roda-3.105.0/lib/roda/plugins/delay_build.rb +18 -0
  40. data/roda-3.105.0/lib/roda/plugins/delegate.rb +77 -0
  41. data/roda-3.105.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
  42. data/roda-3.105.0/lib/roda/plugins/direct_call.rb +38 -0
  43. data/roda-3.105.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
  44. data/roda-3.105.0/lib/roda/plugins/drop_body.rb +43 -0
  45. data/roda-3.105.0/lib/roda/plugins/each_part.rb +87 -0
  46. data/roda-3.105.0/lib/roda/plugins/early_hints.rb +25 -0
  47. data/roda-3.105.0/lib/roda/plugins/empty_root.rb +49 -0
  48. data/roda-3.105.0/lib/roda/plugins/environments.rb +79 -0
  49. data/roda-3.105.0/lib/roda/plugins/erb_h.rb +43 -0
  50. data/roda-3.105.0/lib/roda/plugins/error_email.rb +152 -0
  51. data/roda-3.105.0/lib/roda/plugins/error_handler.rb +133 -0
  52. data/roda-3.105.0/lib/roda/plugins/error_mail.rb +149 -0
  53. data/roda-3.105.0/lib/roda/plugins/exception_page.rb +445 -0
  54. data/roda-3.105.0/lib/roda/plugins/filter_common_logger.rb +46 -0
  55. data/roda-3.105.0/lib/roda/plugins/flash.rb +117 -0
  56. data/roda-3.105.0/lib/roda/plugins/h.rb +59 -0
  57. data/roda-3.105.0/lib/roda/plugins/halt.rb +108 -0
  58. data/roda-3.105.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
  59. data/roda-3.105.0/lib/roda/plugins/hash_branches.rb +145 -0
  60. data/roda-3.105.0/lib/roda/plugins/hash_matcher.rb +39 -0
  61. data/roda-3.105.0/lib/roda/plugins/hash_paths.rb +128 -0
  62. data/roda-3.105.0/lib/roda/plugins/hash_public.rb +125 -0
  63. data/roda-3.105.0/lib/roda/plugins/hash_public_cache.rb +91 -0
  64. data/roda-3.105.0/lib/roda/plugins/hash_routes.rb +304 -0
  65. data/roda-3.105.0/lib/roda/plugins/head.rb +99 -0
  66. data/roda-3.105.0/lib/roda/plugins/header_matchers.rb +88 -0
  67. data/roda-3.105.0/lib/roda/plugins/heartbeat.rb +41 -0
  68. data/roda-3.105.0/lib/roda/plugins/hmac_paths.rb +413 -0
  69. data/roda-3.105.0/lib/roda/plugins/hooks.rb +92 -0
  70. data/roda-3.105.0/lib/roda/plugins/host_authorization.rb +155 -0
  71. data/roda-3.105.0/lib/roda/plugins/host_routing.rb +190 -0
  72. data/roda-3.105.0/lib/roda/plugins/hsts.rb +35 -0
  73. data/roda-3.105.0/lib/roda/plugins/indifferent_params.rb +124 -0
  74. data/roda-3.105.0/lib/roda/plugins/inject_erb.rb +33 -0
  75. data/roda-3.105.0/lib/roda/plugins/invalid_request_body.rb +107 -0
  76. data/roda-3.105.0/lib/roda/plugins/ip_from_header.rb +34 -0
  77. data/roda-3.105.0/lib/roda/plugins/json.rb +119 -0
  78. data/roda-3.105.0/lib/roda/plugins/json_parser.rb +107 -0
  79. data/roda-3.105.0/lib/roda/plugins/link_to.rb +83 -0
  80. data/roda-3.105.0/lib/roda/plugins/mail_processor.rb +629 -0
  81. data/roda-3.105.0/lib/roda/plugins/mailer.rb +283 -0
  82. data/roda-3.105.0/lib/roda/plugins/map_matcher.rb +48 -0
  83. data/roda-3.105.0/lib/roda/plugins/match_affix.rb +64 -0
  84. data/roda-3.105.0/lib/roda/plugins/match_hook.rb +34 -0
  85. data/roda-3.105.0/lib/roda/plugins/match_hook_args.rb +93 -0
  86. data/roda-3.105.0/lib/roda/plugins/middleware.rb +225 -0
  87. data/roda-3.105.0/lib/roda/plugins/middleware_stack.rb +101 -0
  88. data/roda-3.105.0/lib/roda/plugins/module_include.rb +96 -0
  89. data/roda-3.105.0/lib/roda/plugins/multi_public.rb +95 -0
  90. data/roda-3.105.0/lib/roda/plugins/multi_route.rb +156 -0
  91. data/roda-3.105.0/lib/roda/plugins/multi_run.rb +144 -0
  92. data/roda-3.105.0/lib/roda/plugins/multi_view.rb +83 -0
  93. data/roda-3.105.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
  94. data/roda-3.105.0/lib/roda/plugins/named_routes.rb +167 -0
  95. data/roda-3.105.0/lib/roda/plugins/named_templates.rb +102 -0
  96. data/roda-3.105.0/lib/roda/plugins/not_allowed.rb +149 -0
  97. data/roda-3.105.0/lib/roda/plugins/not_found.rb +55 -0
  98. data/roda-3.105.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
  99. data/roda-3.105.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
  100. data/roda-3.105.0/lib/roda/plugins/padrino_render.rb +42 -0
  101. data/roda-3.105.0/lib/roda/plugins/param_matchers.rb +83 -0
  102. data/roda-3.105.0/lib/roda/plugins/params_capturing.rb +116 -0
  103. data/roda-3.105.0/lib/roda/plugins/part.rb +76 -0
  104. data/roda-3.105.0/lib/roda/plugins/partials.rb +74 -0
  105. data/roda-3.105.0/lib/roda/plugins/pass.rb +47 -0
  106. data/roda-3.105.0/lib/roda/plugins/path.rb +255 -0
  107. data/roda-3.105.0/lib/roda/plugins/path_matchers.rb +54 -0
  108. data/roda-3.105.0/lib/roda/plugins/path_rewriter.rb +111 -0
  109. data/roda-3.105.0/lib/roda/plugins/permissions_policy.rb +336 -0
  110. data/roda-3.105.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
  111. data/roda-3.105.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
  112. data/roda-3.105.0/lib/roda/plugins/precompile_templates.rb +156 -0
  113. data/roda-3.105.0/lib/roda/plugins/public.rb +182 -0
  114. data/roda-3.105.0/lib/roda/plugins/r.rb +35 -0
  115. data/roda-3.105.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
  116. data/roda-3.105.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
  117. data/roda-3.105.0/lib/roda/plugins/redirect_path.rb +59 -0
  118. data/roda-3.105.0/lib/roda/plugins/relative_path.rb +74 -0
  119. data/roda-3.105.0/lib/roda/plugins/render.rb +998 -0
  120. data/roda-3.105.0/lib/roda/plugins/render_coverage.rb +105 -0
  121. data/roda-3.105.0/lib/roda/plugins/render_each.rb +224 -0
  122. data/roda-3.105.0/lib/roda/plugins/render_locals.rb +102 -0
  123. data/roda-3.105.0/lib/roda/plugins/request_aref.rb +77 -0
  124. data/roda-3.105.0/lib/roda/plugins/request_headers.rb +81 -0
  125. data/roda-3.105.0/lib/roda/plugins/response_attachment.rb +94 -0
  126. data/roda-3.105.0/lib/roda/plugins/response_content_type.rb +79 -0
  127. data/roda-3.105.0/lib/roda/plugins/response_request.rb +29 -0
  128. data/roda-3.105.0/lib/roda/plugins/route_block_args.rb +50 -0
  129. data/roda-3.105.0/lib/roda/plugins/route_csrf.rb +388 -0
  130. data/roda-3.105.0/lib/roda/plugins/run_append_slash.rb +51 -0
  131. data/roda-3.105.0/lib/roda/plugins/run_handler.rb +51 -0
  132. data/roda-3.105.0/lib/roda/plugins/run_require_slash.rb +46 -0
  133. data/roda-3.105.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
  134. data/roda-3.105.0/lib/roda/plugins/send_file.rb +127 -0
  135. data/roda-3.105.0/lib/roda/plugins/sessions.rb +537 -0
  136. data/roda-3.105.0/lib/roda/plugins/shared_vars.rb +86 -0
  137. data/roda-3.105.0/lib/roda/plugins/sinatra_helpers.rb +443 -0
  138. data/roda-3.105.0/lib/roda/plugins/slash_path_empty.rb +26 -0
  139. data/roda-3.105.0/lib/roda/plugins/static.rb +42 -0
  140. data/roda-3.105.0/lib/roda/plugins/static_routing.rb +89 -0
  141. data/roda-3.105.0/lib/roda/plugins/status_303.rb +34 -0
  142. data/roda-3.105.0/lib/roda/plugins/status_handler.rb +86 -0
  143. data/roda-3.105.0/lib/roda/plugins/streaming.rb +170 -0
  144. data/roda-3.105.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
  145. data/roda-3.105.0/lib/roda/plugins/symbol_matchers.rb +188 -0
  146. data/roda-3.105.0/lib/roda/plugins/symbol_status.rb +32 -0
  147. data/roda-3.105.0/lib/roda/plugins/symbol_views.rb +37 -0
  148. data/roda-3.105.0/lib/roda/plugins/timestamp_public.rb +78 -0
  149. data/roda-3.105.0/lib/roda/plugins/type_routing.rb +215 -0
  150. data/roda-3.105.0/lib/roda/plugins/typecast_params.rb +1183 -0
  151. data/roda-3.105.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
  152. data/roda-3.105.0/lib/roda/plugins/unescape_path.rb +41 -0
  153. data/roda-3.105.0/lib/roda/plugins/view_options.rb +201 -0
  154. data/roda-3.105.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
  155. data/roda-3.105.0/lib/roda/plugins.rb +68 -0
  156. data/roda-3.105.0/lib/roda/request.rb +722 -0
  157. data/roda-3.105.0/lib/roda/response.rb +221 -0
  158. data/roda-3.105.0/lib/roda/session_middleware.rb +183 -0
  159. data/roda-3.105.0/lib/roda/version.rb +18 -0
  160. data/roda-3.105.0/lib/roda.rb +586 -0
  161. data/ultra-lite-hub.gemspec +12 -0
  162. metadata +201 -0
@@ -0,0 +1,329 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The content_security_policy plugin allows you to easily set a Content-Security-Policy
7
+ # header for the application, which modern browsers will use to control access to specific
8
+ # types of page content.
9
+ #
10
+ # You would generally call the plugin with a block to set the default policy:
11
+ #
12
+ # plugin :content_security_policy do |csp|
13
+ # csp.default_src :none
14
+ # csp.img_src :self
15
+ # csp.style_src :self
16
+ # csp.script_src :self
17
+ # csp.font_src :self
18
+ # csp.form_action :self
19
+ # csp.base_uri :none
20
+ # csp.frame_ancestors :none
21
+ # csp.block_all_mixed_content
22
+ # end
23
+ #
24
+ # Then, anywhere in the routing tree, you can customize the policy for just that
25
+ # branch or action using the same block syntax:
26
+ #
27
+ # r.get 'foo' do
28
+ # content_security_policy do |csp|
29
+ # csp.object_src :self
30
+ # csp.add_style_src 'bar.com'
31
+ # end
32
+ # # ...
33
+ # end
34
+ #
35
+ # In addition to using a block, you can also call methods on the object returned
36
+ # by the method:
37
+ #
38
+ # r.get 'foo' do
39
+ # content_security_policy.script_src :self, 'example.com', [:nonce, 'foobarbaz']
40
+ # # ...
41
+ # end
42
+ #
43
+ # The following methods are available for configuring the content security policy,
44
+ # which specify the setting (substituting _ with -):
45
+ #
46
+ # * base_uri
47
+ # * child_src
48
+ # * connect_src
49
+ # * default_src
50
+ # * font_src
51
+ # * form_action
52
+ # * frame_ancestors
53
+ # * frame_src
54
+ # * img_src
55
+ # * manifest_src
56
+ # * media_src
57
+ # * object_src
58
+ # * plugin_types
59
+ # * report_to
60
+ # * report_uri
61
+ # * require_sri_for
62
+ # * sandbox
63
+ # * script_src
64
+ # * style_src
65
+ # * worker_src
66
+ #
67
+ # All of these methods support any number of arguments, and each argument should
68
+ # be one of the following types:
69
+ #
70
+ # String :: used verbatim
71
+ # Symbol :: Substitutes +_+ with +-+ and surrounds with <tt>'</tt>
72
+ # Array :: only accepts 2 element arrays, joins elements with +-+ and
73
+ # surrounds the result with <tt>'</tt>
74
+ #
75
+ # Example:
76
+ #
77
+ # content_security_policy.script_src :self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']
78
+ # # script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
79
+ #
80
+ # When calling a method with no arguments, the setting is removed from the policy instead
81
+ # of being left empty, since all of these setting require at least one value. Likewise,
82
+ # if the policy does not have any settings, the header will not be added.
83
+ #
84
+ # Calling the method overrides any previous setting. Each of the methods has +add_*+ and
85
+ # +get_*+ methods defined. The +add_*+ method appends to any existing setting, and the +get_*+ method
86
+ # returns the current value for the setting.
87
+ #
88
+ # content_security_policy.script_src :self, :unsafe_eval
89
+ # content_security_policy.add_script_src 'example.com', [:nonce, 'foobarbaz']
90
+ # # script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
91
+ #
92
+ # content_security_policy.get_script_src
93
+ # # => [:self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']]
94
+ #
95
+ # The clear method can be used to remove all settings from the policy. Empty policies
96
+ # do not set any headers. You can use +response.skip_content_security_policy!+ to skip
97
+ # setting a policy. This is faster than calling +content_security_policy.clear+, since
98
+ # it does not duplicate the default policy.
99
+ #
100
+ # The following methods to set boolean settings are also defined:
101
+ #
102
+ # * block_all_mixed_content
103
+ # * upgrade_insecure_requests
104
+ #
105
+ # Calling these methods will turn on the related setting. To turn the setting
106
+ # off again, you can call them with a +false+ argument. There is also a <tt>*?</tt> method
107
+ # for each setting for returning whether the setting is currently enabled.
108
+ #
109
+ # Likewise there is also a +report_only+ method for turning on report only mode (the
110
+ # default is enforcement mode), or turning off report only mode if a false argument
111
+ # is given. Also, there is a +report_only?+ method for returning whether report only
112
+ # mode is enabled.
113
+ module ContentSecurityPolicy
114
+ # Represents a content security policy.
115
+ class Policy
116
+ '
117
+ base-uri
118
+ child-src
119
+ connect-src
120
+ default-src
121
+ font-src
122
+ form-action
123
+ frame-ancestors
124
+ frame-src
125
+ img-src
126
+ manifest-src
127
+ media-src
128
+ object-src
129
+ plugin-types
130
+ report-to
131
+ report-uri
132
+ require-sri-for
133
+ sandbox
134
+ script-src
135
+ style-src
136
+ worker-src
137
+ '.split.each(&:freeze).each do |setting|
138
+ meth = setting.tr('-', '_').freeze
139
+
140
+ # Setting method name sets the setting value, or removes it if no args are given.
141
+ define_method(meth) do |*args|
142
+ if args.empty?
143
+ @opts.delete(setting)
144
+ else
145
+ @opts[setting] = args.freeze
146
+ end
147
+ nil
148
+ end
149
+
150
+ # add_* method name adds to the setting value, or clears setting if no values
151
+ # are given.
152
+ define_method("add_#{meth}") do |*args|
153
+ unless args.empty?
154
+ @opts[setting] ||= EMPTY_ARRAY
155
+ @opts[setting] += args
156
+ @opts[setting].freeze
157
+ end
158
+ nil
159
+ end
160
+
161
+ # get_* method always returns current setting value.
162
+ define_method("get_#{meth}") do
163
+ @opts[setting]
164
+ end
165
+ end
166
+
167
+ %w'block-all-mixed-content upgrade-insecure-requests'.each(&:freeze).each do |setting|
168
+ meth = setting.tr('-', '_').freeze
169
+
170
+ # Setting method name turns on setting if true or no argument given,
171
+ # or removes setting if false is given.
172
+ define_method(meth) do |arg=true|
173
+ if arg
174
+ @opts[setting] = true
175
+ else
176
+ @opts.delete(setting)
177
+ end
178
+
179
+ nil
180
+ end
181
+
182
+ # *? method returns true or false depending on whether setting is enabled.
183
+ define_method("#{meth}?") do
184
+ !!@opts[setting]
185
+ end
186
+ end
187
+
188
+ def initialize
189
+ clear
190
+ end
191
+
192
+ # Clear all settings, useful to remove any inherited settings.
193
+ def clear
194
+ @opts = {}
195
+ end
196
+
197
+ # Do not allow future modifications to any settings.
198
+ def freeze
199
+ @opts.freeze
200
+ header_value.freeze
201
+ super
202
+ end
203
+
204
+ # The header name to use, depends on whether report only mode has been enabled.
205
+ def header_key
206
+ @report_only ? RodaResponseHeaders::CONTENT_SECURITY_POLICY_REPORT_ONLY : RodaResponseHeaders::CONTENT_SECURITY_POLICY
207
+ end
208
+
209
+ # The header value to use.
210
+ def header_value
211
+ return @header_value if @header_value
212
+
213
+ s = String.new
214
+ @opts.each do |k, vs|
215
+ s << k
216
+ unless vs == true
217
+ vs.each{|v| append_formatted_value(s, v)}
218
+ end
219
+ s << '; '
220
+ end
221
+ @header_value = s
222
+ end
223
+
224
+ # Set whether the Content-Security-Policy-Report-Only header instead of the
225
+ # default Content-Security-Policy header.
226
+ def report_only(report=true)
227
+ @report_only = report
228
+ end
229
+
230
+ # Whether this policy uses report only mode.
231
+ def report_only?
232
+ !!@report_only
233
+ end
234
+
235
+ # Set the current policy in the headers hash. If no settings have been made
236
+ # in the policy, does not set a header.
237
+ def set_header(headers)
238
+ return if @opts.empty?
239
+ headers[header_key] ||= header_value
240
+ end
241
+
242
+ private
243
+
244
+ # Handle three types of values when formatting the header:
245
+ # String :: used verbatim
246
+ # Symbol :: Substitutes _ with - and surrounds with '
247
+ # Array :: only accepts 2 element arrays, joins them with - and
248
+ # surrounds them with '
249
+ def append_formatted_value(s, v)
250
+ case v
251
+ when String
252
+ s << ' ' << v
253
+ when Array
254
+ case v.length
255
+ when 2
256
+ s << " '" << v.join('-') << "'"
257
+ else
258
+ raise RodaError, "unsupported CSP value used: #{v.inspect}"
259
+ end
260
+ when Symbol
261
+ s << " '" << v.to_s.tr('_', '-') << "'"
262
+ else
263
+ raise RodaError, "unsupported CSP value used: #{v.inspect}"
264
+ end
265
+ end
266
+
267
+ # Make object copy use copy of settings, and remove cached header value.
268
+ def initialize_copy(_)
269
+ super
270
+ @opts = @opts.dup
271
+ @header_value = nil
272
+ end
273
+ end
274
+
275
+
276
+ # Yield the current Content Security Policy to the block.
277
+ def self.configure(app)
278
+ policy = app.opts[:content_security_policy] = if policy = app.opts[:content_security_policy]
279
+ policy.dup
280
+ else
281
+ Policy.new
282
+ end
283
+
284
+ yield policy if defined?(yield)
285
+ policy.freeze
286
+ end
287
+
288
+ module InstanceMethods
289
+ # If a block is given, yield the current content security policy. Returns the
290
+ # current content security policy.
291
+ def content_security_policy
292
+ policy = @_response.content_security_policy
293
+ yield policy if defined?(yield)
294
+ policy
295
+ end
296
+ end
297
+
298
+ module ResponseMethods
299
+ # Unset any content security policy when reinitializing
300
+ def initialize
301
+ super
302
+ @content_security_policy &&= nil
303
+ end
304
+
305
+ # The current content security policy to be used for this response.
306
+ def content_security_policy
307
+ @content_security_policy ||= roda_class.opts[:content_security_policy].dup
308
+ end
309
+
310
+ # Do not set a content security policy header for this response.
311
+ def skip_content_security_policy!
312
+ @skip_content_security_policy = true
313
+ end
314
+
315
+ private
316
+
317
+ # Set the appropriate content security policy header.
318
+ def set_default_headers
319
+ super
320
+ unless @skip_content_security_policy
321
+ (@content_security_policy || roda_class.opts[:content_security_policy]).set_header(headers)
322
+ end
323
+ end
324
+ end
325
+ end
326
+
327
+ register_plugin(:content_security_policy, ContentSecurityPolicy)
328
+ end
329
+ end
@@ -0,0 +1,157 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The cookie_flags plugin allows users to force specific cookie flags for
7
+ # all cookies set by the application. It can also be used to warn or
8
+ # raise for unexpected cookie flags.
9
+ #
10
+ # The cookie_flags plugin deals with the following cookie flags:
11
+ #
12
+ # httponly :: Disallows access to the cookie from client-side scripts.
13
+ # samesite :: Restricts to which domains the cookie is sent.
14
+ # secure :: Instructs the browser to only transmit the cookie over HTTPS.
15
+ #
16
+ # This plugin ships in secure-by-default mode, where it enforces
17
+ # secure, httponly, samesite=strict cookies. You can disable enforcing
18
+ # specific flags using the following options:
19
+ #
20
+ # :httponly :: Set to false to not enforce httponly flag.
21
+ # :same_site :: Set to symbol or string to enforce a different samesite
22
+ # setting, or false to not enforce a specific samesite setting.
23
+ # :secure :: Set to false to not enforce secure flag.
24
+ #
25
+ # For example, to enforce secure cookies and enforce samesite=lax, but not enforce
26
+ # an httponly flag:
27
+ #
28
+ # plugin :cookie_flags, httponly: false, same_site: 'lax'
29
+ #
30
+ # In general, overriding cookie flags using this plugin should be considered a
31
+ # stop-gap solution. Instead of overriding cookie flags, it's better to fix
32
+ # whatever is setting the cookie flags incorrectly. You can use the :action
33
+ # option to modify the behavior:
34
+ #
35
+ # # Issue warnings when modifying cookie flags
36
+ # plugin :cookie_flags, action: :warn_and_modify
37
+ #
38
+ # # Issue warnings for incorrect cookie flags without modifying cookie flags
39
+ # plugin :cookie_flags, action: :warn
40
+ #
41
+ # # Raise errors for incorrect cookie flags
42
+ # plugin :cookie_flags, action: :raise
43
+ #
44
+ # The recommended way to use the plugin is to use it only during testing with
45
+ # <tt>action: :raise</tt>. Then as long as you have fully covering tests, you
46
+ # can be sure the cookies set by your application use the correct flags.
47
+ #
48
+ # Note that this plugin only affects cookies set by the application, and does not
49
+ # affect cookies set by middleware the application is using.
50
+ module CookieFlags
51
+ # :nocov:
52
+ MATCH_METH = RUBY_VERSION >= '2.4' ? :match? : :match
53
+ # :nocov:
54
+ private_constant :MATCH_METH
55
+
56
+ DEFAULTS = {:secure=>true, :httponly=>true, :same_site=>'strict', :action=>:modify}.freeze
57
+ private_constant :DEFAULTS
58
+
59
+ # Error class raised for action: :raise when incorrect cookie flags are used.
60
+ class Error < RodaError
61
+ end
62
+
63
+ def self.configure(app, opts=OPTS)
64
+ previous = app.opts[:cookie_flags] || DEFAULTS
65
+ opts = app.opts[:cookie_flags] = previous.merge(opts)
66
+
67
+ case opts[:same_site]
68
+ when String, Symbol
69
+ opts[:same_site] = opts[:same_site].to_s.downcase.freeze
70
+ opts[:same_site_string] = "; samesite=#{opts[:same_site]}".freeze
71
+ opts[:secure] = true if opts[:same_site] == 'none'
72
+ end
73
+
74
+ opts.freeze
75
+ end
76
+
77
+ module InstanceMethods
78
+ private
79
+
80
+ def _handle_cookie_flags_array(cookies)
81
+ opts = self.class.opts[:cookie_flags]
82
+ needs_secure = opts[:secure]
83
+ needs_httponly = opts[:httponly]
84
+ if needs_same_site = opts[:same_site]
85
+ same_site_string = opts[:same_site_string]
86
+ same_site_regexp = /;\s*samesite\s*=\s*(\S+)\s*(?:\z|;)/i
87
+ end
88
+ action = opts[:action]
89
+
90
+ cookies.map do |cookie|
91
+ if needs_secure
92
+ add_secure = !/;\s*secure\s*(?:\z|;)/i.send(MATCH_METH, cookie)
93
+ end
94
+
95
+ if needs_httponly
96
+ add_httponly = !/;\s*httponly\s*(?:\z|;)/i.send(MATCH_METH, cookie)
97
+ end
98
+
99
+ if needs_same_site
100
+ has_same_site = same_site_regexp.match(cookie)
101
+ unless add_same_site = !has_same_site
102
+ update_same_site = needs_same_site != has_same_site[1].downcase
103
+ end
104
+ end
105
+
106
+ next cookie unless add_secure || add_httponly || add_same_site || update_same_site
107
+
108
+ case action
109
+ when :raise, :warn, :warn_and_modify
110
+ message = "Response contains cookie with unexpected flags: #{cookie.inspect}." \
111
+ "Expecting the following cookie flags: "\
112
+ "#{'secure ' if add_secure}#{'httponly ' if add_httponly}#{same_site_string[2..-1] if add_same_site || update_same_site}"
113
+
114
+ if action == :raise
115
+ raise Error, message
116
+ else
117
+ warn(message)
118
+ next cookie if action == :warn
119
+ end
120
+ end
121
+
122
+ if update_same_site
123
+ cookie = cookie.gsub(same_site_regexp, same_site_string)
124
+ else
125
+ cookie = cookie.dup
126
+ cookie << same_site_string if add_same_site
127
+ end
128
+
129
+ cookie << '; secure' if add_secure
130
+ cookie << '; httponly' if add_httponly
131
+
132
+ cookie
133
+ end
134
+ end
135
+
136
+ if Rack.release >= '3'
137
+ def _handle_cookie_flags(cookies)
138
+ cookies = [cookies] if cookies.is_a?(String)
139
+ _handle_cookie_flags_array(cookies)
140
+ end
141
+ else
142
+ def _handle_cookie_flags(cookie_string)
143
+ _handle_cookie_flags_array(cookie_string.split("\n")).join("\n")
144
+ end
145
+ end
146
+
147
+ # Handle cookie flags in response
148
+ def _roda_after_85__cookie_flags(res)
149
+ return unless res && (headers = res[1]) && (value = headers[RodaResponseHeaders::SET_COOKIE])
150
+ headers[RodaResponseHeaders::SET_COOKIE] = _handle_cookie_flags(value)
151
+ end
152
+ end
153
+ end
154
+
155
+ register_plugin(:cookie_flags, CookieFlags)
156
+ end
157
+ end
@@ -0,0 +1,51 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'rack/utils'
4
+
5
+ #
6
+ class Roda
7
+ module RodaPlugins
8
+ # The cookies plugin adds response methods for handling cookies.
9
+ # Currently, you can set cookies with +set_cookie+ and delete cookies
10
+ # with +delete_cookie+:
11
+ #
12
+ # response.set_cookie('foo', 'bar')
13
+ # response.delete_cookie('foo')
14
+ #
15
+ # Pass a hash of cookie options when loading the plugin to set some
16
+ # defaults for all cookies upon setting and deleting. This is particularly
17
+ # useful for configuring the +domain+ and +path+ of all cookies.
18
+ #
19
+ # plugin :cookies, domain: 'example.com', path: '/api'
20
+ module Cookies
21
+ # Allow setting default cookie options when loading the cookies plugin.
22
+ def self.configure(app, opts={})
23
+ app.opts[:cookies_opts] = (app.opts[:cookies_opts]||{}).merge(opts).freeze
24
+ end
25
+
26
+ module ResponseMethods
27
+ # Modify the headers to include a Set-Cookie value that
28
+ # deletes the cookie. A value hash can be provided to
29
+ # override the default one used to delete the cookie.
30
+ # Example:
31
+ #
32
+ # response.delete_cookie('foo')
33
+ # response.delete_cookie('foo', domain: 'example.org')
34
+ def delete_cookie(key, value = {})
35
+ ::Rack::Utils.delete_cookie_header!(@headers, key, roda_class.opts[:cookies_opts].merge(value))
36
+ end
37
+
38
+ # Set the cookie with the given key in the headers.
39
+ #
40
+ # response.set_cookie('foo', 'bar')
41
+ # response.set_cookie('foo', value: 'bar', domain: 'example.org')
42
+ def set_cookie(key, value)
43
+ value = { :value=>value } unless value.respond_to?(:keys)
44
+ ::Rack::Utils.set_cookie_header!(@headers, key, roda_class.opts[:cookies_opts].merge(value))
45
+ end
46
+ end
47
+ end
48
+
49
+ register_plugin(:cookies, Cookies)
50
+ end
51
+ end
@@ -0,0 +1,81 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'rack/csrf'
4
+
5
+ class Roda
6
+ module RodaPlugins
7
+ # This plugin is no longer recommended for use, it exists only for
8
+ # backwards compatibility. Consider using the route_csrf plugin
9
+ # instead, as that provides stronger CSRF protection.
10
+ #
11
+ # The csrf plugin adds CSRF protection using rack_csrf, along with
12
+ # some csrf helper methods to use in your views. To use it, load
13
+ # the plugin, with the options hash passed to Rack::Csrf:
14
+ #
15
+ # plugin :csrf, raise: true
16
+ #
17
+ # Optionally you can choose not to setup rack_csrf middleware on the
18
+ # roda app if you already have one configured:
19
+ #
20
+ # plugin :csrf, skip_middleware: true
21
+ #
22
+ # This adds the following instance methods:
23
+ #
24
+ # csrf_field :: The field name to use for the hidden/meta csrf tag.
25
+ # csrf_header :: The http header name to use for submitting csrf token via
26
+ # headers (useful for javascript).
27
+ # csrf_metatag :: An html meta tag string containing the token, suitable
28
+ # for placing in the page header
29
+ # csrf_tag :: An html hidden input tag string containing the token, suitable
30
+ # for placing in an html form.
31
+ # csrf_token :: The value of the csrf token, in case it needs to be accessed
32
+ # directly.
33
+ module Csrf
34
+ CSRF = ::Rack::Csrf
35
+
36
+ # Load the Rack::Csrf middleware into the app with the given options.
37
+ def self.configure(app, opts={})
38
+ return if opts[:skip_middleware]
39
+ app.instance_exec do
40
+ @middleware.each do |(mid, *rest), _|
41
+ if mid.equal?(CSRF)
42
+ rest[0].merge!(opts)
43
+ build_rack_app
44
+ return
45
+ end
46
+ end
47
+ use CSRF, opts
48
+ end
49
+ end
50
+
51
+ module InstanceMethods
52
+ # The name of the hidden/meta csrf tag.
53
+ def csrf_field
54
+ CSRF.field
55
+ end
56
+
57
+ # The http header name to use for submitting csrf token via headers.
58
+ def csrf_header
59
+ CSRF.header
60
+ end
61
+
62
+ # An html meta tag string containing the token.
63
+ def csrf_metatag(opts={})
64
+ CSRF.metatag(env, opts)
65
+ end
66
+
67
+ # An html hidden input tag string containing the token.
68
+ def csrf_tag
69
+ CSRF.tag(env)
70
+ end
71
+
72
+ # The value of the csrf token.
73
+ def csrf_token
74
+ CSRF.token(env)
75
+ end
76
+ end
77
+ end
78
+
79
+ register_plugin(:csrf, Csrf)
80
+ end
81
+ end
@@ -0,0 +1,85 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The custom_block_results plugin allows you to specify handling
7
+ # for different block results. By default, Roda only supports
8
+ # nil, false, and string block results, but using this plugin,
9
+ # you can support other block results.
10
+ #
11
+ # For example, if you wanted to support returning Integer
12
+ # block results, and have them set the response status code,
13
+ # you could do:
14
+ #
15
+ # plugin :custom_block_results
16
+ #
17
+ # handle_block_result Integer do |result|
18
+ # response.status_code = result
19
+ # end
20
+ #
21
+ # route do |r|
22
+ # 200
23
+ # end
24
+ #
25
+ # The expected use case for this is to customize behavior by
26
+ # class, but matching uses ===, so it is possible to use non-class
27
+ # objects that respond to === appropriately.
28
+ #
29
+ # Note that custom block result handling only occurs if the types
30
+ # are not handled by Roda itself. You cannot use this to modify
31
+ # the handling of nil, false, or string results. Additionally,
32
+ # if the response body has already been written to before the the
33
+ # route block exits, then the result of the block is ignored,
34
+ # and the related +handle_block_result+ block will not be called
35
+ # (this is standard Roda behavior).
36
+ #
37
+ # The return value of the +handle_block_result+ block is written
38
+ # to the body if the block return value is a String, similar to
39
+ # standard Roda handling of block results. Non-String return
40
+ # values are ignored.
41
+ module CustomBlockResults
42
+ def self.configure(app)
43
+ app.opts[:custom_block_results] ||= {}
44
+ end
45
+
46
+ module ClassMethods
47
+ # Freeze the configured custom block results when freezing the app.
48
+ def freeze
49
+ opts[:custom_block_results].freeze
50
+ super
51
+ end
52
+
53
+ # Specify a block that will be called when an instance of klass
54
+ # is returned as a block result. The block defines a method.
55
+ def handle_block_result(klass, &block)
56
+ opts[:custom_block_results][klass] = define_roda_method(opts[:custom_block_results][klass] || "custom_block_result_#{klass}", 1, &block)
57
+ end
58
+ end
59
+
60
+ module RequestMethods
61
+ private
62
+
63
+ # Try each configured custom block result, and call the related method
64
+ # to get the block result.
65
+ def unsupported_block_result(result)
66
+ roda_class.opts[:custom_block_results].each do |klass, meth|
67
+ if klass === result
68
+ result = scope.send(meth, result)
69
+
70
+ if String === result
71
+ return result
72
+ else
73
+ return
74
+ end
75
+ end
76
+ end
77
+
78
+ super
79
+ end
80
+ end
81
+ end
82
+
83
+ register_plugin(:custom_block_results, CustomBlockResults)
84
+ end
85
+ end