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,445 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The exception_page plugin provides an exception_page method that is designed
|
|
7
|
+
# to be called inside the error handler to provide a page to the developer
|
|
8
|
+
# with debugging information. It should only be used in developer environments
|
|
9
|
+
# with trusted clients, as it can leak source code and other information that
|
|
10
|
+
# may be useful for attackers if used in other environments.
|
|
11
|
+
#
|
|
12
|
+
# Example:
|
|
13
|
+
#
|
|
14
|
+
# plugin :exception_page
|
|
15
|
+
# plugin :error_handler do |e|
|
|
16
|
+
# next exception_page(e) if ENV['RACK_ENV'] == 'development'
|
|
17
|
+
# # ...
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# The exception_page plugin is based on Rack::ShowExceptions, with the following
|
|
21
|
+
# differences:
|
|
22
|
+
#
|
|
23
|
+
# * Not a middleware, so it doesn't handle exceptions itself, and has no effect
|
|
24
|
+
# on the callstack unless the exception_page method is called.
|
|
25
|
+
# * Supports external javascript and stylesheets, allowing context toggling to
|
|
26
|
+
# work in applications that use a content security policy to restrict inline
|
|
27
|
+
# javascript and stylesheets (:assets, :css_file, and :js_file options).
|
|
28
|
+
# * Has fewer dependencies (does not require ostruct and erb).
|
|
29
|
+
# * Sets the Content-Type for the response, and returns the body string, but does
|
|
30
|
+
# not modify other headers or the response status.
|
|
31
|
+
# * Supports a configurable amount of context lines in backtraces (:context option).
|
|
32
|
+
# * Supports optional JSON formatted output, if used with the json plugin (:json option).
|
|
33
|
+
#
|
|
34
|
+
# To use the external javascript and stylesheets, you can call +r.exception_page_assets+
|
|
35
|
+
# in your routing tree:
|
|
36
|
+
#
|
|
37
|
+
# route do |r|
|
|
38
|
+
# # ...
|
|
39
|
+
#
|
|
40
|
+
# # serve GET /exception_page.{css,js} requests
|
|
41
|
+
# # Use with assets: true +exception_page+ option
|
|
42
|
+
# r.exception_page_assets
|
|
43
|
+
#
|
|
44
|
+
# r.on "static" do
|
|
45
|
+
# # serve GET /static/exception_page.{css,js} requests
|
|
46
|
+
# # Use with assets: '/static' +exception_page+ option
|
|
47
|
+
# r.exception_page_assets
|
|
48
|
+
# end
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# It's also possible to store the asset information in static files and serve those,
|
|
52
|
+
# you can get the current assets by calling:
|
|
53
|
+
#
|
|
54
|
+
# Roda::RodaPlugins::ExceptionPage.css
|
|
55
|
+
# Roda::RodaPlugins::ExceptionPage.js
|
|
56
|
+
#
|
|
57
|
+
# As the exception_page plugin is based on Rack::ShowExceptions, it is also under
|
|
58
|
+
# rack's license:
|
|
59
|
+
#
|
|
60
|
+
# Copyright (C) 2007-2018 Christian Neukirchen <http://chneukirchen.org/infopage.html>
|
|
61
|
+
#
|
|
62
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
63
|
+
# of this software and associated documentation files (the "Software"), to
|
|
64
|
+
# deal in the Software without restriction, including without limitation the
|
|
65
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
66
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
|
67
|
+
# furnished to do so, subject to the following conditions:
|
|
68
|
+
#
|
|
69
|
+
# The above copyright notice and this permission notice shall be included in
|
|
70
|
+
# all copies or substantial portions of the Software.
|
|
71
|
+
#
|
|
72
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
73
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
74
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
75
|
+
# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
76
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
77
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
78
|
+
#
|
|
79
|
+
# The HTML template used in Rack::ShowExceptions was based on Django's
|
|
80
|
+
# template and is under the following license:
|
|
81
|
+
#
|
|
82
|
+
# adapted from Django <www.djangoproject.com>
|
|
83
|
+
# Copyright (c) Django Software Foundation and individual contributors.
|
|
84
|
+
# Used under the modified BSD license:
|
|
85
|
+
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
|
86
|
+
module ExceptionPage
|
|
87
|
+
def self.load_dependencies(app)
|
|
88
|
+
app.plugin :h
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Stylesheet used by the HTML exception page
|
|
92
|
+
def self.css
|
|
93
|
+
<<END
|
|
94
|
+
html * { padding:0; margin:0; }
|
|
95
|
+
body * { padding:10px 20px; }
|
|
96
|
+
body * * { padding:0; }
|
|
97
|
+
body { font:small sans-serif; }
|
|
98
|
+
body>div { border-bottom:1px solid #ddd; }
|
|
99
|
+
h1 { font-weight:normal; }
|
|
100
|
+
h2 { margin-bottom:.8em; }
|
|
101
|
+
h2 span { font-size:80%; color:#666; font-weight:normal; }
|
|
102
|
+
h3 { margin:1em 0 .5em 0; }
|
|
103
|
+
h4 { margin:0 0 .5em 0; font-weight: normal; }
|
|
104
|
+
table {
|
|
105
|
+
border:1px solid #ccc; border-collapse: collapse; background:white; }
|
|
106
|
+
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
|
|
107
|
+
thead th {
|
|
108
|
+
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
|
|
109
|
+
font-weight:normal; font-size:11px; border:1px solid #ddd; }
|
|
110
|
+
tbody th { text-align:right; color:#666; padding-right:.5em; }
|
|
111
|
+
table.vars { margin:5px 0 2px 40px; }
|
|
112
|
+
table.vars td, table.req td { font-family:monospace; }
|
|
113
|
+
table td.code { width:100%;}
|
|
114
|
+
table td.code div { overflow:hidden; }
|
|
115
|
+
table.source th { color:#666; }
|
|
116
|
+
table.source td {
|
|
117
|
+
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
|
118
|
+
ul.traceback { list-style-type:none; }
|
|
119
|
+
ul.traceback li.frame { margin-bottom:1em; }
|
|
120
|
+
div.context { margin: 10px 0; }
|
|
121
|
+
div.context ol {
|
|
122
|
+
padding-left:30px; margin:0 10px; list-style-position: inside; }
|
|
123
|
+
div.context ol li {
|
|
124
|
+
font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
|
|
125
|
+
div.context ol.context-line li { color:black; background-color:#ccc; }
|
|
126
|
+
div.context ol.context-line li span { float: right; }
|
|
127
|
+
div.commands { margin-left: 40px; }
|
|
128
|
+
div.commands a { color:black; text-decoration:none; }
|
|
129
|
+
#summary { background: #ffc; }
|
|
130
|
+
#summary h2 { font-weight: normal; color: #666; font-family: monospace; white-space: pre-wrap;}
|
|
131
|
+
#summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
|
|
132
|
+
#summary ul#quicklinks li { float: left; padding: 0 1em; }
|
|
133
|
+
#summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
|
|
134
|
+
#explanation { background:#eee; }
|
|
135
|
+
#traceback { background:#eee; }
|
|
136
|
+
#requestinfo { background:#f6f6f6; padding-left:120px; }
|
|
137
|
+
#summary table { border:none; background:transparent; }
|
|
138
|
+
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
|
139
|
+
#requestinfo h3 { margin-bottom:-1em; }
|
|
140
|
+
.error { background: #ffc; }
|
|
141
|
+
.specific { color:#cc3300; font-weight:bold; }
|
|
142
|
+
END
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Javascript used by the HTML exception page for context toggling
|
|
146
|
+
def self.js
|
|
147
|
+
<<END
|
|
148
|
+
var contexts = document.getElementsByClassName('context');
|
|
149
|
+
var num_contexts = contexts.length;
|
|
150
|
+
function toggle() {
|
|
151
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
152
|
+
var e = document.getElementById(arguments[i]);
|
|
153
|
+
if (e) {
|
|
154
|
+
e.style.display = e.style.display == 'none' ? 'block' : 'none';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
for (var j = 0; j < num_contexts; j++) {
|
|
160
|
+
contexts[j].onclick = function(){toggle('b'+this.id, 'a'+this.id);}
|
|
161
|
+
contexts[j].onclick();
|
|
162
|
+
}
|
|
163
|
+
END
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
module InstanceMethods
|
|
167
|
+
# Return a HTML page showing the exception, allowing a developer
|
|
168
|
+
# more information for debugging. Designed to be called inside
|
|
169
|
+
# an exception handler, passing in the received exception.
|
|
170
|
+
# Sets the Content-Type header in the response, and returns the
|
|
171
|
+
# string used for the body. If the Accept request header is present
|
|
172
|
+
# and text/html is accepted, return an HTML page with the backtrace
|
|
173
|
+
# with the ability to see the context for each backtrace line, as
|
|
174
|
+
# well as the GET, POST, cookie, and rack environment data. If text/html
|
|
175
|
+
# is not accepted, then just show a plain text page with the exception
|
|
176
|
+
# class, message, and backtrace.
|
|
177
|
+
#
|
|
178
|
+
# Options:
|
|
179
|
+
#
|
|
180
|
+
# :assets :: If +true+, sets :css_file to +/exception_page.css+ and :js_file to
|
|
181
|
+
# +/exception_page.js+, assuming that +r.exception_page_assets+ is called
|
|
182
|
+
# in the route block to serve the exception page assets. If a String,
|
|
183
|
+
# uses the string as a prefix, assuming that +r.exception_page_assets+
|
|
184
|
+
# is called in a nested block inside the route block. If false, doesn't
|
|
185
|
+
# use any CSS or JS.
|
|
186
|
+
# :context :: The number of context lines before and after each line in
|
|
187
|
+
# the backtrace (default: 7).
|
|
188
|
+
# :css_file :: A path to the external CSS file for the HTML exception page. If false,
|
|
189
|
+
# doesn't use any CSS.
|
|
190
|
+
# :js_file :: A path to the external javascript file for the HTML exception page. If
|
|
191
|
+
# false, doesn't use any JS.
|
|
192
|
+
# :json :: Return a hash of exception information. The hash will have
|
|
193
|
+
# a single key, "exception", with a value being a hash with
|
|
194
|
+
# three keys, "class", "message", and "backtrace", which
|
|
195
|
+
# contain information derived from the given exception.
|
|
196
|
+
# Designed to be used with the +json+ exception, which will
|
|
197
|
+
# automatically convert the hash to JSON format.
|
|
198
|
+
def exception_page(exception, opts=OPTS)
|
|
199
|
+
message = exception_page_exception_message(exception)
|
|
200
|
+
if opts[:json]
|
|
201
|
+
@_response[RodaResponseHeaders::CONTENT_TYPE] = "application/json"
|
|
202
|
+
{
|
|
203
|
+
"exception"=>{
|
|
204
|
+
"class"=>exception.class.to_s,
|
|
205
|
+
"message"=>message,
|
|
206
|
+
"backtrace"=>exception.backtrace.map(&:to_s)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
elsif env['HTTP_ACCEPT'] =~ /text\/html/
|
|
210
|
+
@_response[RodaResponseHeaders::CONTENT_TYPE] = "text/html"
|
|
211
|
+
|
|
212
|
+
context = opts[:context] || 7
|
|
213
|
+
css_file = opts[:css_file]
|
|
214
|
+
js_file = opts[:js_file]
|
|
215
|
+
|
|
216
|
+
case prefix = opts[:assets]
|
|
217
|
+
when false
|
|
218
|
+
css_file = false if css_file.nil?
|
|
219
|
+
js_file = false if js_file.nil?
|
|
220
|
+
when nil
|
|
221
|
+
# nothing
|
|
222
|
+
else
|
|
223
|
+
prefix = '' if prefix == true
|
|
224
|
+
css_file ||= "#{prefix}/exception_page.css"
|
|
225
|
+
js_file ||= "#{prefix}/exception_page.js"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
css = case css_file
|
|
229
|
+
when nil
|
|
230
|
+
"<style type=\"text/css\">#{exception_page_css}</style>"
|
|
231
|
+
when false
|
|
232
|
+
# :nothing
|
|
233
|
+
else
|
|
234
|
+
"<link rel=\"stylesheet\" href=\"#{h css_file}\" />"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
js = case js_file
|
|
238
|
+
when nil
|
|
239
|
+
"<script type=\"text/javascript\">\n//<!--\n#{exception_page_js}\n//-->\n</script>"
|
|
240
|
+
when false
|
|
241
|
+
# :nothing
|
|
242
|
+
else
|
|
243
|
+
"<script type=\"text/javascript\" src=\"#{h js_file}\"></script>"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
frames = exception.backtrace.map.with_index do |line, i|
|
|
247
|
+
frame = {:id=>i}
|
|
248
|
+
if line =~ /\A(.*?):(\d+)(?::in [`'](.*)')?\Z/
|
|
249
|
+
filename = frame[:filename] = $1
|
|
250
|
+
lineno = frame[:lineno] = $2.to_i
|
|
251
|
+
frame[:function] = $3
|
|
252
|
+
|
|
253
|
+
begin
|
|
254
|
+
lineno -= 1
|
|
255
|
+
lines = ::File.readlines(filename)
|
|
256
|
+
if line = lines[lineno]
|
|
257
|
+
pre_lineno = [lineno-context, 0].max
|
|
258
|
+
if (pre_context = lines[pre_lineno...lineno]) && !pre_context.empty?
|
|
259
|
+
frame[:pre_context_lineno] = pre_lineno
|
|
260
|
+
frame[:pre_context] = pre_context
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
post_lineno = [lineno+context, lines.size].min
|
|
264
|
+
if (post_context = lines[lineno+1..post_lineno]) && !post_context.empty?
|
|
265
|
+
frame[:post_context_lineno] = post_lineno
|
|
266
|
+
frame[:post_context] = post_context
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
frame[:context_line] = line.chomp
|
|
270
|
+
end
|
|
271
|
+
rescue
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
frame
|
|
275
|
+
end
|
|
276
|
+
end.compact
|
|
277
|
+
|
|
278
|
+
r = @_request
|
|
279
|
+
begin
|
|
280
|
+
post_data = r.POST
|
|
281
|
+
missing_post = "No POST data"
|
|
282
|
+
rescue
|
|
283
|
+
missing_post = "Invalid POST data"
|
|
284
|
+
end
|
|
285
|
+
info = lambda do |title, id, var, none|
|
|
286
|
+
<<END
|
|
287
|
+
<h3 id="#{id}">#{title}</h3>
|
|
288
|
+
#{(var && !var.empty?) ? (<<END1) : "<p>#{none}</p>"
|
|
289
|
+
<table class="req">
|
|
290
|
+
<thead>
|
|
291
|
+
<tr>
|
|
292
|
+
<th>Variable</th>
|
|
293
|
+
<th>Value</th>
|
|
294
|
+
</tr>
|
|
295
|
+
</thead>
|
|
296
|
+
<tbody>
|
|
297
|
+
#{var.sort_by{|k, _| k.to_s}.map{|key, val| (<<END2)}.join
|
|
298
|
+
<tr>
|
|
299
|
+
<td>#{h key}</td>
|
|
300
|
+
<td class="code"><div>#{h val.inspect}</div></td>
|
|
301
|
+
</tr>
|
|
302
|
+
END2
|
|
303
|
+
}
|
|
304
|
+
</tbody>
|
|
305
|
+
</table>
|
|
306
|
+
END1
|
|
307
|
+
}
|
|
308
|
+
END
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
<<END
|
|
312
|
+
<!DOCTYPE html>
|
|
313
|
+
<html lang="en">
|
|
314
|
+
<head>
|
|
315
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
316
|
+
<title>#{h exception.class} at #{h r.path}</title>
|
|
317
|
+
#{css}
|
|
318
|
+
</head>
|
|
319
|
+
<body>
|
|
320
|
+
|
|
321
|
+
<div id="summary">
|
|
322
|
+
<h1>#{h exception.class} at #{h r.path}</h1>
|
|
323
|
+
<h2>#{h message}</h2>
|
|
324
|
+
<table><tr>
|
|
325
|
+
<th>Ruby</th>
|
|
326
|
+
<td>
|
|
327
|
+
#{(first = frames.first) ? "<code>#{h first[:filename]}</code>: in <code>#{h first[:function]}</code>, line #{first[:lineno]}" : "unknown location"}
|
|
328
|
+
</td>
|
|
329
|
+
</tr><tr>
|
|
330
|
+
<th>Web</th>
|
|
331
|
+
<td><code>#{r.request_method} #{h r.host}#{h r.path}</code></td>
|
|
332
|
+
</tr></table>
|
|
333
|
+
|
|
334
|
+
<h3>Jump to:</h3>
|
|
335
|
+
<ul id="quicklinks">
|
|
336
|
+
<li><a href="#get-info">GET</a></li>
|
|
337
|
+
<li><a href="#post-info">POST</a></li>
|
|
338
|
+
<li><a href="#cookie-info">Cookies</a></li>
|
|
339
|
+
<li><a href="#env-info">ENV</a></li>
|
|
340
|
+
</ul>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div id="traceback">
|
|
344
|
+
<h2>Traceback <span>(innermost first)</span></h2>
|
|
345
|
+
<ul class="traceback">
|
|
346
|
+
#{frames.map{|frame| id = frame[:id]; (<<END1)}.join
|
|
347
|
+
<li class="frame">
|
|
348
|
+
<code>#{h frame[:filename]}:#{frame[:lineno]}</code> in <code>#{h frame[:function]}</code>
|
|
349
|
+
|
|
350
|
+
#{frame[:context_line] ? (<<END2) : '</li>'
|
|
351
|
+
<div class="context" id="c#{id}">
|
|
352
|
+
#{frame[:pre_context] ? (<<END3) : ''
|
|
353
|
+
<ol start="#{frame[:pre_context_lineno]+1}" id="bc#{id}">
|
|
354
|
+
#{frame[:pre_context].map{|line| "<li>#{h line}</li>"}.join}
|
|
355
|
+
</ol>
|
|
356
|
+
END3
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
<ol start="#{frame[:lineno]}" class="context-line">
|
|
360
|
+
<li>#{h frame[:context_line]}<span>...</span></li>
|
|
361
|
+
</ol>
|
|
362
|
+
|
|
363
|
+
#{frame[:post_context] ? (<<END4) : ''
|
|
364
|
+
<ol start='#{frame[:lineno]+1}' id="ac#{id}">
|
|
365
|
+
#{frame[:post_context].map{|line| "<li>#{h line}</li>"}.join}
|
|
366
|
+
</ol>
|
|
367
|
+
END4
|
|
368
|
+
}
|
|
369
|
+
</div>
|
|
370
|
+
END2
|
|
371
|
+
}
|
|
372
|
+
END1
|
|
373
|
+
}
|
|
374
|
+
</ul>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<div id="requestinfo">
|
|
378
|
+
<h2>Request information</h2>
|
|
379
|
+
|
|
380
|
+
#{info.call('GET', 'get-info', r.GET, 'No GET data')}
|
|
381
|
+
#{info.call('POST', 'post-info', post_data, missing_post)}
|
|
382
|
+
#{info.call('Cookies', 'cookie-info', r.cookies, 'No cookie data')}
|
|
383
|
+
#{info.call('Rack ENV', 'env-info', r.env, 'No Rack env?')}
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
<div id="explanation">
|
|
387
|
+
<p>
|
|
388
|
+
You're seeing this error because you use the Roda exception_page plugin.
|
|
389
|
+
</p>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
#{js}
|
|
393
|
+
</body>
|
|
394
|
+
</html>
|
|
395
|
+
END
|
|
396
|
+
else
|
|
397
|
+
@_response[RodaResponseHeaders::CONTENT_TYPE] = "text/plain"
|
|
398
|
+
"#{exception.class}: #{message}\n#{exception.backtrace.map{|l| "\t#{l}"}.join("\n")}"
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# The CSS to use on the exception page
|
|
403
|
+
def exception_page_css
|
|
404
|
+
ExceptionPage.css
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# The JavaScript to use on the exception page
|
|
408
|
+
def exception_page_js
|
|
409
|
+
ExceptionPage.js
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
private
|
|
413
|
+
|
|
414
|
+
if Exception.method_defined?(:detailed_message)
|
|
415
|
+
def exception_page_exception_message(exception)
|
|
416
|
+
exception.detailed_message(highlight: false).to_s
|
|
417
|
+
end
|
|
418
|
+
# :nocov:
|
|
419
|
+
else
|
|
420
|
+
# Return message to use for exception.
|
|
421
|
+
def exception_page_exception_message(exception)
|
|
422
|
+
exception.message.to_s
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
# :nocov:
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
module RequestMethods
|
|
429
|
+
# Serve exception page assets
|
|
430
|
+
def exception_page_assets
|
|
431
|
+
get 'exception_page.css' do
|
|
432
|
+
response[RodaResponseHeaders::CONTENT_TYPE] = "text/css"
|
|
433
|
+
scope.exception_page_css
|
|
434
|
+
end
|
|
435
|
+
get 'exception_page.js' do
|
|
436
|
+
response[RodaResponseHeaders::CONTENT_TYPE] = "application/javascript"
|
|
437
|
+
scope.exception_page_js
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
register_plugin(:exception_page, ExceptionPage)
|
|
444
|
+
end
|
|
445
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The skip_common_logger plugin allows for skipping common_logger logging
|
|
7
|
+
# of some requests. You pass a block when loading the plugin, and the
|
|
8
|
+
# block will be called before logging each request. The block should return
|
|
9
|
+
# whether the request should be logged.
|
|
10
|
+
#
|
|
11
|
+
# Example:
|
|
12
|
+
#
|
|
13
|
+
# # Only log server errors
|
|
14
|
+
# plugin :filter_common_logger do |result|
|
|
15
|
+
# result[0] >= 500
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # Don't log requests to certain paths
|
|
19
|
+
# plugin :filter_common_logger do |_|
|
|
20
|
+
# # Block is called in the same context as the route block
|
|
21
|
+
# !request.path.start_with?('/admin/')
|
|
22
|
+
# end
|
|
23
|
+
module FilterCommonLogger
|
|
24
|
+
def self.load_dependencies(app, &_)
|
|
25
|
+
app.plugin :common_logger
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.configure(app, &block)
|
|
29
|
+
app.send(:define_method, :_common_log_request?, &block)
|
|
30
|
+
app.send(:private, :_common_log_request?)
|
|
31
|
+
app.send(:alias_method, :_common_log_request?, :_common_log_request?)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module InstanceMethods
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Log request/response information in common log format to logger.
|
|
38
|
+
def _roda_after_90__common_logger(result)
|
|
39
|
+
super if result && _common_log_request?(result)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
register_plugin(:filter_common_logger, FilterCommonLogger)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require 'delegate'
|
|
4
|
+
|
|
5
|
+
class Roda
|
|
6
|
+
module RodaPlugins
|
|
7
|
+
# The flash plugin adds a +flash+ instance method to Roda,
|
|
8
|
+
# for typical web application flash handling, where values
|
|
9
|
+
# set in the current flash hash are available in the next
|
|
10
|
+
# request.
|
|
11
|
+
#
|
|
12
|
+
# With the example below, if a POST request is submitted,
|
|
13
|
+
# it will redirect and the resulting GET request will
|
|
14
|
+
# return 'b'.
|
|
15
|
+
#
|
|
16
|
+
# plugin :flash
|
|
17
|
+
#
|
|
18
|
+
# route do |r|
|
|
19
|
+
# r.is '' do
|
|
20
|
+
# r.get do
|
|
21
|
+
# flash['a']
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# r.post do
|
|
25
|
+
# flash['a'] = 'b'
|
|
26
|
+
# r.redirect('')
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# You can modify the flash for the current request (instead of
|
|
32
|
+
# the next request) by using the +flash.now+ method:
|
|
33
|
+
#
|
|
34
|
+
# r.get do
|
|
35
|
+
# flash.now['a'] = 'b'
|
|
36
|
+
# flash['a'] # = >'b'
|
|
37
|
+
# end
|
|
38
|
+
module Flash
|
|
39
|
+
SCOPE_INSTANCE_VARIABLES = [:@_flash].freeze
|
|
40
|
+
|
|
41
|
+
# Simple flash hash, where assiging to the hash updates the flash
|
|
42
|
+
# used in the following request.
|
|
43
|
+
class FlashHash < DelegateClass(Hash)
|
|
44
|
+
# The flash hash for the next request. This
|
|
45
|
+
# is what gets written to by #[]=.
|
|
46
|
+
attr_reader :next
|
|
47
|
+
|
|
48
|
+
# The flash hash for the current request
|
|
49
|
+
alias now __getobj__
|
|
50
|
+
|
|
51
|
+
# Setup the next hash when initializing, and handle treat nil
|
|
52
|
+
# as a new empty hash.
|
|
53
|
+
def initialize(hash={})
|
|
54
|
+
super(hash||{})
|
|
55
|
+
@next = {}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Update the next hash with the given key and value.
|
|
59
|
+
def []=(k, v)
|
|
60
|
+
@next[k] = v
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Remove given key from the next hash, or clear the next hash if
|
|
64
|
+
# no argument is given.
|
|
65
|
+
def discard(key=(no_arg=true))
|
|
66
|
+
if no_arg
|
|
67
|
+
@next.clear
|
|
68
|
+
else
|
|
69
|
+
@next.delete(key)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Copy the entry with the given key from the current hash to the
|
|
74
|
+
# next hash, or copy all entries from the current hash to the
|
|
75
|
+
# next hash if no argument is given.
|
|
76
|
+
def keep(key=(no_arg=true))
|
|
77
|
+
if no_arg
|
|
78
|
+
@next.merge!(self)
|
|
79
|
+
else
|
|
80
|
+
self[key] = self[key]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Replace the current hash with the next hash and clear the next hash.
|
|
85
|
+
def sweep
|
|
86
|
+
replace(@next)
|
|
87
|
+
@next.clear
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
module InstanceMethods
|
|
93
|
+
# Access the flash hash for the current request, loading
|
|
94
|
+
# it from the session if it is not already loaded.
|
|
95
|
+
def flash
|
|
96
|
+
# :_flash to support transparent upgrades from previous key
|
|
97
|
+
@_flash ||= FlashHash.new(session['_flash'] || (session['_flash'] = session.delete(:_flash)))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
# If the routing doesn't raise an error, rotate the flash
|
|
103
|
+
# hash in the session so the next request has access to it.
|
|
104
|
+
def _roda_after_40__flash(_)
|
|
105
|
+
if f = @_flash
|
|
106
|
+
f = f.next
|
|
107
|
+
if f.empty?
|
|
108
|
+
session.delete('_flash')
|
|
109
|
+
else
|
|
110
|
+
session['_flash'] = f
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
register_plugin(:flash, Flash)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The h plugin adds an +h+ instance method that will HTML
|
|
7
|
+
# escape the input and return it.
|
|
8
|
+
#
|
|
9
|
+
# The following example will return "<foo>" as the body.
|
|
10
|
+
#
|
|
11
|
+
# plugin :h
|
|
12
|
+
#
|
|
13
|
+
# route do |r|
|
|
14
|
+
# h('<foo>')
|
|
15
|
+
# end
|
|
16
|
+
module H
|
|
17
|
+
begin
|
|
18
|
+
require 'cgi/escape'
|
|
19
|
+
unless CGI.respond_to?(:escapeHTML) # work around for JRuby 9.1
|
|
20
|
+
# :nocov:
|
|
21
|
+
CGI = Object.new
|
|
22
|
+
CGI.extend(defined?(::CGI::Escape) ? ::CGI::Escape : ::CGI::Util)
|
|
23
|
+
# :nocov:
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module InstanceMethods
|
|
27
|
+
# HTML escape the input and return the escaped version.
|
|
28
|
+
def h(string)
|
|
29
|
+
CGI.escapeHTML(string.to_s)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
rescue LoadError
|
|
33
|
+
# :nocov:
|
|
34
|
+
|
|
35
|
+
# A Hash of entities and their escaped equivalents,
|
|
36
|
+
# to be escaped by h().
|
|
37
|
+
ESCAPE_HTML = {
|
|
38
|
+
"&" => "&".freeze,
|
|
39
|
+
"<" => "<".freeze,
|
|
40
|
+
">" => ">".freeze,
|
|
41
|
+
"'" => "'".freeze,
|
|
42
|
+
'"' => """.freeze,
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
# A Regexp of HTML entities to match for escaping.
|
|
46
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
|
47
|
+
|
|
48
|
+
module InstanceMethods
|
|
49
|
+
def h(string)
|
|
50
|
+
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
# :nocov:
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
register_plugin(:h, H)
|
|
58
|
+
end
|
|
59
|
+
end
|