nano-sharp-hub 0.0.1

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