rack 1.0.1 → 2.2.21

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 (147) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +829 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +355 -0
  6. data/Rakefile +64 -98
  7. data/SPEC.rdoc +292 -0
  8. data/bin/rackup +3 -174
  9. data/contrib/rack.png +0 -0
  10. data/contrib/rack.svg +150 -0
  11. data/contrib/rack_logo.svg +164 -111
  12. data/contrib/rdoc.css +412 -0
  13. data/example/lobster.ru +2 -0
  14. data/example/protectedlobster.rb +4 -2
  15. data/example/protectedlobster.ru +3 -1
  16. data/lib/rack/auth/abstract/handler.rb +7 -5
  17. data/lib/rack/auth/abstract/request.rb +12 -2
  18. data/lib/rack/auth/basic.rb +6 -4
  19. data/lib/rack/auth/digest/md5.rb +20 -13
  20. data/lib/rack/auth/digest/nonce.rb +6 -4
  21. data/lib/rack/auth/digest/params.rb +11 -12
  22. data/lib/rack/auth/digest/request.rb +15 -12
  23. data/lib/rack/body_proxy.rb +45 -0
  24. data/lib/rack/builder.rb +213 -19
  25. data/lib/rack/cascade.rb +50 -18
  26. data/lib/rack/chunked.rb +95 -27
  27. data/lib/rack/common_logger.rb +84 -0
  28. data/lib/rack/conditional_get.rb +83 -0
  29. data/lib/rack/config.rb +22 -0
  30. data/lib/rack/content_length.rb +19 -10
  31. data/lib/rack/content_type.rb +12 -5
  32. data/lib/rack/core_ext/regexp.rb +14 -0
  33. data/lib/rack/deflater.rb +90 -42
  34. data/lib/rack/directory.rb +118 -72
  35. data/lib/rack/etag.rb +77 -0
  36. data/lib/rack/events.rb +153 -0
  37. data/lib/rack/file.rb +4 -85
  38. data/lib/rack/files.rb +218 -0
  39. data/lib/rack/handler/cgi.rb +22 -24
  40. data/lib/rack/handler/fastcgi.rb +34 -24
  41. data/lib/rack/handler/lsws.rb +16 -15
  42. data/lib/rack/handler/scgi.rb +38 -29
  43. data/lib/rack/handler/thin.rb +23 -7
  44. data/lib/rack/handler/webrick.rb +95 -37
  45. data/lib/rack/handler.rb +49 -14
  46. data/lib/rack/head.rb +19 -13
  47. data/lib/rack/lint.rb +466 -217
  48. data/lib/rack/lobster.rb +19 -14
  49. data/lib/rack/lock.rb +24 -8
  50. data/lib/rack/logger.rb +20 -0
  51. data/lib/rack/media_type.rb +53 -0
  52. data/lib/rack/method_override.rb +52 -0
  53. data/lib/rack/mime.rb +652 -171
  54. data/lib/rack/mock.rb +203 -64
  55. data/lib/rack/multipart/generator.rb +97 -0
  56. data/lib/rack/multipart/parser.rb +440 -0
  57. data/lib/rack/multipart/uploaded_file.rb +41 -0
  58. data/lib/rack/multipart.rb +64 -0
  59. data/lib/rack/null_logger.rb +39 -0
  60. data/lib/rack/query_parser.rb +266 -0
  61. data/lib/rack/recursive.rb +18 -11
  62. data/lib/rack/reloader.rb +13 -5
  63. data/lib/rack/request.rb +607 -191
  64. data/lib/rack/response.rb +245 -110
  65. data/lib/rack/rewindable_input.rb +15 -21
  66. data/lib/rack/runtime.rb +34 -0
  67. data/lib/rack/sendfile.rb +192 -0
  68. data/lib/rack/server.rb +466 -0
  69. data/lib/rack/session/abstract/id.rb +449 -68
  70. data/lib/rack/session/cookie.rb +159 -47
  71. data/lib/rack/session/memcache.rb +4 -103
  72. data/lib/rack/session/pool.rb +39 -49
  73. data/lib/rack/show_exceptions.rb +390 -0
  74. data/lib/rack/{showstatus.rb → show_status.rb} +21 -14
  75. data/lib/rack/static.rb +164 -14
  76. data/lib/rack/tempfile_reaper.rb +22 -0
  77. data/lib/rack/urlmap.rb +60 -18
  78. data/lib/rack/utils.rb +508 -277
  79. data/lib/rack/version.rb +29 -0
  80. data/lib/rack.rb +85 -34
  81. data/rack.gemspec +43 -51
  82. metadata +119 -207
  83. data/KNOWN-ISSUES +0 -18
  84. data/RDOX +0 -428
  85. data/README +0 -364
  86. data/SPEC +0 -164
  87. data/lib/rack/adapter/camping.rb +0 -22
  88. data/lib/rack/auth/openid.rb +0 -480
  89. data/lib/rack/commonlogger.rb +0 -61
  90. data/lib/rack/conditionalget.rb +0 -47
  91. data/lib/rack/handler/evented_mongrel.rb +0 -8
  92. data/lib/rack/handler/mongrel.rb +0 -87
  93. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  94. data/lib/rack/methodoverride.rb +0 -27
  95. data/lib/rack/showexceptions.rb +0 -349
  96. data/test/cgi/lighttpd.conf +0 -20
  97. data/test/cgi/test +0 -9
  98. data/test/cgi/test.fcgi +0 -8
  99. data/test/cgi/test.ru +0 -7
  100. data/test/multipart/binary +0 -0
  101. data/test/multipart/empty +0 -10
  102. data/test/multipart/ie +0 -6
  103. data/test/multipart/nested +0 -10
  104. data/test/multipart/none +0 -9
  105. data/test/multipart/semicolon +0 -6
  106. data/test/multipart/text +0 -10
  107. data/test/spec_rack_auth_basic.rb +0 -73
  108. data/test/spec_rack_auth_digest.rb +0 -226
  109. data/test/spec_rack_auth_openid.rb +0 -84
  110. data/test/spec_rack_builder.rb +0 -84
  111. data/test/spec_rack_camping.rb +0 -51
  112. data/test/spec_rack_cascade.rb +0 -50
  113. data/test/spec_rack_cgi.rb +0 -89
  114. data/test/spec_rack_chunked.rb +0 -62
  115. data/test/spec_rack_commonlogger.rb +0 -32
  116. data/test/spec_rack_conditionalget.rb +0 -41
  117. data/test/spec_rack_content_length.rb +0 -43
  118. data/test/spec_rack_content_type.rb +0 -30
  119. data/test/spec_rack_deflater.rb +0 -127
  120. data/test/spec_rack_directory.rb +0 -61
  121. data/test/spec_rack_fastcgi.rb +0 -89
  122. data/test/spec_rack_file.rb +0 -75
  123. data/test/spec_rack_handler.rb +0 -43
  124. data/test/spec_rack_head.rb +0 -30
  125. data/test/spec_rack_lint.rb +0 -521
  126. data/test/spec_rack_lobster.rb +0 -45
  127. data/test/spec_rack_lock.rb +0 -38
  128. data/test/spec_rack_methodoverride.rb +0 -60
  129. data/test/spec_rack_mock.rb +0 -157
  130. data/test/spec_rack_mongrel.rb +0 -189
  131. data/test/spec_rack_recursive.rb +0 -77
  132. data/test/spec_rack_request.rb +0 -504
  133. data/test/spec_rack_response.rb +0 -218
  134. data/test/spec_rack_rewindable_input.rb +0 -118
  135. data/test/spec_rack_session_cookie.rb +0 -82
  136. data/test/spec_rack_session_memcache.rb +0 -240
  137. data/test/spec_rack_session_pool.rb +0 -172
  138. data/test/spec_rack_showexceptions.rb +0 -21
  139. data/test/spec_rack_showstatus.rb +0 -72
  140. data/test/spec_rack_static.rb +0 -37
  141. data/test/spec_rack_thin.rb +0 -91
  142. data/test/spec_rack_urlmap.rb +0 -185
  143. data/test/spec_rack_utils.rb +0 -387
  144. data/test/spec_rack_webrick.rb +0 -130
  145. data/test/testrequest.rb +0 -57
  146. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  147. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -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,108 +33,153 @@ 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
 
42
- attr_reader :files
43
- attr_accessor :root, :path
43
+ # Body class for directory entries, showing an index page with links
44
+ # to each file.
45
+ class DirectoryBody < Struct.new(:root, :path, :files)
46
+ # Yield strings for each part of the directory entry
47
+ def each
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)
62
+ end
63
+
64
+ private
44
65
 
45
- def initialize(root, app=nil)
46
- @root = F.expand_path(root)
47
- @app = app || Rack::File.new(@root)
66
+ # Escape each element in the array of html strings.
67
+ def DIR_FILE_escape(htmls)
68
+ htmls.map { |e| Utils.escape_html(e) }
69
+ end
48
70
  end
49
71
 
50
- def call(env)
51
- dup._call(env)
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
75
+
76
+ # Set the root directory and application for serving files.
77
+ def initialize(root, app = nil)
78
+ @root = ::File.expand_path(root)
79
+ @app = app || Files.new(@root)
80
+ @head = Head.new(method(:get))
52
81
  end
53
82
 
54
- F = ::File
83
+ def call(env)
84
+ # strip body if this is a HEAD call
85
+ @head.call env
86
+ end
55
87
 
56
- def _call(env)
57
- @env = env
58
- @script_name = env['SCRIPT_NAME']
59
- @path_info = Utils.unescape(env['PATH_INFO'])
88
+ # Internals of request handling. Similar to call but does
89
+ # not remove body for HEAD requests.
90
+ def get(env)
91
+ script_name = env[SCRIPT_NAME]
92
+ path_info = Utils.unescape_path(env[PATH_INFO])
60
93
 
61
- if forbidden = check_forbidden
62
- forbidden
94
+ if client_error_response = check_bad_request(path_info) || check_forbidden(path_info)
95
+ client_error_response
63
96
  else
64
- @path = F.join(@root, @path_info)
65
- list_path
97
+ path = ::File.join(@root, path_info)
98
+ list_path(env, path, path_info, script_name)
66
99
  end
67
100
  end
68
101
 
69
- def check_forbidden
70
- return unless @path_info.include? ".."
102
+ # Rack response to use for requests with invalid paths, or nil if path is valid.
103
+ def check_bad_request(path_info)
104
+ return if Utils.valid_path?(path_info)
71
105
 
72
- body = "Forbidden\n"
73
- size = Rack::Utils.bytesize(body)
74
- return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]]
106
+ body = "Bad Request\n"
107
+ [400, { CONTENT_TYPE => "text/plain",
108
+ CONTENT_LENGTH => body.bytesize.to_s,
109
+ "X-Cascade" => "pass" }, [body]]
75
110
  end
76
111
 
77
- def list_directory
78
- @files = [['../','Parent Directory','','','']]
79
- glob = F.join(@path, '*')
80
-
81
- Dir[glob].sort.each do |node|
82
- stat = stat(node)
83
- next unless stat
84
- basename = F.basename(node)
85
- ext = F.extname(node)
112
+ # Rack response to use for requests with paths outside the root, or nil if path is inside the root.
113
+ def check_forbidden(path_info)
114
+ return unless path_info.include? ".."
115
+ return if ::File.expand_path(::File.join(@root, path_info)).start_with?(@root)
86
116
 
87
- url = F.join(@script_name, @path_info, basename)
88
- size = stat.size
89
- type = stat.directory? ? 'directory' : Mime.mime_type(ext)
90
- size = stat.directory? ? '-' : filesize_format(size)
91
- mtime = stat.mtime.httpdate
92
- url << '/' if stat.directory?
93
- basename << '/' if stat.directory?
117
+ body = "Forbidden\n"
118
+ [403, { CONTENT_TYPE => "text/plain",
119
+ CONTENT_LENGTH => body.bytesize.to_s,
120
+ "X-Cascade" => "pass" }, [body]]
121
+ end
94
122
 
95
- @files << [ url, basename, size, type, mtime ]
123
+ # Rack response to use for directories under the root.
124
+ def list_directory(path_info, path, script_name)
125
+ url_head = (script_name.split('/') + path_info.split('/')).map do |part|
126
+ Utils.escape_path part
96
127
  end
97
128
 
98
- return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
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))
132
+ next unless stat
133
+
134
+ url = ::File.join(*url_head + [Utils.escape_path(basename)])
135
+ mtime = stat.mtime.httpdate
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 ]
99
154
  end
100
155
 
101
- def stat(node, max = 10)
102
- F.stat(node)
156
+ # File::Stat for the given path, but return nil for missing/bad entries.
157
+ def stat(path)
158
+ ::File.stat(path)
103
159
  rescue Errno::ENOENT, Errno::ELOOP
104
160
  return nil
105
161
  end
106
162
 
107
- # TODO: add correct response if not readable, not sure if 404 is the best
108
- # option
109
- def list_path
110
- @stat = F.stat(@path)
111
-
112
- if @stat.readable?
113
- return @app.call(@env) if @stat.file?
114
- return list_directory if @stat.directory?
115
- else
116
- raise Errno::ENOENT, 'No such file or directory'
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.
165
+ def list_path(env, path, path_info, script_name)
166
+ if (stat = stat(path)) && stat.readable?
167
+ return @app.call(env) if stat.file?
168
+ return list_directory(path_info, path, script_name) if stat.directory?
117
169
  end
118
170
 
119
- rescue Errno::ENOENT, Errno::ELOOP
120
- return entity_not_found
171
+ entity_not_found(path_info)
121
172
  end
122
173
 
123
- def entity_not_found
124
- body = "Entity not found: #{@path_info}\n"
125
- size = Rack::Utils.bytesize(body)
126
- return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
127
- end
128
-
129
- def each
130
- show_path = @path.sub(/^#{@root}/,'')
131
- files = @files.map{|f| DIR_FILE % f }*"\n"
132
- page = DIR_PAGE % [ show_path, show_path , files ]
133
- page.each_line{|l| yield l }
174
+ # Rack response to use for unreadable and non-file, non-directory entries.
175
+ def entity_not_found(path_info)
176
+ body = "Entity not found: #{path_info}\n"
177
+ [404, { CONTENT_TYPE => "text/plain",
178
+ CONTENT_LENGTH => body.bytesize.to_s,
179
+ "X-Cascade" => "pass" }, [body]]
134
180
  end
135
181
 
136
182
  # Stolen from Ramaze
137
-
138
183
  FILESIZE_FORMAT = [
139
184
  ['%.1fT', 1 << 40],
140
185
  ['%.1fG', 1 << 30],
@@ -142,12 +187,13 @@ table { width:100%%; }
142
187
  ['%.1fK', 1 << 10],
143
188
  ]
144
189
 
190
+ # Provide human readable file sizes
145
191
  def filesize_format(int)
146
192
  FILESIZE_FORMAT.each do |format, size|
147
193
  return format % (int.to_f / size) if int >= size
148
194
  end
149
195
 
150
- int.to_s + 'B'
196
+ "#{int}B"
151
197
  end
152
198
  end
153
199
  end
data/lib/rack/etag.rb ADDED
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../rack'
4
+ require 'digest/sha2'
5
+
6
+ module Rack
7
+ # Automatically sets the ETag header on all String bodies.
8
+ #
9
+ # The ETag header is skipped if ETag or Last-Modified headers are sent or if
10
+ # a sendfile body (body.responds_to :to_path) is given (since such cases
11
+ # should be handled by apache/nginx).
12
+ #
13
+ # On initialization, you can pass two parameters: a Cache-Control directive
14
+ # used when Etag is absent and a directive when it is present. The first
15
+ # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
16
+ class ETag
17
+ ETAG_STRING = Rack::ETAG
18
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
19
+
20
+ def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
21
+ @app = app
22
+ @cache_control = cache_control
23
+ @no_cache_control = no_cache_control
24
+ end
25
+
26
+ def call(env)
27
+ status, headers, body = @app.call(env)
28
+
29
+ headers = Utils::HeaderHash[headers]
30
+
31
+ if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
32
+ original_body = body
33
+ digest, new_body = digest_body(body)
34
+ body = Rack::BodyProxy.new(new_body) do
35
+ original_body.close if original_body.respond_to?(:close)
36
+ end
37
+ headers[ETAG_STRING] = %(W/"#{digest}") if digest
38
+ end
39
+
40
+ unless headers[CACHE_CONTROL]
41
+ if digest
42
+ headers[CACHE_CONTROL] = @cache_control if @cache_control
43
+ else
44
+ headers[CACHE_CONTROL] = @no_cache_control if @no_cache_control
45
+ end
46
+ end
47
+
48
+ [status, headers, body]
49
+ end
50
+
51
+ private
52
+
53
+ def etag_status?(status)
54
+ status == 200 || status == 201
55
+ end
56
+
57
+ def etag_body?(body)
58
+ !body.respond_to?(:to_path)
59
+ end
60
+
61
+ def skip_caching?(headers)
62
+ headers.key?(ETAG_STRING) || headers.key?('Last-Modified')
63
+ end
64
+
65
+ def digest_body(body)
66
+ parts = []
67
+ digest = nil
68
+
69
+ body.each do |part|
70
+ parts << part
71
+ (digest ||= Digest::SHA256.new) << part unless part.empty?
72
+ end
73
+
74
+ [digest && digest.hexdigest.byteslice(0, 32), parts]
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ ### This middleware provides hooks to certain places in the request /
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".
8
+ #
9
+ # The events are:
10
+ #
11
+ # * on_start(request, response)
12
+ #
13
+ # This event is sent at the start of the request, before the next
14
+ # middleware in the chain is called. This method is called with a request
15
+ # object, and a response object. Right now, the response object is always
16
+ # nil, but in the future it may actually be a real response object.
17
+ #
18
+ # * on_commit(request, response)
19
+ #
20
+ # The response has been committed. The application has returned, but the
21
+ # response has not been sent to the webserver yet. This method is always
22
+ # called with a request object and the response object. The response
23
+ # object is constructed from the rack triple that the application returned.
24
+ # Changes may still be made to the response object at this point.
25
+ #
26
+ # * on_send(request, response)
27
+ #
28
+ # The webserver has started iterating over the response body and presumably
29
+ # has started sending data over the wire. This method is always called with
30
+ # a request object and the response object. The response object is
31
+ # constructed from the rack triple that the application returned. Changes
32
+ # SHOULD NOT be made to the response object as the webserver has already
33
+ # started sending data. Any mutations will likely result in an exception.
34
+ #
35
+ # * on_finish(request, response)
36
+ #
37
+ # The webserver has closed the response, and all data has been written to
38
+ # the response socket. The request and response object should both be
39
+ # read-only at this point. The body MAY NOT be available on the response
40
+ # object as it may have been flushed to the socket.
41
+ #
42
+ # * on_error(request, response, error)
43
+ #
44
+ # An exception has occurred in the application or an `on_commit` event.
45
+ # This method will get the request, the response (if available) and the
46
+ # exception that was raised.
47
+ #
48
+ # ## Order
49
+ #
50
+ # `on_start` is called on the handlers in the order that they were passed to
51
+ # the constructor. `on_commit`, on_send`, `on_finish`, and `on_error` are
52
+ # called in the reverse order. `on_finish` handlers are called inside an
53
+ # `ensure` block, so they are guaranteed to be called even if something
54
+ # raises an exception. If something raises an exception in a `on_finish`
55
+ # method, then nothing is guaranteed.
56
+
57
+ class Events
58
+ module Abstract
59
+ def on_start(req, res)
60
+ end
61
+
62
+ def on_commit(req, res)
63
+ end
64
+
65
+ def on_send(req, res)
66
+ end
67
+
68
+ def on_finish(req, res)
69
+ end
70
+
71
+ def on_error(req, res, e)
72
+ end
73
+ end
74
+
75
+ class EventedBodyProxy < Rack::BodyProxy # :nodoc:
76
+ attr_reader :request, :response
77
+
78
+ def initialize(body, request, response, handlers, &block)
79
+ super(body, &block)
80
+ @request = request
81
+ @response = response
82
+ @handlers = handlers
83
+ end
84
+
85
+ def each
86
+ @handlers.reverse_each { |handler| handler.on_send request, response }
87
+ super
88
+ end
89
+ end
90
+
91
+ class BufferedResponse < Rack::Response::Raw # :nodoc:
92
+ attr_reader :body
93
+
94
+ def initialize(status, headers, body)
95
+ super(status, headers)
96
+ @body = body
97
+ end
98
+
99
+ def to_a; [status, headers, body]; end
100
+ end
101
+
102
+ def initialize(app, handlers)
103
+ @app = app
104
+ @handlers = handlers
105
+ end
106
+
107
+ def call(env)
108
+ request = make_request env
109
+ on_start request, nil
110
+
111
+ begin
112
+ status, headers, body = @app.call request.env
113
+ response = make_response status, headers, body
114
+ on_commit request, response
115
+ rescue StandardError => e
116
+ on_error request, response, e
117
+ on_finish request, response
118
+ raise
119
+ end
120
+
121
+ body = EventedBodyProxy.new(body, request, response, @handlers) do
122
+ on_finish request, response
123
+ end
124
+ [response.status, response.headers, body]
125
+ end
126
+
127
+ private
128
+
129
+ def on_error(request, response, e)
130
+ @handlers.reverse_each { |handler| handler.on_error request, response, e }
131
+ end
132
+
133
+ def on_commit(request, response)
134
+ @handlers.reverse_each { |handler| handler.on_commit request, response }
135
+ end
136
+
137
+ def on_start(request, response)
138
+ @handlers.each { |handler| handler.on_start request, nil }
139
+ end
140
+
141
+ def on_finish(request, response)
142
+ @handlers.reverse_each { |handler| handler.on_finish request, response }
143
+ end
144
+
145
+ def make_request(env)
146
+ Rack::Request.new env
147
+ end
148
+
149
+ def make_response(status, headers, body)
150
+ BufferedResponse.new status, headers, body
151
+ end
152
+ end
153
+ end
data/lib/rack/file.rb CHANGED
@@ -1,88 +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+ given, according to the
7
- # path info of the Rack request.
8
- #
9
- # Handlers can detect if bodies are a Rack::File, and use mechanisms
10
- # like sendfile on the +path+.
11
-
12
- class File
13
- attr_accessor :root
14
- attr_accessor :path
15
-
16
- alias :to_path :path
17
-
18
- def initialize(root)
19
- @root = root
20
- end
21
-
22
- def call(env)
23
- dup._call(env)
24
- end
25
-
26
- F = ::File
27
-
28
- def _call(env)
29
- @path_info = Utils.unescape(env["PATH_INFO"])
30
- return forbidden if @path_info.include? ".."
31
-
32
- @path = F.join(@root, @path_info)
3
+ require_relative 'files'
33
4
 
34
- begin
35
- if F.file?(@path) && F.readable?(@path)
36
- serving
37
- else
38
- raise Errno::EPERM
39
- end
40
- rescue SystemCallError
41
- not_found
42
- end
43
- end
44
-
45
- def forbidden
46
- body = "Forbidden\n"
47
- [403, {"Content-Type" => "text/plain",
48
- "Content-Length" => body.size.to_s},
49
- [body]]
50
- end
51
-
52
- # NOTE:
53
- # We check via File::size? whether this file provides size info
54
- # via stat (e.g. /proc files often don't), otherwise we have to
55
- # figure it out by reading the whole file into memory. And while
56
- # we're at it we also use this as body then.
57
-
58
- def serving
59
- if size = F.size?(@path)
60
- body = self
61
- else
62
- body = [F.read(@path)]
63
- size = Utils.bytesize(body.first)
64
- end
65
-
66
- [200, {
67
- "Last-Modified" => F.mtime(@path).httpdate,
68
- "Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'),
69
- "Content-Length" => size.to_s
70
- }, body]
71
- end
72
-
73
- def not_found
74
- body = "File not found: #{@path_info}\n"
75
- [404, {"Content-Type" => "text/plain",
76
- "Content-Length" => body.size.to_s},
77
- [body]]
78
- end
79
-
80
- def each
81
- F.open(@path, "rb") { |file|
82
- while part = file.read(8192)
83
- yield part
84
- end
85
- }
86
- end
87
- end
5
+ module Rack
6
+ File = Files
88
7
  end