rack 2.0.9.3 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +681 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +152 -162
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +35 -10
  8. data/bin/rackup +1 -0
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +3 -1
  11. data/example/protectedlobster.ru +2 -0
  12. data/lib/rack/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +1 -1
  14. data/lib/rack/auth/basic.rb +7 -4
  15. data/lib/rack/auth/digest/md5.rb +13 -11
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +4 -2
  18. data/lib/rack/auth/digest/request.rb +5 -3
  19. data/lib/rack/body_proxy.rb +15 -14
  20. data/lib/rack/builder.rb +116 -23
  21. data/lib/rack/cascade.rb +28 -12
  22. data/lib/rack/chunked.rb +68 -20
  23. data/lib/rack/common_logger.rb +33 -28
  24. data/lib/rack/conditional_get.rb +20 -16
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +8 -7
  27. data/lib/rack/content_type.rb +5 -4
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +59 -34
  30. data/lib/rack/directory.rb +84 -64
  31. data/lib/rack/etag.rb +5 -4
  32. data/lib/rack/events.rb +19 -20
  33. data/lib/rack/file.rb +4 -173
  34. data/lib/rack/files.rb +218 -0
  35. data/lib/rack/handler/cgi.rb +2 -3
  36. data/lib/rack/handler/fastcgi.rb +4 -4
  37. data/lib/rack/handler/lsws.rb +3 -3
  38. data/lib/rack/handler/scgi.rb +9 -8
  39. data/lib/rack/handler/thin.rb +17 -11
  40. data/lib/rack/handler/webrick.rb +15 -6
  41. data/lib/rack/handler.rb +7 -2
  42. data/lib/rack/head.rb +1 -1
  43. data/lib/rack/lint.rb +72 -26
  44. data/lib/rack/lobster.rb +10 -10
  45. data/lib/rack/lock.rb +2 -1
  46. data/lib/rack/logger.rb +2 -0
  47. data/lib/rack/media_type.rb +10 -5
  48. data/lib/rack/method_override.rb +4 -2
  49. data/lib/rack/mime.rb +9 -1
  50. data/lib/rack/mock.rb +97 -20
  51. data/lib/rack/multipart/generator.rb +17 -13
  52. data/lib/rack/multipart/parser.rb +58 -73
  53. data/lib/rack/multipart/uploaded_file.rb +15 -7
  54. data/lib/rack/multipart.rb +7 -4
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +53 -28
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +8 -4
  59. data/lib/rack/request.rb +220 -61
  60. data/lib/rack/response.rb +127 -44
  61. data/lib/rack/rewindable_input.rb +4 -3
  62. data/lib/rack/runtime.rb +6 -4
  63. data/lib/rack/sendfile.rb +13 -9
  64. data/lib/rack/server.rb +95 -24
  65. data/lib/rack/session/abstract/id.rb +33 -21
  66. data/lib/rack/session/cookie.rb +12 -12
  67. data/lib/rack/session/memcache.rb +4 -93
  68. data/lib/rack/session/pool.rb +5 -3
  69. data/lib/rack/show_exceptions.rb +17 -13
  70. data/lib/rack/show_status.rb +5 -5
  71. data/lib/rack/static.rb +23 -11
  72. data/lib/rack/tempfile_reaper.rb +1 -1
  73. data/lib/rack/urlmap.rb +12 -6
  74. data/lib/rack/utils.rb +105 -130
  75. data/lib/rack/version.rb +29 -0
  76. data/lib/rack.rb +67 -73
  77. data/rack.gemspec +40 -28
  78. metadata +39 -182
  79. data/HISTORY.md +0 -520
  80. data/test/builder/an_underscore_app.rb +0 -5
  81. data/test/builder/anything.rb +0 -5
  82. data/test/builder/comment.ru +0 -4
  83. data/test/builder/end.ru +0 -5
  84. data/test/builder/line.ru +0 -1
  85. data/test/builder/options.ru +0 -2
  86. data/test/cgi/assets/folder/test.js +0 -1
  87. data/test/cgi/assets/fonts/font.eot +0 -1
  88. data/test/cgi/assets/images/image.png +0 -1
  89. data/test/cgi/assets/index.html +0 -1
  90. data/test/cgi/assets/javascripts/app.js +0 -1
  91. data/test/cgi/assets/stylesheets/app.css +0 -1
  92. data/test/cgi/lighttpd.conf +0 -26
  93. data/test/cgi/rackup_stub.rb +0 -6
  94. data/test/cgi/sample_rackup.ru +0 -5
  95. data/test/cgi/test +0 -9
  96. data/test/cgi/test+directory/test+file +0 -1
  97. data/test/cgi/test.fcgi +0 -9
  98. data/test/cgi/test.gz +0 -0
  99. data/test/cgi/test.ru +0 -5
  100. data/test/gemloader.rb +0 -10
  101. data/test/helper.rb +0 -34
  102. data/test/multipart/bad_robots +0 -259
  103. data/test/multipart/binary +0 -0
  104. data/test/multipart/content_type_and_no_filename +0 -6
  105. data/test/multipart/empty +0 -10
  106. data/test/multipart/fail_16384_nofile +0 -814
  107. data/test/multipart/file1.txt +0 -1
  108. data/test/multipart/filename_and_modification_param +0 -7
  109. data/test/multipart/filename_and_no_name +0 -6
  110. data/test/multipart/filename_with_encoded_words +0 -7
  111. data/test/multipart/filename_with_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  113. data/test/multipart/filename_with_null_byte +0 -7
  114. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  115. data/test/multipart/filename_with_single_quote +0 -7
  116. data/test/multipart/filename_with_unescaped_percentages +0 -6
  117. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  118. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  119. data/test/multipart/filename_with_unescaped_quotes +0 -6
  120. data/test/multipart/ie +0 -6
  121. data/test/multipart/invalid_character +0 -6
  122. data/test/multipart/mixed_files +0 -21
  123. data/test/multipart/nested +0 -10
  124. data/test/multipart/none +0 -9
  125. data/test/multipart/quoted +0 -15
  126. data/test/multipart/rack-logo.png +0 -0
  127. data/test/multipart/semicolon +0 -6
  128. data/test/multipart/text +0 -15
  129. data/test/multipart/three_files_three_fields +0 -31
  130. data/test/multipart/unity3d_wwwform +0 -11
  131. data/test/multipart/webkit +0 -32
  132. data/test/rackup/config.ru +0 -31
  133. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  134. data/test/spec_auth_basic.rb +0 -89
  135. data/test/spec_auth_digest.rb +0 -260
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -233
  138. data/test/spec_cascade.rb +0 -63
  139. data/test/spec_cgi.rb +0 -84
  140. data/test/spec_chunked.rb +0 -103
  141. data/test/spec_common_logger.rb +0 -107
  142. data/test/spec_conditional_get.rb +0 -103
  143. data/test/spec_config.rb +0 -23
  144. data/test/spec_content_length.rb +0 -86
  145. data/test/spec_content_type.rb +0 -46
  146. data/test/spec_deflater.rb +0 -375
  147. data/test/spec_directory.rb +0 -148
  148. data/test/spec_etag.rb +0 -108
  149. data/test/spec_events.rb +0 -133
  150. data/test/spec_fastcgi.rb +0 -85
  151. data/test/spec_file.rb +0 -264
  152. data/test/spec_handler.rb +0 -57
  153. data/test/spec_head.rb +0 -46
  154. data/test/spec_lint.rb +0 -520
  155. data/test/spec_lobster.rb +0 -59
  156. data/test/spec_lock.rb +0 -204
  157. data/test/spec_logger.rb +0 -24
  158. data/test/spec_media_type.rb +0 -42
  159. data/test/spec_method_override.rb +0 -110
  160. data/test/spec_mime.rb +0 -51
  161. data/test/spec_mock.rb +0 -359
  162. data/test/spec_multipart.rb +0 -721
  163. data/test/spec_null_logger.rb +0 -21
  164. data/test/spec_recursive.rb +0 -75
  165. data/test/spec_request.rb +0 -1423
  166. data/test/spec_response.rb +0 -528
  167. data/test/spec_rewindable_input.rb +0 -128
  168. data/test/spec_runtime.rb +0 -50
  169. data/test/spec_sendfile.rb +0 -125
  170. data/test/spec_server.rb +0 -193
  171. data/test/spec_session_abstract_id.rb +0 -31
  172. data/test/spec_session_abstract_session_hash.rb +0 -45
  173. data/test/spec_session_cookie.rb +0 -442
  174. data/test/spec_session_memcache.rb +0 -357
  175. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  176. data/test/spec_session_pool.rb +0 -247
  177. data/test/spec_show_exceptions.rb +0 -93
  178. data/test/spec_show_status.rb +0 -104
  179. data/test/spec_static.rb +0 -184
  180. data/test/spec_tempfile_reaper.rb +0 -64
  181. data/test/spec_thin.rb +0 -96
  182. data/test/spec_urlmap.rb +0 -237
  183. data/test/spec_utils.rb +0 -742
  184. data/test/spec_version.rb +0 -11
  185. data/test/spec_webrick.rb +0 -206
  186. data/test/static/another/index.html +0 -1
  187. data/test/static/foo.html +0 -1
  188. data/test/static/index.html +0 -1
  189. data/test/testrequest.rb +0 -78
  190. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  191. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Regexp has `match?` since Ruby 2.4
4
+ # so to support Ruby < 2.4 we need to define this method
5
+
6
+ module Rack
7
+ module RegexpExtensions
8
+ refine Regexp do
9
+ def match?(string, pos = 0)
10
+ !!match(string, pos)
11
+ end
12
+ end unless //.respond_to?(:match?)
13
+ end
14
+ end
data/lib/rack/deflater.rb CHANGED
@@ -1,38 +1,48 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "zlib"
2
4
  require "time" # for Time.httpdate
3
- require 'rack/utils'
4
5
 
5
6
  module Rack
6
- # This middleware enables compression of http responses.
7
+ # This middleware enables content encoding of http responses,
8
+ # usually for purposes of compression.
9
+ #
10
+ # Currently supported encodings:
7
11
  #
8
- # Currently supported compression algorithms:
12
+ # * gzip
13
+ # * identity (no transformation)
9
14
  #
10
- # * gzip
11
- # * identity (no transformation)
15
+ # This middleware automatically detects when encoding is supported
16
+ # and allowed. For example no encoding is made when a cache
17
+ # directive of 'no-transform' is present, when the response status
18
+ # code is one that doesn't allow an entity body, or when the body
19
+ # is empty.
12
20
  #
13
- # The middleware automatically detects when compression is supported
14
- # and allowed. For example no transformation is made when a cache
15
- # directive of 'no-transform' is present, or when the response status
16
- # code is one that doesn't allow an entity body.
21
+ # Note that despite the name, Deflater does not support the +deflate+
22
+ # encoding.
17
23
  class Deflater
18
- ##
19
- # Creates Rack::Deflater middleware.
24
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
25
+
26
+ # Creates Rack::Deflater middleware. Options:
20
27
  #
21
- # [app] rack app instance
22
- # [options] hash of deflater options, i.e.
23
- # 'if' - a lambda enabling / disabling deflation based on returned boolean value
24
- # e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.map(&:bytesize).reduce(0, :+) > 512 }
25
- # 'include' - a list of content types that should be compressed
28
+ # :if :: a lambda enabling / disabling deflation based on returned boolean value
29
+ # (e.g <tt>use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }</tt>).
30
+ # However, be aware that calling `body.each` inside the block will break cases where `body.each` is not idempotent,
31
+ # such as when it is an +IO+ instance.
32
+ # :include :: a list of content types that should be compressed. By default, all content types are compressed.
33
+ # :sync :: determines if the stream is going to be flushed after every chunk. Flushing after every chunk reduces
34
+ # latency for time-sensitive streaming applications, but hurts compression and throughput.
35
+ # Defaults to +true+.
26
36
  def initialize(app, options = {})
27
37
  @app = app
28
-
29
38
  @condition = options[:if]
30
39
  @compressible_types = options[:include]
40
+ @sync = options.fetch(:sync, true)
31
41
  end
32
42
 
33
43
  def call(env)
34
44
  status, headers, body = @app.call(env)
35
- headers = Utils::HeaderHash.new(headers)
45
+ headers = Utils::HeaderHash[headers]
36
46
 
37
47
  unless should_deflate?(env, status, headers, body)
38
48
  return [status, headers, body]
@@ -53,66 +63,81 @@ module Rack
53
63
  when "gzip"
54
64
  headers['Content-Encoding'] = "gzip"
55
65
  headers.delete(CONTENT_LENGTH)
56
- mtime = headers.key?("Last-Modified") ?
57
- Time.httpdate(headers["Last-Modified"]) : Time.now
58
- [status, headers, GzipStream.new(body, mtime)]
66
+ mtime = headers["Last-Modified"]
67
+ mtime = Time.httpdate(mtime).to_i if mtime
68
+ [status, headers, GzipStream.new(body, mtime, @sync)]
59
69
  when "identity"
60
70
  [status, headers, body]
61
71
  when nil
62
72
  message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
63
73
  bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
64
- [406, {CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s}, bp]
74
+ [406, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s }, bp]
65
75
  end
66
76
  end
67
77
 
78
+ # Body class used for gzip encoded responses.
68
79
  class GzipStream
69
- def initialize(body, mtime)
80
+ # Initialize the gzip stream. Arguments:
81
+ # body :: Response body to compress with gzip
82
+ # mtime :: The modification time of the body, used to set the
83
+ # modification time in the gzip header.
84
+ # sync :: Whether to flush each gzip chunk as soon as it is ready.
85
+ def initialize(body, mtime, sync)
70
86
  @body = body
71
87
  @mtime = mtime
72
- @closed = false
88
+ @sync = sync
73
89
  end
74
90
 
91
+ # Yield gzip compressed strings to the given block.
75
92
  def each(&block)
76
93
  @writer = block
77
- gzip =::Zlib::GzipWriter.new(self)
78
- gzip.mtime = @mtime
94
+ gzip = ::Zlib::GzipWriter.new(self)
95
+ gzip.mtime = @mtime if @mtime
79
96
  @body.each { |part|
97
+ # Skip empty strings, as they would result in no output,
98
+ # and flushing empty parts would raise Zlib::BufError.
99
+ next if part.empty?
100
+
80
101
  gzip.write(part)
81
- gzip.flush
102
+ gzip.flush if @sync
82
103
  }
83
104
  ensure
84
105
  gzip.close
85
- @writer = nil
86
106
  end
87
107
 
108
+ # Call the block passed to #each with the the gzipped data.
88
109
  def write(data)
89
110
  @writer.call(data)
90
111
  end
91
112
 
113
+ # Close the original body if possible.
92
114
  def close
93
- return if @closed
94
- @closed = true
95
115
  @body.close if @body.respond_to?(:close)
96
116
  end
97
117
  end
98
118
 
99
119
  private
100
120
 
121
+ # Whether the body should be compressed.
101
122
  def should_deflate?(env, status, headers, body)
102
123
  # Skip compressing empty entity body responses and responses with
103
124
  # no-transform set.
104
- if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
105
- headers[CACHE_CONTROL].to_s =~ /\bno-transform\b/ ||
106
- (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
125
+ if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
126
+ /\bno-transform\b/.match?(headers['Cache-Control'].to_s) ||
127
+ headers['Content-Encoding']&.!~(/\bidentity\b/)
107
128
  return false
108
129
  end
109
130
 
110
131
  # Skip if @compressible_types are given and does not include request's content type
111
- return false if @compressible_types && !(headers.has_key?(CONTENT_TYPE) && @compressible_types.include?(headers[CONTENT_TYPE][/[^;]*/]))
132
+ return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
112
133
 
113
134
  # Skip if @condition lambda is given and evaluates to false
114
135
  return false if @condition && !@condition.call(env, status, headers, body)
115
136
 
137
+ # No point in compressing empty body, also handles usage with
138
+ # Rack::Sendfile.
139
+ return false if headers[CONTENT_LENGTH] == '0'
140
+
116
141
  true
117
142
  end
118
143
  end
@@ -1,6 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
- require 'rack/utils'
3
- require 'rack/mime'
4
4
 
5
5
  module Rack
6
6
  # Rack::Directory serves entries below the +root+ given, according to the
@@ -8,11 +8,11 @@ module Rack
8
8
  # will be presented in an html based index. If a file is found, the env will
9
9
  # be passed to the specified +app+.
10
10
  #
11
- # If +app+ is not specified, a Rack::File of the same +root+ will be used.
11
+ # If +app+ is not specified, a Rack::Files of the same +root+ will be used.
12
12
 
13
13
  class Directory
14
- DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
15
- DIR_PAGE = <<-PAGE
14
+ DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>\n"
15
+ DIR_PAGE_HEADER = <<-PAGE
16
16
  <html><head>
17
17
  <title>%s</title>
18
18
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
@@ -33,33 +33,51 @@ table { width:100%%; }
33
33
  <th class='type'>Type</th>
34
34
  <th class='mtime'>Last Modified</th>
35
35
  </tr>
36
- %s
36
+ PAGE
37
+ DIR_PAGE_FOOTER = <<-PAGE
37
38
  </table>
38
39
  <hr />
39
40
  </body></html>
40
41
  PAGE
41
42
 
43
+ # Body class for directory entries, showing an index page with links
44
+ # to each file.
42
45
  class DirectoryBody < Struct.new(:root, :path, :files)
46
+ # Yield strings for each part of the directory entry
43
47
  def each
44
- show_path = Rack::Utils.escape_html(path.sub(/^#{root}/,''))
45
- listings = files.map{|f| DIR_FILE % DIR_FILE_escape(*f) }*"\n"
46
- page = DIR_PAGE % [ show_path, show_path , listings ]
47
- page.each_line{|l| yield l }
48
+ show_path = Utils.escape_html(path.sub(/^#{root}/, ''))
49
+ yield(DIR_PAGE_HEADER % [ show_path, show_path ])
50
+
51
+ unless path.chomp('/') == root
52
+ yield(DIR_FILE % DIR_FILE_escape(files.call('..')))
53
+ end
54
+
55
+ Dir.foreach(path) do |basename|
56
+ next if basename.start_with?('.')
57
+ next unless f = files.call(basename)
58
+ yield(DIR_FILE % DIR_FILE_escape(f))
59
+ end
60
+
61
+ yield(DIR_PAGE_FOOTER)
48
62
  end
49
63
 
50
64
  private
51
- # Assumes url is already escaped.
52
- def DIR_FILE_escape url, *html
53
- [url, *html.map { |e| Utils.escape_html(e) }]
65
+
66
+ # Escape each element in the array of html strings.
67
+ def DIR_FILE_escape(htmls)
68
+ htmls.map { |e| Utils.escape_html(e) }
54
69
  end
55
70
  end
56
71
 
57
- attr_reader :root, :path
72
+ # The root of the directory hierarchy. Only requests for files and
73
+ # directories inside of the root directory are supported.
74
+ attr_reader :root
58
75
 
59
- def initialize(root, app=nil)
76
+ # Set the root directory and application for serving files.
77
+ def initialize(root, app = nil)
60
78
  @root = ::File.expand_path(root)
61
- @app = app || Rack::File.new(@root)
62
- @head = Rack::Head.new(lambda { |env| get env })
79
+ @app = app || Files.new(@root)
80
+ @head = Head.new(method(:get))
63
81
  end
64
82
 
65
83
  def call(env)
@@ -67,100 +85,101 @@ table { width:100%%; }
67
85
  @head.call env
68
86
  end
69
87
 
88
+ # Internals of request handling. Similar to call but does
89
+ # not remove body for HEAD requests.
70
90
  def get(env)
71
91
  script_name = env[SCRIPT_NAME]
72
92
  path_info = Utils.unescape_path(env[PATH_INFO])
73
93
 
74
- if bad_request = check_bad_request(path_info)
75
- bad_request
76
- elsif forbidden = check_forbidden(path_info)
77
- forbidden
94
+ if client_error_response = check_bad_request(path_info) || check_forbidden(path_info)
95
+ client_error_response
78
96
  else
79
97
  path = ::File.join(@root, path_info)
80
98
  list_path(env, path, path_info, script_name)
81
99
  end
82
100
  end
83
101
 
102
+ # Rack response to use for requests with invalid paths, or nil if path is valid.
84
103
  def check_bad_request(path_info)
85
104
  return if Utils.valid_path?(path_info)
86
105
 
87
106
  body = "Bad Request\n"
88
- size = body.bytesize
89
- return [400, {CONTENT_TYPE => "text/plain",
90
- CONTENT_LENGTH => size.to_s,
91
- "X-Cascade" => "pass"}, [body]]
107
+ [400, { CONTENT_TYPE => "text/plain",
108
+ CONTENT_LENGTH => body.bytesize.to_s,
109
+ "X-Cascade" => "pass" }, [body]]
92
110
  end
93
111
 
112
+ # Rack response to use for requests with paths outside the root, or nil if path is inside the root.
94
113
  def check_forbidden(path_info)
95
114
  return unless path_info.include? ".."
115
+ return if ::File.expand_path(::File.join(@root, path_info)).start_with?(@root)
96
116
 
97
117
  body = "Forbidden\n"
98
- size = body.bytesize
99
- return [403, {CONTENT_TYPE => "text/plain",
100
- CONTENT_LENGTH => size.to_s,
101
- "X-Cascade" => "pass"}, [body]]
118
+ [403, { CONTENT_TYPE => "text/plain",
119
+ CONTENT_LENGTH => body.bytesize.to_s,
120
+ "X-Cascade" => "pass" }, [body]]
102
121
  end
103
122
 
123
+ # Rack response to use for directories under the root.
104
124
  def list_directory(path_info, path, script_name)
105
- files = [['../','Parent Directory','','','']]
106
- glob = ::File.join(path, '*')
107
-
108
125
  url_head = (script_name.split('/') + path_info.split('/')).map do |part|
109
- Rack::Utils.escape_path part
126
+ Utils.escape_path part
110
127
  end
111
128
 
112
- Dir[glob].sort.each do |node|
113
- stat = stat(node)
129
+ # Globbing not safe as path could contain glob metacharacters
130
+ body = DirectoryBody.new(@root, path, ->(basename) do
131
+ stat = stat(::File.join(path, basename))
114
132
  next unless stat
115
- basename = ::File.basename(node)
116
- ext = ::File.extname(node)
117
133
 
118
- url = ::File.join(*url_head + [Rack::Utils.escape_path(basename)])
119
- size = stat.size
120
- type = stat.directory? ? 'directory' : Mime.mime_type(ext)
121
- size = stat.directory? ? '-' : filesize_format(size)
134
+ url = ::File.join(*url_head + [Utils.escape_path(basename)])
122
135
  mtime = stat.mtime.httpdate
123
- url << '/' if stat.directory?
124
- basename << '/' if stat.directory?
125
-
126
- files << [ url, basename, size, type, mtime ]
127
- end
128
-
129
- return [ 200, { CONTENT_TYPE =>'text/html; charset=utf-8'}, DirectoryBody.new(@root, path, files) ]
136
+ if stat.directory?
137
+ type = 'directory'
138
+ size = '-'
139
+ url << '/'
140
+ if basename == '..'
141
+ basename = 'Parent Directory'
142
+ else
143
+ basename << '/'
144
+ end
145
+ else
146
+ type = Mime.mime_type(::File.extname(basename))
147
+ size = filesize_format(stat.size)
148
+ end
149
+
150
+ [ url, basename, size, type, mtime ]
151
+ end)
152
+
153
+ [ 200, { CONTENT_TYPE => 'text/html; charset=utf-8' }, body ]
130
154
  end
131
155
 
132
- def stat(node)
133
- ::File.stat(node)
156
+ # File::Stat for the given path, but return nil for missing/bad entries.
157
+ def stat(path)
158
+ ::File.stat(path)
134
159
  rescue Errno::ENOENT, Errno::ELOOP
135
160
  return nil
136
161
  end
137
162
 
138
- # TODO: add correct response if not readable, not sure if 404 is the best
139
- # option
163
+ # Rack response to use for files and directories under the root.
164
+ # Unreadable and non-file, non-directory entries will get a 404 response.
140
165
  def list_path(env, path, path_info, script_name)
141
- stat = ::File.stat(path)
142
-
143
- if stat.readable?
166
+ if (stat = stat(path)) && stat.readable?
144
167
  return @app.call(env) if stat.file?
145
168
  return list_directory(path_info, path, script_name) if stat.directory?
146
- else
147
- raise Errno::ENOENT, 'No such file or directory'
148
169
  end
149
170
 
150
- rescue Errno::ENOENT, Errno::ELOOP
151
- return entity_not_found(path_info)
171
+ entity_not_found(path_info)
152
172
  end
153
173
 
174
+ # Rack response to use for unreadable and non-file, non-directory entries.
154
175
  def entity_not_found(path_info)
155
176
  body = "Entity not found: #{path_info}\n"
156
- size = body.bytesize
157
- return [404, {CONTENT_TYPE => "text/plain",
158
- CONTENT_LENGTH => size.to_s,
159
- "X-Cascade" => "pass"}, [body]]
177
+ [404, { CONTENT_TYPE => "text/plain",
178
+ CONTENT_LENGTH => body.bytesize.to_s,
179
+ "X-Cascade" => "pass" }, [body]]
160
180
  end
161
181
 
162
182
  # Stolen from Ramaze
163
-
164
183
  FILESIZE_FORMAT = [
165
184
  ['%.1fT', 1 << 40],
166
185
  ['%.1fG', 1 << 30],
@@ -168,6 +187,7 @@ table { width:100%%; }
168
187
  ['%.1fK', 1 << 10],
169
188
  ]
170
189
 
190
+ # Provide human readable file sizes
171
191
  def filesize_format(int)
172
192
  FILESIZE_FORMAT.each do |format, size|
173
193
  return format % (int.to_f / size) if int >= size
data/lib/rack/etag.rb CHANGED
@@ -1,4 +1,6 @@
1
- require 'rack'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../rack'
2
4
  require 'digest/sha2'
3
5
 
4
6
  module Rack
@@ -13,7 +15,7 @@ module Rack
13
15
  # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
14
16
  class ETag
15
17
  ETAG_STRING = Rack::ETAG
16
- DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
18
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
17
19
 
18
20
  def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
19
21
  @app = app
@@ -55,8 +57,7 @@ module Rack
55
57
  end
56
58
 
57
59
  def skip_caching?(headers)
58
- (headers[CACHE_CONTROL] && headers[CACHE_CONTROL].include?('no-cache')) ||
59
- headers.key?(ETAG_STRING) || headers.key?('Last-Modified')
60
+ headers.key?(ETAG_STRING) || headers.key?('Last-Modified')
60
61
  end
61
62
 
62
63
  def digest_body(body)
data/lib/rack/events.rb CHANGED
@@ -1,11 +1,10 @@
1
- require 'rack/response'
2
- require 'rack/body_proxy'
1
+ # frozen_string_literal: true
3
2
 
4
3
  module Rack
5
4
  ### This middleware provides hooks to certain places in the request /
6
- #response lifecycle. This is so that middleware that don't need to filter
7
- #the response data can safely leave it alone and not have to send messages
8
- #down the traditional "rack stack".
5
+ # response lifecycle. This is so that middleware that don't need to filter
6
+ # the response data can safely leave it alone and not have to send messages
7
+ # down the traditional "rack stack".
9
8
  #
10
9
  # The events are:
11
10
  #
@@ -57,26 +56,26 @@ module Rack
57
56
 
58
57
  class Events
59
58
  module Abstract
60
- def on_start req, res
59
+ def on_start(req, res)
61
60
  end
62
61
 
63
- def on_commit req, res
62
+ def on_commit(req, res)
64
63
  end
65
64
 
66
- def on_send req, res
65
+ def on_send(req, res)
67
66
  end
68
67
 
69
- def on_finish req, res
68
+ def on_finish(req, res)
70
69
  end
71
70
 
72
- def on_error req, res, e
71
+ def on_error(req, res, e)
73
72
  end
74
73
  end
75
74
 
76
75
  class EventedBodyProxy < Rack::BodyProxy # :nodoc:
77
76
  attr_reader :request, :response
78
77
 
79
- def initialize body, request, response, handlers, &block
78
+ def initialize(body, request, response, handlers, &block)
80
79
  super(body, &block)
81
80
  @request = request
82
81
  @response = response
@@ -92,7 +91,7 @@ module Rack
92
91
  class BufferedResponse < Rack::Response::Raw # :nodoc:
93
92
  attr_reader :body
94
93
 
95
- def initialize status, headers, body
94
+ def initialize(status, headers, body)
96
95
  super(status, headers)
97
96
  @body = body
98
97
  end
@@ -100,12 +99,12 @@ module Rack
100
99
  def to_a; [status, headers, body]; end
101
100
  end
102
101
 
103
- def initialize app, handlers
102
+ def initialize(app, handlers)
104
103
  @app = app
105
104
  @handlers = handlers
106
105
  end
107
106
 
108
- def call env
107
+ def call(env)
109
108
  request = make_request env
110
109
  on_start request, nil
111
110
 
@@ -127,27 +126,27 @@ module Rack
127
126
 
128
127
  private
129
128
 
130
- def on_error request, response, e
129
+ def on_error(request, response, e)
131
130
  @handlers.reverse_each { |handler| handler.on_error request, response, e }
132
131
  end
133
132
 
134
- def on_commit request, response
133
+ def on_commit(request, response)
135
134
  @handlers.reverse_each { |handler| handler.on_commit request, response }
136
135
  end
137
136
 
138
- def on_start request, response
137
+ def on_start(request, response)
139
138
  @handlers.each { |handler| handler.on_start request, nil }
140
139
  end
141
140
 
142
- def on_finish request, response
141
+ def on_finish(request, response)
143
142
  @handlers.reverse_each { |handler| handler.on_finish request, response }
144
143
  end
145
144
 
146
- def make_request env
145
+ def make_request(env)
147
146
  Rack::Request.new env
148
147
  end
149
148
 
150
- def make_response status, headers, body
149
+ def make_response(status, headers, body)
151
150
  BufferedResponse.new status, headers, body
152
151
  end
153
152
  end