rack 1.6.13 → 2.1.4.3

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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +105 -141
  5. data/Rakefile +27 -28
  6. data/SPEC +6 -7
  7. data/bin/rackup +1 -0
  8. data/contrib/rack_logo.svg +164 -111
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +4 -2
  11. data/example/protectedlobster.ru +3 -1
  12. data/lib/rack/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +7 -1
  14. data/lib/rack/auth/basic.rb +4 -1
  15. data/lib/rack/auth/digest/md5.rb +9 -7
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +5 -4
  18. data/lib/rack/auth/digest/request.rb +3 -1
  19. data/lib/rack/body_proxy.rb +11 -9
  20. data/lib/rack/builder.rb +42 -18
  21. data/lib/rack/cascade.rb +6 -5
  22. data/lib/rack/chunked.rb +33 -10
  23. data/lib/rack/{commonlogger.rb → common_logger.rb} +14 -10
  24. data/lib/rack/{conditionalget.rb → conditional_get.rb} +3 -1
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +5 -3
  27. data/lib/rack/content_type.rb +3 -1
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +33 -53
  30. data/lib/rack/directory.rb +75 -60
  31. data/lib/rack/etag.rb +8 -5
  32. data/lib/rack/events.rb +156 -0
  33. data/lib/rack/file.rb +4 -149
  34. data/lib/rack/files.rb +178 -0
  35. data/lib/rack/handler/cgi.rb +18 -17
  36. data/lib/rack/handler/fastcgi.rb +17 -16
  37. data/lib/rack/handler/lsws.rb +14 -12
  38. data/lib/rack/handler/scgi.rb +22 -19
  39. data/lib/rack/handler/thin.rb +6 -1
  40. data/lib/rack/handler/webrick.rb +28 -28
  41. data/lib/rack/handler.rb +9 -26
  42. data/lib/rack/head.rb +17 -17
  43. data/lib/rack/lint.rb +55 -52
  44. data/lib/rack/lobster.rb +8 -6
  45. data/lib/rack/lock.rb +17 -10
  46. data/lib/rack/logger.rb +4 -2
  47. data/lib/rack/media_type.rb +43 -0
  48. data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
  49. data/lib/rack/mime.rb +27 -6
  50. data/lib/rack/mock.rb +101 -60
  51. data/lib/rack/multipart/generator.rb +11 -12
  52. data/lib/rack/multipart/parser.rb +292 -161
  53. data/lib/rack/multipart/uploaded_file.rb +3 -2
  54. data/lib/rack/multipart.rb +38 -8
  55. data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
  56. data/lib/rack/query_parser.rb +218 -0
  57. data/lib/rack/recursive.rb +11 -9
  58. data/lib/rack/reloader.rb +10 -4
  59. data/lib/rack/request.rb +447 -305
  60. data/lib/rack/response.rb +196 -83
  61. data/lib/rack/rewindable_input.rb +5 -14
  62. data/lib/rack/runtime.rb +12 -18
  63. data/lib/rack/sendfile.rb +19 -14
  64. data/lib/rack/server.rb +118 -41
  65. data/lib/rack/session/abstract/id.rb +139 -94
  66. data/lib/rack/session/cookie.rb +34 -26
  67. data/lib/rack/session/memcache.rb +4 -93
  68. data/lib/rack/session/pool.rb +12 -10
  69. data/lib/rack/show_exceptions.rb +392 -0
  70. data/lib/rack/{showstatus.rb → show_status.rb} +7 -5
  71. data/lib/rack/static.rb +41 -11
  72. data/lib/rack/tempfile_reaper.rb +4 -2
  73. data/lib/rack/urlmap.rb +25 -15
  74. data/lib/rack/utils.rb +203 -277
  75. data/lib/rack.rb +76 -24
  76. data/rack.gemspec +25 -14
  77. metadata +62 -183
  78. data/HISTORY.md +0 -375
  79. data/KNOWN-ISSUES +0 -44
  80. data/lib/rack/backports/uri/common_18.rb +0 -56
  81. data/lib/rack/backports/uri/common_192.rb +0 -52
  82. data/lib/rack/backports/uri/common_193.rb +0 -29
  83. data/lib/rack/handler/evented_mongrel.rb +0 -8
  84. data/lib/rack/handler/mongrel.rb +0 -106
  85. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  86. data/lib/rack/showexceptions.rb +0 -387
  87. data/lib/rack/utils/okjson.rb +0 -600
  88. data/test/builder/anything.rb +0 -5
  89. data/test/builder/comment.ru +0 -4
  90. data/test/builder/end.ru +0 -5
  91. data/test/builder/line.ru +0 -1
  92. data/test/builder/options.ru +0 -2
  93. data/test/cgi/assets/folder/test.js +0 -1
  94. data/test/cgi/assets/fonts/font.eot +0 -1
  95. data/test/cgi/assets/images/image.png +0 -1
  96. data/test/cgi/assets/index.html +0 -1
  97. data/test/cgi/assets/javascripts/app.js +0 -1
  98. data/test/cgi/assets/stylesheets/app.css +0 -1
  99. data/test/cgi/lighttpd.conf +0 -26
  100. data/test/cgi/rackup_stub.rb +0 -6
  101. data/test/cgi/sample_rackup.ru +0 -5
  102. data/test/cgi/test +0 -9
  103. data/test/cgi/test+directory/test+file +0 -1
  104. data/test/cgi/test.fcgi +0 -8
  105. data/test/cgi/test.ru +0 -5
  106. data/test/gemloader.rb +0 -10
  107. data/test/multipart/bad_robots +0 -259
  108. data/test/multipart/binary +0 -0
  109. data/test/multipart/content_type_and_no_filename +0 -6
  110. data/test/multipart/empty +0 -10
  111. data/test/multipart/fail_16384_nofile +0 -814
  112. data/test/multipart/file1.txt +0 -1
  113. data/test/multipart/filename_and_modification_param +0 -7
  114. data/test/multipart/filename_and_no_name +0 -6
  115. data/test/multipart/filename_with_escaped_quotes +0 -6
  116. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  117. data/test/multipart/filename_with_null_byte +0 -7
  118. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  119. data/test/multipart/filename_with_unescaped_percentages +0 -6
  120. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  121. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  122. data/test/multipart/filename_with_unescaped_quotes +0 -6
  123. data/test/multipart/ie +0 -6
  124. data/test/multipart/invalid_character +0 -6
  125. data/test/multipart/mixed_files +0 -21
  126. data/test/multipart/nested +0 -10
  127. data/test/multipart/none +0 -9
  128. data/test/multipart/semicolon +0 -6
  129. data/test/multipart/text +0 -15
  130. data/test/multipart/three_files_three_fields +0 -31
  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 -81
  135. data/test/spec_auth_digest.rb +0 -259
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -223
  138. data/test/spec_cascade.rb +0 -61
  139. data/test/spec_cgi.rb +0 -102
  140. data/test/spec_chunked.rb +0 -101
  141. data/test/spec_commonlogger.rb +0 -93
  142. data/test/spec_conditionalget.rb +0 -102
  143. data/test/spec_config.rb +0 -22
  144. data/test/spec_content_length.rb +0 -85
  145. data/test/spec_content_type.rb +0 -45
  146. data/test/spec_deflater.rb +0 -339
  147. data/test/spec_directory.rb +0 -88
  148. data/test/spec_etag.rb +0 -107
  149. data/test/spec_fastcgi.rb +0 -107
  150. data/test/spec_file.rb +0 -221
  151. data/test/spec_handler.rb +0 -72
  152. data/test/spec_head.rb +0 -45
  153. data/test/spec_lint.rb +0 -550
  154. data/test/spec_lobster.rb +0 -58
  155. data/test/spec_lock.rb +0 -164
  156. data/test/spec_logger.rb +0 -23
  157. data/test/spec_methodoverride.rb +0 -111
  158. data/test/spec_mime.rb +0 -51
  159. data/test/spec_mock.rb +0 -297
  160. data/test/spec_mongrel.rb +0 -182
  161. data/test/spec_multipart.rb +0 -600
  162. data/test/spec_nulllogger.rb +0 -20
  163. data/test/spec_recursive.rb +0 -72
  164. data/test/spec_request.rb +0 -1232
  165. data/test/spec_response.rb +0 -407
  166. data/test/spec_rewindable_input.rb +0 -118
  167. data/test/spec_runtime.rb +0 -49
  168. data/test/spec_sendfile.rb +0 -130
  169. data/test/spec_server.rb +0 -167
  170. data/test/spec_session_abstract_id.rb +0 -53
  171. data/test/spec_session_cookie.rb +0 -410
  172. data/test/spec_session_memcache.rb +0 -358
  173. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  174. data/test/spec_session_pool.rb +0 -246
  175. data/test/spec_showexceptions.rb +0 -98
  176. data/test/spec_showstatus.rb +0 -103
  177. data/test/spec_static.rb +0 -145
  178. data/test/spec_tempfile_reaper.rb +0 -63
  179. data/test/spec_thin.rb +0 -91
  180. data/test/spec_urlmap.rb +0 -236
  181. data/test/spec_utils.rb +0 -647
  182. data/test/spec_version.rb +0 -17
  183. data/test/spec_webrick.rb +0 -184
  184. data/test/static/another/index.html +0 -1
  185. data/test/static/index.html +0 -1
  186. data/test/testrequest.rb +0 -78
  187. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  188. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
4
  require 'rack/utils'
3
5
  require 'rack/mime'
6
+ require 'rack/files'
4
7
 
5
8
  module Rack
6
9
  # Rack::Directory serves entries below the +root+ given, according to the
@@ -8,7 +11,7 @@ module Rack
8
11
  # will be presented in an html based index. If a file is found, the env will
9
12
  # be passed to the specified +app+.
10
13
  #
11
- # If +app+ is not specified, a Rack::File of the same +root+ will be used.
14
+ # If +app+ is not specified, a Rack::Files of the same +root+ will be used.
12
15
 
13
16
  class Directory
14
17
  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>"
@@ -39,58 +42,83 @@ table { width:100%%; }
39
42
  </body></html>
40
43
  PAGE
41
44
 
42
- attr_reader :files
43
- attr_accessor :root, :path
45
+ class DirectoryBody < Struct.new(:root, :path, :files)
46
+ def each
47
+ show_path = Rack::Utils.escape_html(path.sub(/^#{root}/, ''))
48
+ listings = files.map{|f| DIR_FILE % DIR_FILE_escape(*f) } * "\n"
49
+ page = DIR_PAGE % [ show_path, show_path, listings ]
50
+ page.each_line{|l| yield l }
51
+ end
44
52
 
45
- def initialize(root, app=nil)
46
- @root = F.expand_path(root)
47
- @app = app || Rack::File.new(@root)
53
+ private
54
+ # Assumes url is already escaped.
55
+ def DIR_FILE_escape url, *html
56
+ [url, *html.map { |e| Utils.escape_html(e) }]
57
+ end
48
58
  end
49
59
 
50
- def call(env)
51
- dup._call(env)
60
+ attr_reader :root, :path
61
+
62
+ def initialize(root, app = nil)
63
+ @root = ::File.expand_path(root)
64
+ @app = app || Rack::Files.new(@root)
65
+ @head = Rack::Head.new(lambda { |env| get env })
52
66
  end
53
67
 
54
- F = ::File
68
+ def call(env)
69
+ # strip body if this is a HEAD call
70
+ @head.call env
71
+ end
55
72
 
56
- def _call(env)
57
- @env = env
58
- @script_name = env[SCRIPT_NAME]
59
- @path_info = Utils.unescape(env[PATH_INFO])
73
+ def get(env)
74
+ script_name = env[SCRIPT_NAME]
75
+ path_info = Utils.unescape_path(env[PATH_INFO])
60
76
 
61
- if forbidden = check_forbidden
77
+ if bad_request = check_bad_request(path_info)
78
+ bad_request
79
+ elsif forbidden = check_forbidden(path_info)
62
80
  forbidden
63
81
  else
64
- @path = F.join(@root, @path_info)
65
- list_path
82
+ path = ::File.join(@root, path_info)
83
+ list_path(env, path, path_info, script_name)
66
84
  end
67
85
  end
68
86
 
69
- def check_forbidden
70
- return unless @path_info.include? ".."
87
+ def check_bad_request(path_info)
88
+ return if Utils.valid_path?(path_info)
89
+
90
+ body = "Bad Request\n"
91
+ size = body.bytesize
92
+ return [400, { CONTENT_TYPE => "text/plain",
93
+ CONTENT_LENGTH => size.to_s,
94
+ "X-Cascade" => "pass" }, [body]]
95
+ end
96
+
97
+ def check_forbidden(path_info)
98
+ return unless path_info.include? ".."
71
99
 
72
100
  body = "Forbidden\n"
73
- size = Rack::Utils.bytesize(body)
74
- return [403, {"Content-Type" => "text/plain",
101
+ size = body.bytesize
102
+ return [403, { CONTENT_TYPE => "text/plain",
75
103
  CONTENT_LENGTH => size.to_s,
76
- "X-Cascade" => "pass"}, [body]]
104
+ "X-Cascade" => "pass" }, [body]]
77
105
  end
78
106
 
79
- def list_directory
80
- @files = [['../','Parent Directory','','','']]
81
- glob = F.join(@path, '*')
107
+ def list_directory(path_info, path, script_name)
108
+ files = [['../', 'Parent Directory', '', '', '']]
82
109
 
83
- url_head = (@script_name.split('/') + @path_info.split('/')).map do |part|
84
- Rack::Utils.escape part
110
+ url_head = (script_name.split('/') + path_info.split('/')).map do |part|
111
+ Rack::Utils.escape_path part
85
112
  end
86
113
 
87
- Dir[glob].sort.each do |node|
114
+ Dir.entries(path).reject { |e| e.start_with?('.') }.sort.each do |node|
115
+ node = ::File.join path, node
88
116
  stat = stat(node)
89
- next unless stat
90
- basename = F.basename(node)
91
- ext = F.extname(node)
117
+ next unless stat
118
+ basename = ::File.basename(node)
119
+ ext = ::File.extname(node)
92
120
 
93
- url = F.join(*url_head + [Rack::Utils.escape(basename)])
121
+ url = ::File.join(*url_head + [Rack::Utils.escape_path(basename)])
94
122
  size = stat.size
95
123
  type = stat.directory? ? 'directory' : Mime.mime_type(ext)
96
124
  size = stat.directory? ? '-' : filesize_format(size)
@@ -98,47 +126,40 @@ table { width:100%%; }
98
126
  url << '/' if stat.directory?
99
127
  basename << '/' if stat.directory?
100
128
 
101
- @files << [ url, basename, size, type, mtime ]
129
+ files << [ url, basename, size, type, mtime ]
102
130
  end
103
131
 
104
- return [ 200, { CONTENT_TYPE =>'text/html; charset=utf-8'}, self ]
132
+ return [ 200, { CONTENT_TYPE => 'text/html; charset=utf-8' }, DirectoryBody.new(@root, path, files) ]
105
133
  end
106
134
 
107
- def stat(node, max = 10)
108
- F.stat(node)
135
+ def stat(node)
136
+ ::File.stat(node)
109
137
  rescue Errno::ENOENT, Errno::ELOOP
110
138
  return nil
111
139
  end
112
140
 
113
141
  # TODO: add correct response if not readable, not sure if 404 is the best
114
142
  # option
115
- def list_path
116
- @stat = F.stat(@path)
143
+ def list_path(env, path, path_info, script_name)
144
+ stat = ::File.stat(path)
117
145
 
118
- if @stat.readable?
119
- return @app.call(@env) if @stat.file?
120
- return list_directory if @stat.directory?
146
+ if stat.readable?
147
+ return @app.call(env) if stat.file?
148
+ return list_directory(path_info, path, script_name) if stat.directory?
121
149
  else
122
150
  raise Errno::ENOENT, 'No such file or directory'
123
151
  end
124
152
 
125
153
  rescue Errno::ENOENT, Errno::ELOOP
126
- return entity_not_found
154
+ return entity_not_found(path_info)
127
155
  end
128
156
 
129
- def entity_not_found
130
- body = "Entity not found: #{@path_info}\n"
131
- size = Rack::Utils.bytesize(body)
132
- return [404, {"Content-Type" => "text/plain",
157
+ def entity_not_found(path_info)
158
+ body = "Entity not found: #{path_info}\n"
159
+ size = body.bytesize
160
+ return [404, { CONTENT_TYPE => "text/plain",
133
161
  CONTENT_LENGTH => size.to_s,
134
- "X-Cascade" => "pass"}, [body]]
135
- end
136
-
137
- def each
138
- show_path = Rack::Utils.escape_html(@path.sub(/^#{@root}/,''))
139
- files = @files.map{|f| DIR_FILE % DIR_FILE_escape(*f) }*"\n"
140
- page = DIR_PAGE % [ show_path, show_path , files ]
141
- page.each_line{|l| yield l }
162
+ "X-Cascade" => "pass" }, [body]]
142
163
  end
143
164
 
144
165
  # Stolen from Ramaze
@@ -155,13 +176,7 @@ table { width:100%%; }
155
176
  return format % (int.to_f / size) if int >= size
156
177
  end
157
178
 
158
- int.to_s + 'B'
159
- end
160
-
161
- private
162
- # Assumes url is already escaped.
163
- def DIR_FILE_escape url, *html
164
- [url, *html.map { |e| Utils.escape_html(e) }]
179
+ "#{int}B"
165
180
  end
166
181
  end
167
182
  end
data/lib/rack/etag.rb CHANGED
@@ -1,4 +1,7 @@
1
- require 'digest/md5'
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+ require 'digest/sha2'
2
5
 
3
6
  module Rack
4
7
  # Automatically sets the ETag header on all String bodies.
@@ -11,8 +14,8 @@ module Rack
11
14
  # used when Etag is absent and a directive when it is present. The first
12
15
  # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
13
16
  class ETag
14
- ETAG_STRING = 'ETag'.freeze
15
- DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
17
+ ETAG_STRING = Rack::ETAG
18
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
16
19
 
17
20
  def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
18
21
  @app = app
@@ -64,10 +67,10 @@ module Rack
64
67
 
65
68
  body.each do |part|
66
69
  parts << part
67
- (digest ||= Digest::MD5.new) << part unless part.empty?
70
+ (digest ||= Digest::SHA256.new) << part unless part.empty?
68
71
  end
69
72
 
70
- [digest && digest.hexdigest, parts]
73
+ [digest && digest.hexdigest.byteslice(0, 32), parts]
71
74
  end
72
75
  end
73
76
  end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/response'
4
+ require 'rack/body_proxy'
5
+
6
+ module Rack
7
+ ### This middleware provides hooks to certain places in the request /
8
+ # response lifecycle. This is so that middleware that don't need to filter
9
+ # the response data can safely leave it alone and not have to send messages
10
+ # down the traditional "rack stack".
11
+ #
12
+ # The events are:
13
+ #
14
+ # * on_start(request, response)
15
+ #
16
+ # This event is sent at the start of the request, before the next
17
+ # middleware in the chain is called. This method is called with a request
18
+ # object, and a response object. Right now, the response object is always
19
+ # nil, but in the future it may actually be a real response object.
20
+ #
21
+ # * on_commit(request, response)
22
+ #
23
+ # The response has been committed. The application has returned, but the
24
+ # response has not been sent to the webserver yet. This method is always
25
+ # called with a request object and the response object. The response
26
+ # object is constructed from the rack triple that the application returned.
27
+ # Changes may still be made to the response object at this point.
28
+ #
29
+ # * on_send(request, response)
30
+ #
31
+ # The webserver has started iterating over the response body and presumably
32
+ # has started sending data over the wire. This method is always called with
33
+ # a request object and the response object. The response object is
34
+ # constructed from the rack triple that the application returned. Changes
35
+ # SHOULD NOT be made to the response object as the webserver has already
36
+ # started sending data. Any mutations will likely result in an exception.
37
+ #
38
+ # * on_finish(request, response)
39
+ #
40
+ # The webserver has closed the response, and all data has been written to
41
+ # the response socket. The request and response object should both be
42
+ # read-only at this point. The body MAY NOT be available on the response
43
+ # object as it may have been flushed to the socket.
44
+ #
45
+ # * on_error(request, response, error)
46
+ #
47
+ # An exception has occurred in the application or an `on_commit` event.
48
+ # This method will get the request, the response (if available) and the
49
+ # exception that was raised.
50
+ #
51
+ # ## Order
52
+ #
53
+ # `on_start` is called on the handlers in the order that they were passed to
54
+ # the constructor. `on_commit`, on_send`, `on_finish`, and `on_error` are
55
+ # called in the reverse order. `on_finish` handlers are called inside an
56
+ # `ensure` block, so they are guaranteed to be called even if something
57
+ # raises an exception. If something raises an exception in a `on_finish`
58
+ # method, then nothing is guaranteed.
59
+
60
+ class Events
61
+ module Abstract
62
+ def on_start req, res
63
+ end
64
+
65
+ def on_commit req, res
66
+ end
67
+
68
+ def on_send req, res
69
+ end
70
+
71
+ def on_finish req, res
72
+ end
73
+
74
+ def on_error req, res, e
75
+ end
76
+ end
77
+
78
+ class EventedBodyProxy < Rack::BodyProxy # :nodoc:
79
+ attr_reader :request, :response
80
+
81
+ def initialize body, request, response, handlers, &block
82
+ super(body, &block)
83
+ @request = request
84
+ @response = response
85
+ @handlers = handlers
86
+ end
87
+
88
+ def each
89
+ @handlers.reverse_each { |handler| handler.on_send request, response }
90
+ super
91
+ end
92
+ end
93
+
94
+ class BufferedResponse < Rack::Response::Raw # :nodoc:
95
+ attr_reader :body
96
+
97
+ def initialize status, headers, body
98
+ super(status, headers)
99
+ @body = body
100
+ end
101
+
102
+ def to_a; [status, headers, body]; end
103
+ end
104
+
105
+ def initialize app, handlers
106
+ @app = app
107
+ @handlers = handlers
108
+ end
109
+
110
+ def call env
111
+ request = make_request env
112
+ on_start request, nil
113
+
114
+ begin
115
+ status, headers, body = @app.call request.env
116
+ response = make_response status, headers, body
117
+ on_commit request, response
118
+ rescue StandardError => e
119
+ on_error request, response, e
120
+ on_finish request, response
121
+ raise
122
+ end
123
+
124
+ body = EventedBodyProxy.new(body, request, response, @handlers) do
125
+ on_finish request, response
126
+ end
127
+ [response.status, response.headers, body]
128
+ end
129
+
130
+ private
131
+
132
+ def on_error request, response, e
133
+ @handlers.reverse_each { |handler| handler.on_error request, response, e }
134
+ end
135
+
136
+ def on_commit request, response
137
+ @handlers.reverse_each { |handler| handler.on_commit request, response }
138
+ end
139
+
140
+ def on_start request, response
141
+ @handlers.each { |handler| handler.on_start request, nil }
142
+ end
143
+
144
+ def on_finish request, response
145
+ @handlers.reverse_each { |handler| handler.on_finish request, response }
146
+ end
147
+
148
+ def make_request env
149
+ Rack::Request.new env
150
+ end
151
+
152
+ def make_response status, headers, body
153
+ BufferedResponse.new status, headers, body
154
+ end
155
+ end
156
+ end
data/lib/rack/file.rb CHANGED
@@ -1,152 +1,7 @@
1
- require 'time'
2
- require 'rack/utils'
3
- require 'rack/mime'
1
+ # frozen_string_literal: true
4
2
 
5
- module Rack
6
- # Rack::File serves files below the +root+ directory given, according to the
7
- # path info of the Rack request.
8
- # e.g. when Rack::File.new("/etc") is used, you can access 'passwd' file
9
- # as http://localhost:9292/passwd
10
- #
11
- # Handlers can detect if bodies are a Rack::File, and use mechanisms
12
- # like sendfile on the +path+.
13
-
14
- class File
15
- ALLOWED_VERBS = %w[GET HEAD OPTIONS]
16
- ALLOW_HEADER = ALLOWED_VERBS.join(', ')
17
-
18
- attr_accessor :root
19
- attr_accessor :path
20
- attr_accessor :cache_control
21
-
22
- alias :to_path :path
23
-
24
- def initialize(root, headers={}, default_mime = 'text/plain')
25
- @root = root
26
- @headers = headers
27
- @default_mime = default_mime
28
- end
29
-
30
- def call(env)
31
- dup._call(env)
32
- end
33
-
34
- F = ::File
35
-
36
- def _call(env)
37
- unless ALLOWED_VERBS.include? env[REQUEST_METHOD]
38
- return fail(405, "Method Not Allowed", {'Allow' => ALLOW_HEADER})
39
- end
40
-
41
- path_info = Utils.unescape(env[PATH_INFO])
42
- clean_path_info = Utils.clean_path_info(path_info)
43
-
44
- @path = F.join(@root, clean_path_info)
45
-
46
- available = begin
47
- F.file?(@path) && F.readable?(@path)
48
- rescue SystemCallError
49
- false
50
- end
51
-
52
- if available
53
- serving(env)
54
- else
55
- fail(404, "File not found: #{path_info}")
56
- end
57
- end
58
-
59
- def serving(env)
60
- if env["REQUEST_METHOD"] == "OPTIONS"
61
- return [200, {'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0'}, []]
62
- end
63
- last_modified = F.mtime(@path).httpdate
64
- return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
65
-
66
- headers = { "Last-Modified" => last_modified }
67
- headers[CONTENT_TYPE] = mime_type if mime_type
3
+ require 'rack/files'
68
4
 
69
- # Set custom headers
70
- @headers.each { |field, content| headers[field] = content } if @headers
71
-
72
- response = [ 200, headers, env[REQUEST_METHOD] == "HEAD" ? [] : self ]
73
-
74
- size = filesize
75
-
76
- ranges = Rack::Utils.byte_ranges(env, size)
77
- if ranges.nil? || ranges.length > 1
78
- # No ranges, or multiple ranges (which we don't support):
79
- # TODO: Support multiple byte-ranges
80
- response[0] = 200
81
- @range = 0..size-1
82
- elsif ranges.empty?
83
- # Unsatisfiable. Return error, and file size:
84
- response = fail(416, "Byte range unsatisfiable")
85
- response[1]["Content-Range"] = "bytes */#{size}"
86
- return response
87
- else
88
- # Partial content:
89
- @range = ranges[0]
90
- response[0] = 206
91
- response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
92
- size = @range.end - @range.begin + 1
93
- end
94
-
95
- response[2] = [response_body] unless response_body.nil?
96
-
97
- response[1][CONTENT_LENGTH] = size.to_s
98
- response
99
- end
100
-
101
- def each
102
- F.open(@path, "rb") do |file|
103
- file.seek(@range.begin)
104
- remaining_len = @range.end-@range.begin+1
105
- while remaining_len > 0
106
- part = file.read([8192, remaining_len].min)
107
- break unless part
108
- remaining_len -= part.length
109
-
110
- yield part
111
- end
112
- end
113
- end
114
-
115
- private
116
-
117
- def fail(status, body, headers = {})
118
- body += "\n"
119
- [
120
- status,
121
- {
122
- CONTENT_TYPE => "text/plain",
123
- CONTENT_LENGTH => body.size.to_s,
124
- "X-Cascade" => "pass"
125
- }.merge!(headers),
126
- [body]
127
- ]
128
- end
129
-
130
- # The MIME type for the contents of the file located at @path
131
- def mime_type
132
- Mime.mime_type(F.extname(@path), @default_mime)
133
- end
134
-
135
- def filesize
136
- # If response_body is present, use its size.
137
- return Rack::Utils.bytesize(response_body) if response_body
138
-
139
- # We check via File::size? whether this file provides size info
140
- # via stat (e.g. /proc files often don't), otherwise we have to
141
- # figure it out by reading the whole file into memory.
142
- F.size?(@path) || Utils.bytesize(F.read(@path))
143
- end
144
-
145
- # By default, the response body for file requests is nil.
146
- # In this case, the response body will be generated later
147
- # from the file at @path
148
- def response_body
149
- nil
150
- end
151
- end
5
+ module Rack
6
+ File = Files
152
7
  end