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.
- checksums.yaml +7 -0
- data/roda-3.106.0/MIT-LICENSE +21 -0
- data/roda-3.106.0/lib/roda/cache.rb +42 -0
- data/roda-3.106.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
- data/roda-3.106.0/lib/roda/plugins/_after_hook.rb +11 -0
- data/roda-3.106.0/lib/roda/plugins/_base64.rb +34 -0
- data/roda-3.106.0/lib/roda/plugins/_before_hook.rb +41 -0
- data/roda-3.106.0/lib/roda/plugins/_optimized_matching.rb +227 -0
- data/roda-3.106.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
- data/roda-3.106.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
- data/roda-3.106.0/lib/roda/plugins/additional_render_engines.rb +61 -0
- data/roda-3.106.0/lib/roda/plugins/additional_view_directories.rb +66 -0
- data/roda-3.106.0/lib/roda/plugins/all_verbs.rb +50 -0
- data/roda-3.106.0/lib/roda/plugins/assets.rb +902 -0
- data/roda-3.106.0/lib/roda/plugins/assets_preloading.rb +89 -0
- data/roda-3.106.0/lib/roda/plugins/assume_ssl.rb +28 -0
- data/roda-3.106.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
- data/roda-3.106.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
- data/roda-3.106.0/lib/roda/plugins/backtracking_array.rb +88 -0
- data/roda-3.106.0/lib/roda/plugins/bearer_token.rb +28 -0
- data/roda-3.106.0/lib/roda/plugins/branch_locals.rb +76 -0
- data/roda-3.106.0/lib/roda/plugins/break.rb +43 -0
- data/roda-3.106.0/lib/roda/plugins/caching.rb +206 -0
- data/roda-3.106.0/lib/roda/plugins/capture_erb.rb +94 -0
- data/roda-3.106.0/lib/roda/plugins/chunked.rb +333 -0
- data/roda-3.106.0/lib/roda/plugins/class_level_routing.rb +111 -0
- data/roda-3.106.0/lib/roda/plugins/class_matchers.rb +155 -0
- data/roda-3.106.0/lib/roda/plugins/common_logger.rb +82 -0
- data/roda-3.106.0/lib/roda/plugins/conditional_sessions.rb +67 -0
- data/roda-3.106.0/lib/roda/plugins/content_for.rb +105 -0
- data/roda-3.106.0/lib/roda/plugins/content_security_policy.rb +333 -0
- data/roda-3.106.0/lib/roda/plugins/cookie_flags.rb +157 -0
- data/roda-3.106.0/lib/roda/plugins/cookies.rb +51 -0
- data/roda-3.106.0/lib/roda/plugins/csrf.rb +81 -0
- data/roda-3.106.0/lib/roda/plugins/custom_block_results.rb +85 -0
- data/roda-3.106.0/lib/roda/plugins/custom_matchers.rb +89 -0
- data/roda-3.106.0/lib/roda/plugins/default_headers.rb +64 -0
- data/roda-3.106.0/lib/roda/plugins/default_status.rb +35 -0
- data/roda-3.106.0/lib/roda/plugins/delay_build.rb +18 -0
- data/roda-3.106.0/lib/roda/plugins/delegate.rb +77 -0
- data/roda-3.106.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
- data/roda-3.106.0/lib/roda/plugins/direct_call.rb +38 -0
- data/roda-3.106.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
- data/roda-3.106.0/lib/roda/plugins/drop_body.rb +43 -0
- data/roda-3.106.0/lib/roda/plugins/each_part.rb +87 -0
- data/roda-3.106.0/lib/roda/plugins/early_hints.rb +25 -0
- data/roda-3.106.0/lib/roda/plugins/empty_root.rb +49 -0
- data/roda-3.106.0/lib/roda/plugins/environments.rb +79 -0
- data/roda-3.106.0/lib/roda/plugins/erb_h.rb +43 -0
- data/roda-3.106.0/lib/roda/plugins/error_email.rb +152 -0
- data/roda-3.106.0/lib/roda/plugins/error_handler.rb +133 -0
- data/roda-3.106.0/lib/roda/plugins/error_mail.rb +149 -0
- data/roda-3.106.0/lib/roda/plugins/exception_page.rb +445 -0
- data/roda-3.106.0/lib/roda/plugins/filter_common_logger.rb +46 -0
- data/roda-3.106.0/lib/roda/plugins/flash.rb +119 -0
- data/roda-3.106.0/lib/roda/plugins/h.rb +59 -0
- data/roda-3.106.0/lib/roda/plugins/halt.rb +108 -0
- data/roda-3.106.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
- data/roda-3.106.0/lib/roda/plugins/hash_branches.rb +145 -0
- data/roda-3.106.0/lib/roda/plugins/hash_matcher.rb +39 -0
- data/roda-3.106.0/lib/roda/plugins/hash_paths.rb +128 -0
- data/roda-3.106.0/lib/roda/plugins/hash_public.rb +125 -0
- data/roda-3.106.0/lib/roda/plugins/hash_public_cache.rb +91 -0
- data/roda-3.106.0/lib/roda/plugins/hash_routes.rb +304 -0
- data/roda-3.106.0/lib/roda/plugins/head.rb +99 -0
- data/roda-3.106.0/lib/roda/plugins/header_matchers.rb +88 -0
- data/roda-3.106.0/lib/roda/plugins/heartbeat.rb +41 -0
- data/roda-3.106.0/lib/roda/plugins/hmac_paths.rb +413 -0
- data/roda-3.106.0/lib/roda/plugins/hooks.rb +92 -0
- data/roda-3.106.0/lib/roda/plugins/host_authorization.rb +155 -0
- data/roda-3.106.0/lib/roda/plugins/host_routing.rb +194 -0
- data/roda-3.106.0/lib/roda/plugins/hsts.rb +35 -0
- data/roda-3.106.0/lib/roda/plugins/indifferent_params.rb +126 -0
- data/roda-3.106.0/lib/roda/plugins/inject_erb.rb +33 -0
- data/roda-3.106.0/lib/roda/plugins/invalid_request_body.rb +107 -0
- data/roda-3.106.0/lib/roda/plugins/ip_from_header.rb +34 -0
- data/roda-3.106.0/lib/roda/plugins/json.rb +119 -0
- data/roda-3.106.0/lib/roda/plugins/json_parser.rb +107 -0
- data/roda-3.106.0/lib/roda/plugins/link_to.rb +83 -0
- data/roda-3.106.0/lib/roda/plugins/mail_processor.rb +629 -0
- data/roda-3.106.0/lib/roda/plugins/mailer.rb +285 -0
- data/roda-3.106.0/lib/roda/plugins/map_matcher.rb +48 -0
- data/roda-3.106.0/lib/roda/plugins/match_affix.rb +64 -0
- data/roda-3.106.0/lib/roda/plugins/match_hook.rb +34 -0
- data/roda-3.106.0/lib/roda/plugins/match_hook_args.rb +93 -0
- data/roda-3.106.0/lib/roda/plugins/middleware.rb +225 -0
- data/roda-3.106.0/lib/roda/plugins/middleware_stack.rb +101 -0
- data/roda-3.106.0/lib/roda/plugins/module_include.rb +96 -0
- data/roda-3.106.0/lib/roda/plugins/multi_public.rb +95 -0
- data/roda-3.106.0/lib/roda/plugins/multi_route.rb +156 -0
- data/roda-3.106.0/lib/roda/plugins/multi_run.rb +144 -0
- data/roda-3.106.0/lib/roda/plugins/multi_view.rb +83 -0
- data/roda-3.106.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
- data/roda-3.106.0/lib/roda/plugins/named_routes.rb +167 -0
- data/roda-3.106.0/lib/roda/plugins/named_templates.rb +102 -0
- data/roda-3.106.0/lib/roda/plugins/not_allowed.rb +151 -0
- data/roda-3.106.0/lib/roda/plugins/not_found.rb +55 -0
- data/roda-3.106.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
- data/roda-3.106.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
- data/roda-3.106.0/lib/roda/plugins/padrino_render.rb +42 -0
- data/roda-3.106.0/lib/roda/plugins/param_matchers.rb +83 -0
- data/roda-3.106.0/lib/roda/plugins/params_capturing.rb +118 -0
- data/roda-3.106.0/lib/roda/plugins/part.rb +76 -0
- data/roda-3.106.0/lib/roda/plugins/partials.rb +74 -0
- data/roda-3.106.0/lib/roda/plugins/pass.rb +47 -0
- data/roda-3.106.0/lib/roda/plugins/path.rb +255 -0
- data/roda-3.106.0/lib/roda/plugins/path_matchers.rb +54 -0
- data/roda-3.106.0/lib/roda/plugins/path_rewriter.rb +111 -0
- data/roda-3.106.0/lib/roda/plugins/permissions_policy.rb +340 -0
- data/roda-3.106.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
- data/roda-3.106.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
- data/roda-3.106.0/lib/roda/plugins/precompile_templates.rb +156 -0
- data/roda-3.106.0/lib/roda/plugins/public.rb +182 -0
- data/roda-3.106.0/lib/roda/plugins/r.rb +35 -0
- data/roda-3.106.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
- data/roda-3.106.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
- data/roda-3.106.0/lib/roda/plugins/redirect_path.rb +59 -0
- data/roda-3.106.0/lib/roda/plugins/relative_path.rb +76 -0
- data/roda-3.106.0/lib/roda/plugins/render.rb +998 -0
- data/roda-3.106.0/lib/roda/plugins/render_coverage.rb +105 -0
- data/roda-3.106.0/lib/roda/plugins/render_each.rb +224 -0
- data/roda-3.106.0/lib/roda/plugins/render_locals.rb +102 -0
- data/roda-3.106.0/lib/roda/plugins/request_aref.rb +77 -0
- data/roda-3.106.0/lib/roda/plugins/request_headers.rb +83 -0
- data/roda-3.106.0/lib/roda/plugins/response_attachment.rb +94 -0
- data/roda-3.106.0/lib/roda/plugins/response_content_type.rb +79 -0
- data/roda-3.106.0/lib/roda/plugins/response_request.rb +36 -0
- data/roda-3.106.0/lib/roda/plugins/route_block_args.rb +50 -0
- data/roda-3.106.0/lib/roda/plugins/route_csrf.rb +388 -0
- data/roda-3.106.0/lib/roda/plugins/run_append_slash.rb +51 -0
- data/roda-3.106.0/lib/roda/plugins/run_handler.rb +51 -0
- data/roda-3.106.0/lib/roda/plugins/run_require_slash.rb +46 -0
- data/roda-3.106.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
- data/roda-3.106.0/lib/roda/plugins/send_file.rb +127 -0
- data/roda-3.106.0/lib/roda/plugins/sessions.rb +537 -0
- data/roda-3.106.0/lib/roda/plugins/shape_friendly.rb +232 -0
- data/roda-3.106.0/lib/roda/plugins/shared_vars.rb +86 -0
- data/roda-3.106.0/lib/roda/plugins/sinatra_helpers.rb +444 -0
- data/roda-3.106.0/lib/roda/plugins/slash_path_empty.rb +26 -0
- data/roda-3.106.0/lib/roda/plugins/static.rb +42 -0
- data/roda-3.106.0/lib/roda/plugins/static_routing.rb +89 -0
- data/roda-3.106.0/lib/roda/plugins/status_303.rb +34 -0
- data/roda-3.106.0/lib/roda/plugins/status_handler.rb +86 -0
- data/roda-3.106.0/lib/roda/plugins/streaming.rb +170 -0
- data/roda-3.106.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
- data/roda-3.106.0/lib/roda/plugins/symbol_matchers.rb +188 -0
- data/roda-3.106.0/lib/roda/plugins/symbol_status.rb +32 -0
- data/roda-3.106.0/lib/roda/plugins/symbol_views.rb +37 -0
- data/roda-3.106.0/lib/roda/plugins/timestamp_public.rb +78 -0
- data/roda-3.106.0/lib/roda/plugins/type_routing.rb +217 -0
- data/roda-3.106.0/lib/roda/plugins/typecast_params.rb +1185 -0
- data/roda-3.106.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
- data/roda-3.106.0/lib/roda/plugins/unescape_path.rb +41 -0
- data/roda-3.106.0/lib/roda/plugins/view_options.rb +203 -0
- data/roda-3.106.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
- data/roda-3.106.0/lib/roda/plugins.rb +68 -0
- data/roda-3.106.0/lib/roda/request.rb +722 -0
- data/roda-3.106.0/lib/roda/response.rb +221 -0
- data/roda-3.106.0/lib/roda/session_middleware.rb +184 -0
- data/roda-3.106.0/lib/roda/version.rb +18 -0
- data/roda-3.106.0/lib/roda.rb +741 -0
- data/ultra-max-sys.gemspec +12 -0
- 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
|