homura-runtime 0.3.2 → 0.3.4

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/exe/compile-assets +2 -2
  4. data/exe/compile-erb +5 -7
  5. data/lib/homura/runtime/build_support.rb +19 -2
  6. data/lib/homura/runtime/version.rb +1 -1
  7. data/vendor/rack/auth/abstract/handler.rb +41 -0
  8. data/vendor/rack/auth/abstract/request.rb +51 -0
  9. data/vendor/rack/auth/basic.rb +58 -0
  10. data/vendor/rack/bad_request.rb +8 -0
  11. data/vendor/rack/body_proxy.rb +63 -0
  12. data/vendor/rack/builder.rb +315 -0
  13. data/vendor/rack/cascade.rb +67 -0
  14. data/vendor/rack/common_logger.rb +94 -0
  15. data/vendor/rack/conditional_get.rb +87 -0
  16. data/vendor/rack/config.rb +22 -0
  17. data/vendor/rack/constants.rb +68 -0
  18. data/vendor/rack/content_length.rb +34 -0
  19. data/vendor/rack/content_type.rb +33 -0
  20. data/vendor/rack/deflater.rb +159 -0
  21. data/vendor/rack/directory.rb +210 -0
  22. data/vendor/rack/etag.rb +71 -0
  23. data/vendor/rack/events.rb +172 -0
  24. data/vendor/rack/files.rb +224 -0
  25. data/vendor/rack/head.rb +25 -0
  26. data/vendor/rack/headers.rb +238 -0
  27. data/vendor/rack/lint.rb +1000 -0
  28. data/vendor/rack/lock.rb +29 -0
  29. data/vendor/rack/media_type.rb +42 -0
  30. data/vendor/rack/method_override.rb +56 -0
  31. data/vendor/rack/mime.rb +694 -0
  32. data/vendor/rack/mock.rb +3 -0
  33. data/vendor/rack/mock_request.rb +161 -0
  34. data/vendor/rack/mock_response.rb +147 -0
  35. data/vendor/rack/multipart/generator.rb +99 -0
  36. data/vendor/rack/multipart/parser.rb +586 -0
  37. data/vendor/rack/multipart/uploaded_file.rb +82 -0
  38. data/vendor/rack/multipart.rb +77 -0
  39. data/vendor/rack/null_logger.rb +48 -0
  40. data/vendor/rack/protection/authenticity_token.rb +256 -0
  41. data/vendor/rack/protection/base.rb +140 -0
  42. data/vendor/rack/protection/content_security_policy.rb +80 -0
  43. data/vendor/rack/protection/cookie_tossing.rb +77 -0
  44. data/vendor/rack/protection/escaped_params.rb +93 -0
  45. data/vendor/rack/protection/form_token.rb +25 -0
  46. data/vendor/rack/protection/frame_options.rb +39 -0
  47. data/vendor/rack/protection/http_origin.rb +43 -0
  48. data/vendor/rack/protection/ip_spoofing.rb +27 -0
  49. data/vendor/rack/protection/json_csrf.rb +60 -0
  50. data/vendor/rack/protection/path_traversal.rb +45 -0
  51. data/vendor/rack/protection/referrer_policy.rb +27 -0
  52. data/vendor/rack/protection/remote_referrer.rb +22 -0
  53. data/vendor/rack/protection/remote_token.rb +24 -0
  54. data/vendor/rack/protection/session_hijacking.rb +37 -0
  55. data/vendor/rack/protection/strict_transport.rb +41 -0
  56. data/vendor/rack/protection/version.rb +7 -0
  57. data/vendor/rack/protection/xss_header.rb +27 -0
  58. data/vendor/rack/protection.rb +58 -0
  59. data/vendor/rack/query_parser.rb +261 -0
  60. data/vendor/rack/recursive.rb +66 -0
  61. data/vendor/rack/reloader.rb +112 -0
  62. data/vendor/rack/request.rb +818 -0
  63. data/vendor/rack/response.rb +403 -0
  64. data/vendor/rack/rewindable_input.rb +116 -0
  65. data/vendor/rack/runtime.rb +35 -0
  66. data/vendor/rack/sendfile.rb +197 -0
  67. data/vendor/rack/session/abstract/id.rb +533 -0
  68. data/vendor/rack/session/constants.rb +13 -0
  69. data/vendor/rack/session/cookie.rb +292 -0
  70. data/vendor/rack/session/encryptor.rb +415 -0
  71. data/vendor/rack/session/pool.rb +76 -0
  72. data/vendor/rack/session/version.rb +10 -0
  73. data/vendor/rack/session.rb +12 -0
  74. data/vendor/rack/show_exceptions.rb +433 -0
  75. data/vendor/rack/show_status.rb +121 -0
  76. data/vendor/rack/static.rb +188 -0
  77. data/vendor/rack/tempfile_reaper.rb +44 -0
  78. data/vendor/rack/urlmap.rb +99 -0
  79. data/vendor/rack/utils.rb +631 -0
  80. data/vendor/rack/version.rb +17 -0
  81. data/vendor/rack.rb +66 -0
  82. metadata +76 -1
@@ -0,0 +1,631 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
4
+ # homura patch: Opal's URI module does not provide RFC2396_PARSER /
5
+ # DEFAULT_PARSER, and `fileutils` / `tempfile` are stubbed. We use `cgi`
6
+ # for escape / unescape and provide a tiny CGI-backed URI_PARSER so the
7
+ # rest of rack/utils.rb continues to work unchanged.
8
+ require 'cgi'
9
+ # require 'uri'
10
+ # require 'fileutils'
11
+ require 'set'
12
+ require 'tempfile'
13
+ require 'time'
14
+ require 'erb'
15
+
16
+ require_relative 'query_parser'
17
+ require_relative 'mime'
18
+ require_relative 'headers'
19
+ require_relative 'constants'
20
+
21
+ module Rack
22
+ # Rack::Utils contains a grab-bag of useful methods for writing web
23
+ # applications adopted from all kinds of Ruby libraries.
24
+
25
+ module Utils
26
+ ParameterTypeError = QueryParser::ParameterTypeError
27
+ InvalidParameterError = QueryParser::InvalidParameterError
28
+ ParamsTooDeepError = QueryParser::ParamsTooDeepError
29
+ DEFAULT_SEP = QueryParser::DEFAULT_SEP
30
+ COMMON_SEP = QueryParser::COMMON_SEP
31
+ KeySpaceConstrainedParams = QueryParser::Params
32
+ # homura patch: CGI-backed URI_PARSER stand-in for Opal.
33
+ URI_PARSER = Module.new do
34
+ def self.escape(s); CGI.escape(s.to_s); end
35
+ def self.unescape(s); CGI.unescape(s.to_s); end
36
+ end
37
+
38
+ class << self
39
+ attr_accessor :default_query_parser
40
+ end
41
+ # The default amount of nesting to allowed by hash parameters.
42
+ # This helps prevent a rogue client from triggering a possible stack overflow
43
+ # when parsing parameters.
44
+ self.default_query_parser = QueryParser.make_default(32)
45
+
46
+ module_function
47
+
48
+ # URI escapes. (CGI style space to +)
49
+ def escape(s)
50
+ URI.encode_www_form_component(s)
51
+ end
52
+
53
+ # Like URI escaping, but with %20 instead of +. Strictly speaking this is
54
+ # true URI escaping.
55
+ def escape_path(s)
56
+ URI_PARSER.escape s
57
+ end
58
+
59
+ # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
60
+ # unescaping query parameters or form components.
61
+ def unescape_path(s)
62
+ URI_PARSER.unescape s
63
+ end
64
+
65
+ # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
66
+ # target encoding of the string returned, and it defaults to UTF-8
67
+ def unescape(s, encoding = Encoding::UTF_8)
68
+ URI.decode_www_form_component(s, encoding)
69
+ end
70
+
71
+ class << self
72
+ attr_accessor :multipart_total_part_limit
73
+
74
+ attr_accessor :multipart_file_limit
75
+
76
+ # multipart_part_limit is the original name of multipart_file_limit, but
77
+ # the limit only counts parts with filenames.
78
+ alias multipart_part_limit multipart_file_limit
79
+ alias multipart_part_limit= multipart_file_limit=
80
+ end
81
+
82
+ # The maximum number of file parts a request can contain. Accepting too
83
+ # many parts can lead to the server running out of file handles.
84
+ # Set to `0` for no limit.
85
+ self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
86
+
87
+ # The maximum total number of parts a request can contain. Accepting too
88
+ # many can lead to excessive memory use and parsing time.
89
+ self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
90
+
91
+ def self.param_depth_limit
92
+ default_query_parser.param_depth_limit
93
+ end
94
+
95
+ def self.param_depth_limit=(v)
96
+ self.default_query_parser = self.default_query_parser.new_depth_limit(v)
97
+ end
98
+
99
+ if defined?(Process::CLOCK_MONOTONIC)
100
+ def clock_time
101
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
102
+ end
103
+ else
104
+ # :nocov:
105
+ def clock_time
106
+ Time.now.to_f
107
+ end
108
+ # :nocov:
109
+ end
110
+
111
+ def parse_query(qs, d = nil, &unescaper)
112
+ Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
113
+ end
114
+
115
+ def parse_nested_query(qs, d = nil)
116
+ Rack::Utils.default_query_parser.parse_nested_query(qs, d)
117
+ end
118
+
119
+ def build_query(params)
120
+ params.map { |k, v|
121
+ if v.class == Array
122
+ build_query(v.map { |x| [k, x] })
123
+ else
124
+ v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
125
+ end
126
+ }.join("&")
127
+ end
128
+
129
+ def build_nested_query(value, prefix = nil)
130
+ case value
131
+ when Array
132
+ value.map { |v|
133
+ build_nested_query(v, "#{prefix}[]")
134
+ }.join("&")
135
+ when Hash
136
+ value.map { |k, v|
137
+ build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
138
+ }.delete_if(&:empty?).join('&')
139
+ when nil
140
+ escape(prefix)
141
+ else
142
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
143
+ "#{escape(prefix)}=#{escape(value)}"
144
+ end
145
+ end
146
+
147
+ def q_values(q_value_header)
148
+ q_value_header.to_s.split(',').map do |part|
149
+ value, parameters = part.split(';', 2).map(&:strip)
150
+ quality = 1.0
151
+ if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
152
+ quality = md[1].to_f
153
+ end
154
+ [value, quality]
155
+ end
156
+ end
157
+
158
+ def forwarded_values(forwarded_header)
159
+ return nil unless forwarded_header
160
+ forwarded_header = forwarded_header.to_s.gsub("\n", ";")
161
+
162
+ forwarded_header.split(';').each_with_object({}) do |field, values|
163
+ field.split(',').each do |pair|
164
+ pair = pair.split('=').map(&:strip).join('=')
165
+ return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\Z/i
166
+ (values[$1.downcase.to_sym] ||= []) << $2
167
+ end
168
+ end
169
+ end
170
+ module_function :forwarded_values
171
+
172
+ # Return best accept value to use, based on the algorithm
173
+ # in RFC 2616 Section 14. If there are multiple best
174
+ # matches (same specificity and quality), the value returned
175
+ # is arbitrary.
176
+ def best_q_match(q_value_header, available_mimes)
177
+ values = q_values(q_value_header)
178
+
179
+ matches = values.map do |req_mime, quality|
180
+ match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) }
181
+ next unless match
182
+ [match, quality]
183
+ end.compact.sort_by do |match, quality|
184
+ (match.split('/', 2).count('*') * -10) + quality
185
+ end.last
186
+ matches&.first
187
+ end
188
+
189
+ # Introduced in ERB 4.0. ERB::Escape is an alias for ERB::Utils which
190
+ # doesn't get monkey-patched by rails
191
+ if defined?(ERB::Escape) && ERB::Escape.instance_method(:html_escape)
192
+ define_method(:escape_html, ERB::Escape.instance_method(:html_escape))
193
+ # :nocov:
194
+ # Ruby 3.2/ERB 4.0 added ERB::Escape#html_escape, so the else
195
+ # branch cannot be hit on the current Ruby version.
196
+ else
197
+ require 'cgi/escape'
198
+ # Escape ampersands, brackets and quotes to their HTML/XML entities.
199
+ def escape_html(string)
200
+ CGI.escapeHTML(string.to_s)
201
+ end
202
+ # :nocov:
203
+ end
204
+
205
+ def select_best_encoding(available_encodings, accept_encoding)
206
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
207
+
208
+ expanded_accept_encoding = []
209
+
210
+ accept_encoding.each do |m, q|
211
+ preference = available_encodings.index(m) || available_encodings.size
212
+
213
+ if m == "*"
214
+ (available_encodings - accept_encoding.map(&:first)).each do |m2|
215
+ expanded_accept_encoding << [m2, q, preference]
216
+ end
217
+ else
218
+ expanded_accept_encoding << [m, q, preference]
219
+ end
220
+ end
221
+
222
+ encoding_candidates = expanded_accept_encoding
223
+ .sort_by { |_, q, p| [-q, p] }
224
+ .map!(&:first)
225
+
226
+ unless encoding_candidates.include?("identity")
227
+ encoding_candidates.push("identity")
228
+ end
229
+
230
+ expanded_accept_encoding.each do |m, q|
231
+ encoding_candidates.delete(m) if q == 0.0
232
+ end
233
+
234
+ (encoding_candidates & available_encodings)[0]
235
+ end
236
+
237
+ # :call-seq:
238
+ # parse_cookies_header(value) -> hash
239
+ #
240
+ # Parse cookies from the provided header +value+ according to RFC6265. The
241
+ # syntax for cookie headers only supports semicolons. Returns a map of
242
+ # cookie +key+ to cookie +value+.
243
+ #
244
+ # parse_cookies_header('myname=myvalue; max-age=0')
245
+ # # => {"myname"=>"myvalue", "max-age"=>"0"}
246
+ #
247
+ def parse_cookies_header(value)
248
+ return {} unless value
249
+
250
+ value.split(/; */n).each_with_object({}) do |cookie, cookies|
251
+ next if cookie.empty?
252
+ key, value = cookie.split('=', 2)
253
+ cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
254
+ end
255
+ end
256
+
257
+ # :call-seq:
258
+ # parse_cookies(env) -> hash
259
+ #
260
+ # Parse cookies from the provided request environment using
261
+ # parse_cookies_header. Returns a map of cookie +key+ to cookie +value+.
262
+ #
263
+ # parse_cookies({'HTTP_COOKIE' => 'myname=myvalue'})
264
+ # # => {'myname' => 'myvalue'}
265
+ #
266
+ def parse_cookies(env)
267
+ parse_cookies_header env[HTTP_COOKIE]
268
+ end
269
+
270
+ # A valid cookie key according to RFC6265 and RFC2616.
271
+ # A <cookie-name> can be any US-ASCII characters, except control characters, spaces, or tabs. It also must not contain a separator character like the following: ( ) < > @ , ; : \ " / [ ] ? = { }.
272
+ VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
273
+ private_constant :VALID_COOKIE_KEY
274
+
275
+ # :call-seq:
276
+ # set_cookie_header(key, value) -> encoded string
277
+ #
278
+ # Generate an encoded string using the provided +key+ and +value+ suitable
279
+ # for the +set-cookie+ header according to RFC6265. The +value+ may be an
280
+ # instance of either +String+ or +Hash+. If the cookie key is invalid (as
281
+ # defined by RFC6265), an +ArgumentError+ will be raised.
282
+ #
283
+ # If the cookie +value+ is an instance of +Hash+, it considers the following
284
+ # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
285
+ # of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
286
+ # details about the interpretation of these fields, consult
287
+ # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
288
+ #
289
+ # set_cookie_header("myname", "myvalue")
290
+ # # => "myname=myvalue"
291
+ #
292
+ # set_cookie_header("myname", {value: "myvalue", max_age: 10})
293
+ # # => "myname=myvalue; max-age=10"
294
+ #
295
+ def set_cookie_header(key, value)
296
+ unless key =~ VALID_COOKIE_KEY
297
+ raise ArgumentError, "invalid cookie key: #{key.inspect}"
298
+ end
299
+
300
+ case value
301
+ when Hash
302
+ domain = "; domain=#{value[:domain]}" if value[:domain]
303
+ path = "; path=#{value[:path]}" if value[:path]
304
+ max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
305
+ expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
306
+ secure = "; secure" if value[:secure]
307
+ httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
308
+ same_site =
309
+ case value[:same_site]
310
+ when false, nil
311
+ nil
312
+ when :none, 'None', :None
313
+ '; samesite=none'
314
+ when :lax, 'Lax', :Lax
315
+ '; samesite=lax'
316
+ when true, :strict, 'Strict', :Strict
317
+ '; samesite=strict'
318
+ else
319
+ raise ArgumentError, "Invalid :same_site value: #{value[:same_site].inspect}"
320
+ end
321
+ partitioned = "; partitioned" if value[:partitioned]
322
+ value = value[:value]
323
+ end
324
+
325
+ value = [value] unless Array === value
326
+
327
+ return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
328
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}"
329
+ end
330
+
331
+ # :call-seq:
332
+ # set_cookie_header!(headers, key, value) -> header value
333
+ #
334
+ # Append a cookie in the specified headers with the given cookie +key+ and
335
+ # +value+ using set_cookie_header.
336
+ #
337
+ # If the headers already contains a +set-cookie+ key, it will be converted
338
+ # to an +Array+ if not already, and appended to.
339
+ def set_cookie_header!(headers, key, value)
340
+ if header = headers[SET_COOKIE]
341
+ if header.is_a?(Array)
342
+ header << set_cookie_header(key, value)
343
+ else
344
+ headers[SET_COOKIE] = [header, set_cookie_header(key, value)]
345
+ end
346
+ else
347
+ headers[SET_COOKIE] = set_cookie_header(key, value)
348
+ end
349
+ end
350
+
351
+ # :call-seq:
352
+ # delete_set_cookie_header(key, value = {}) -> encoded string
353
+ #
354
+ # Generate an encoded string based on the given +key+ and +value+ using
355
+ # set_cookie_header for the purpose of causing the specified cookie to be
356
+ # deleted. The +value+ may be an instance of +Hash+ and can include
357
+ # attributes as outlined by set_cookie_header. The encoded cookie will have
358
+ # a +max_age+ of 0 seconds, an +expires+ date in the past and an empty
359
+ # +value+. When used with the +set-cookie+ header, it will cause the client
360
+ # to *remove* any matching cookie.
361
+ #
362
+ # delete_set_cookie_header("myname")
363
+ # # => "myname=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
364
+ #
365
+ def delete_set_cookie_header(key, value = {})
366
+ set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
367
+ end
368
+
369
+ def delete_cookie_header!(headers, key, value = {})
370
+ headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
371
+
372
+ return nil
373
+ end
374
+
375
+ # :call-seq:
376
+ # delete_set_cookie_header!(header, key, value = {}) -> header value
377
+ #
378
+ # Set an expired cookie in the specified headers with the given cookie
379
+ # +key+ and +value+ using delete_set_cookie_header. This causes
380
+ # the client to immediately delete the specified cookie.
381
+ #
382
+ # delete_set_cookie_header!(nil, "mycookie")
383
+ # # => "mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
384
+ #
385
+ # If the header is non-nil, it will be modified in place.
386
+ #
387
+ # header = []
388
+ # delete_set_cookie_header!(header, "mycookie")
389
+ # # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
390
+ # header
391
+ # # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
392
+ #
393
+ def delete_set_cookie_header!(header, key, value = {})
394
+ if header
395
+ header = Array(header)
396
+ header << delete_set_cookie_header(key, value)
397
+ else
398
+ header = delete_set_cookie_header(key, value)
399
+ end
400
+
401
+ return header
402
+ end
403
+
404
+ def rfc2822(time)
405
+ time.rfc2822
406
+ end
407
+
408
+ # Parses the "Range:" header, if present, into an array of Range objects.
409
+ # Returns nil if the header is missing or syntactically invalid.
410
+ # Returns an empty array if none of the ranges are satisfiable.
411
+ def byte_ranges(env, size)
412
+ get_byte_ranges env['HTTP_RANGE'], size
413
+ end
414
+
415
+ def get_byte_ranges(http_range, size)
416
+ # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
417
+ # Ignore Range when file size is 0 to avoid a 416 error.
418
+ return nil if size.zero?
419
+ return nil unless http_range && http_range =~ /bytes=([^;]+)/
420
+ ranges = []
421
+ $1.split(/,[ \t]*/).each do |range_spec|
422
+ return nil unless range_spec.include?('-')
423
+ range = range_spec.split('-')
424
+ r0, r1 = range[0], range[1]
425
+ if r0.nil? || r0.empty?
426
+ return nil if r1.nil?
427
+ # suffix-byte-range-spec, represents trailing suffix of file
428
+ r0 = size - r1.to_i
429
+ r0 = 0 if r0 < 0
430
+ r1 = size - 1
431
+ else
432
+ r0 = r0.to_i
433
+ if r1.nil?
434
+ r1 = size - 1
435
+ else
436
+ r1 = r1.to_i
437
+ return nil if r1 < r0 # backwards range is syntactically invalid
438
+ r1 = size - 1 if r1 >= size
439
+ end
440
+ end
441
+ ranges << (r0..r1) if r0 <= r1
442
+ end
443
+
444
+ return [] if ranges.map(&:size).sum > size
445
+
446
+ ranges
447
+ end
448
+
449
+ # :nocov:
450
+ if defined?(OpenSSL.fixed_length_secure_compare)
451
+ # Constant time string comparison.
452
+ #
453
+ # NOTE: the values compared should be of fixed length, such as strings
454
+ # that have already been processed by HMAC. This should not be used
455
+ # on variable length plaintext strings because it could leak length info
456
+ # via timing attacks.
457
+ def secure_compare(a, b)
458
+ return false unless a.bytesize == b.bytesize
459
+
460
+ OpenSSL.fixed_length_secure_compare(a, b)
461
+ end
462
+ # :nocov:
463
+ else
464
+ def secure_compare(a, b)
465
+ return false unless a.bytesize == b.bytesize
466
+
467
+ l = a.unpack("C*")
468
+
469
+ r, i = 0, -1
470
+ b.each_byte { |v| r |= v ^ l[i += 1] }
471
+ r == 0
472
+ end
473
+ end
474
+
475
+ # Context allows the use of a compatible middleware at different points
476
+ # in a request handling stack. A compatible middleware must define
477
+ # #context which should take the arguments env and app. The first of which
478
+ # would be the request environment. The second of which would be the rack
479
+ # application that the request would be forwarded to.
480
+ class Context
481
+ attr_reader :for, :app
482
+
483
+ def initialize(app_f, app_r)
484
+ raise 'running context does not respond to #context' unless app_f.respond_to? :context
485
+ @for, @app = app_f, app_r
486
+ end
487
+
488
+ def call(env)
489
+ @for.context(env, @app)
490
+ end
491
+
492
+ def recontext(app)
493
+ self.class.new(@for, app)
494
+ end
495
+
496
+ def context(env, app = @app)
497
+ recontext(app).call(env)
498
+ end
499
+ end
500
+
501
+ # Every standard HTTP code mapped to the appropriate message.
502
+ # Generated with:
503
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv \
504
+ # | ruby -rcsv -e "puts CSV.parse(STDIN, headers: true) \
505
+ # .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \
506
+ # .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)"
507
+ HTTP_STATUS_CODES = {
508
+ 100 => 'Continue',
509
+ 101 => 'Switching Protocols',
510
+ 102 => 'Processing',
511
+ 103 => 'Early Hints',
512
+ 200 => 'OK',
513
+ 201 => 'Created',
514
+ 202 => 'Accepted',
515
+ 203 => 'Non-Authoritative Information',
516
+ 204 => 'No Content',
517
+ 205 => 'Reset Content',
518
+ 206 => 'Partial Content',
519
+ 207 => 'Multi-Status',
520
+ 208 => 'Already Reported',
521
+ 226 => 'IM Used',
522
+ 300 => 'Multiple Choices',
523
+ 301 => 'Moved Permanently',
524
+ 302 => 'Found',
525
+ 303 => 'See Other',
526
+ 304 => 'Not Modified',
527
+ 305 => 'Use Proxy',
528
+ 307 => 'Temporary Redirect',
529
+ 308 => 'Permanent Redirect',
530
+ 400 => 'Bad Request',
531
+ 401 => 'Unauthorized',
532
+ 402 => 'Payment Required',
533
+ 403 => 'Forbidden',
534
+ 404 => 'Not Found',
535
+ 405 => 'Method Not Allowed',
536
+ 406 => 'Not Acceptable',
537
+ 407 => 'Proxy Authentication Required',
538
+ 408 => 'Request Timeout',
539
+ 409 => 'Conflict',
540
+ 410 => 'Gone',
541
+ 411 => 'Length Required',
542
+ 412 => 'Precondition Failed',
543
+ 413 => 'Content Too Large',
544
+ 414 => 'URI Too Long',
545
+ 415 => 'Unsupported Media Type',
546
+ 416 => 'Range Not Satisfiable',
547
+ 417 => 'Expectation Failed',
548
+ 421 => 'Misdirected Request',
549
+ 422 => 'Unprocessable Content',
550
+ 423 => 'Locked',
551
+ 424 => 'Failed Dependency',
552
+ 425 => 'Too Early',
553
+ 426 => 'Upgrade Required',
554
+ 428 => 'Precondition Required',
555
+ 429 => 'Too Many Requests',
556
+ 431 => 'Request Header Fields Too Large',
557
+ 451 => 'Unavailable For Legal Reasons',
558
+ 500 => 'Internal Server Error',
559
+ 501 => 'Not Implemented',
560
+ 502 => 'Bad Gateway',
561
+ 503 => 'Service Unavailable',
562
+ 504 => 'Gateway Timeout',
563
+ 505 => 'HTTP Version Not Supported',
564
+ 506 => 'Variant Also Negotiates',
565
+ 507 => 'Insufficient Storage',
566
+ 508 => 'Loop Detected',
567
+ 511 => 'Network Authentication Required'
568
+ }
569
+
570
+ # Responses with HTTP status codes that should not have an entity body
571
+ STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
572
+
573
+ SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
574
+ [message.downcase.gsub(/\s|-/, '_').to_sym, code]
575
+ }.flatten]
576
+
577
+ OBSOLETE_SYMBOLS_TO_STATUS_CODES = {
578
+ payload_too_large: 413,
579
+ unprocessable_entity: 422,
580
+ bandwidth_limit_exceeded: 509,
581
+ not_extended: 510
582
+ }.freeze
583
+ private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES
584
+
585
+ OBSOLETE_SYMBOL_MAPPINGS = {
586
+ payload_too_large: :content_too_large,
587
+ unprocessable_entity: :unprocessable_content
588
+ }.freeze
589
+ private_constant :OBSOLETE_SYMBOL_MAPPINGS
590
+
591
+ def status_code(status)
592
+ if status.is_a?(Symbol)
593
+ SYMBOL_TO_STATUS_CODE.fetch(status) do
594
+ fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
595
+ message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
596
+ if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
597
+ message = "#{message} Please use #{canonical_symbol.inspect} instead."
598
+ end
599
+ warn message, uplevel: 3
600
+ fallback_code
601
+ end
602
+ else
603
+ status.to_i
604
+ end
605
+ end
606
+
607
+ PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact).freeze
608
+
609
+ def clean_path_info(path_info)
610
+ parts = path_info.split PATH_SEPS
611
+
612
+ clean = []
613
+
614
+ parts.each do |part|
615
+ next if part.empty? || part == '.'
616
+ part == '..' ? clean.pop : clean << part
617
+ end
618
+
619
+ clean_path = clean.join(::File::SEPARATOR)
620
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
621
+ clean_path
622
+ end
623
+
624
+ NULL_BYTE = "\0"
625
+
626
+ def valid_path?(path)
627
+ path.valid_encoding? && !path.include?(NULL_BYTE)
628
+ end
629
+
630
+ end
631
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
4
+ #
5
+ # Rack is freely distributable under the terms of an MIT-style license.
6
+ # See MIT-LICENSE or https://opensource.org/licenses/MIT.
7
+
8
+ module Rack
9
+ VERSION = "3.2.0"
10
+
11
+ RELEASE = VERSION
12
+
13
+ # Return the Rack release as a dotted string.
14
+ def self.release
15
+ VERSION
16
+ end
17
+ end