rack 1.6.11 → 2.2.0

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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +675 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +157 -163
  6. data/Rakefile +38 -32
  7. data/{SPEC → SPEC.rdoc} +41 -13
  8. data/bin/rackup +1 -0
  9. data/contrib/rack_logo.svg +164 -111
  10. data/example/lobster.ru +2 -0
  11. data/example/protectedlobster.rb +4 -2
  12. data/example/protectedlobster.ru +3 -1
  13. data/lib/rack/auth/abstract/handler.rb +3 -1
  14. data/lib/rack/auth/abstract/request.rb +6 -2
  15. data/lib/rack/auth/basic.rb +7 -4
  16. data/lib/rack/auth/digest/md5.rb +13 -11
  17. data/lib/rack/auth/digest/nonce.rb +6 -3
  18. data/lib/rack/auth/digest/params.rb +5 -4
  19. data/lib/rack/auth/digest/request.rb +6 -4
  20. data/lib/rack/body_proxy.rb +21 -15
  21. data/lib/rack/builder.rb +119 -26
  22. data/lib/rack/cascade.rb +28 -12
  23. data/lib/rack/chunked.rb +70 -22
  24. data/lib/rack/common_logger.rb +80 -0
  25. data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -16
  26. data/lib/rack/config.rb +2 -0
  27. data/lib/rack/content_length.rb +9 -8
  28. data/lib/rack/content_type.rb +5 -4
  29. data/lib/rack/core_ext/regexp.rb +14 -0
  30. data/lib/rack/deflater.rb +60 -70
  31. data/lib/rack/directory.rb +117 -85
  32. data/lib/rack/etag.rb +9 -7
  33. data/lib/rack/events.rb +153 -0
  34. data/lib/rack/file.rb +4 -149
  35. data/lib/rack/files.rb +218 -0
  36. data/lib/rack/handler/cgi.rb +17 -19
  37. data/lib/rack/handler/fastcgi.rb +17 -18
  38. data/lib/rack/handler/lsws.rb +14 -14
  39. data/lib/rack/handler/scgi.rb +22 -21
  40. data/lib/rack/handler/thin.rb +20 -11
  41. data/lib/rack/handler/webrick.rb +39 -32
  42. data/lib/rack/handler.rb +9 -26
  43. data/lib/rack/head.rb +16 -18
  44. data/lib/rack/lint.rb +110 -64
  45. data/lib/rack/lobster.rb +10 -10
  46. data/lib/rack/lock.rb +17 -11
  47. data/lib/rack/logger.rb +4 -2
  48. data/lib/rack/media_type.rb +43 -0
  49. data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
  50. data/lib/rack/mime.rb +27 -6
  51. data/lib/rack/mock.rb +124 -65
  52. data/lib/rack/multipart/generator.rb +20 -16
  53. data/lib/rack/multipart/parser.rb +273 -162
  54. data/lib/rack/multipart/uploaded_file.rb +15 -8
  55. data/lib/rack/multipart.rb +39 -8
  56. data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
  57. data/lib/rack/query_parser.rb +217 -0
  58. data/lib/rack/recursive.rb +11 -9
  59. data/lib/rack/reloader.rb +8 -4
  60. data/lib/rack/request.rb +543 -305
  61. data/lib/rack/response.rb +244 -88
  62. data/lib/rack/rewindable_input.rb +5 -15
  63. data/lib/rack/runtime.rb +12 -18
  64. data/lib/rack/sendfile.rb +17 -15
  65. data/lib/rack/server.rb +125 -47
  66. data/lib/rack/session/abstract/id.rb +216 -93
  67. data/lib/rack/session/cookie.rb +47 -31
  68. data/lib/rack/session/memcache.rb +4 -87
  69. data/lib/rack/session/pool.rb +26 -17
  70. data/lib/rack/show_exceptions.rb +390 -0
  71. data/lib/rack/{showstatus.rb → show_status.rb} +8 -8
  72. data/lib/rack/static.rb +48 -11
  73. data/lib/rack/tempfile_reaper.rb +3 -3
  74. data/lib/rack/urlmap.rb +26 -19
  75. data/lib/rack/utils.rb +208 -294
  76. data/lib/rack/version.rb +29 -0
  77. data/lib/rack.rb +76 -33
  78. data/rack.gemspec +43 -30
  79. metadata +62 -183
  80. data/HISTORY.md +0 -375
  81. data/KNOWN-ISSUES +0 -44
  82. data/lib/rack/backports/uri/common_18.rb +0 -56
  83. data/lib/rack/backports/uri/common_192.rb +0 -52
  84. data/lib/rack/backports/uri/common_193.rb +0 -29
  85. data/lib/rack/commonlogger.rb +0 -72
  86. data/lib/rack/handler/evented_mongrel.rb +0 -8
  87. data/lib/rack/handler/mongrel.rb +0 -106
  88. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  89. data/lib/rack/showexceptions.rb +0 -387
  90. data/lib/rack/utils/okjson.rb +0 -600
  91. data/test/builder/anything.rb +0 -5
  92. data/test/builder/comment.ru +0 -4
  93. data/test/builder/end.ru +0 -5
  94. data/test/builder/line.ru +0 -1
  95. data/test/builder/options.ru +0 -2
  96. data/test/cgi/assets/folder/test.js +0 -1
  97. data/test/cgi/assets/fonts/font.eot +0 -1
  98. data/test/cgi/assets/images/image.png +0 -1
  99. data/test/cgi/assets/index.html +0 -1
  100. data/test/cgi/assets/javascripts/app.js +0 -1
  101. data/test/cgi/assets/stylesheets/app.css +0 -1
  102. data/test/cgi/lighttpd.conf +0 -26
  103. data/test/cgi/rackup_stub.rb +0 -6
  104. data/test/cgi/sample_rackup.ru +0 -5
  105. data/test/cgi/test +0 -9
  106. data/test/cgi/test+directory/test+file +0 -1
  107. data/test/cgi/test.fcgi +0 -8
  108. data/test/cgi/test.ru +0 -5
  109. data/test/gemloader.rb +0 -10
  110. data/test/multipart/bad_robots +0 -259
  111. data/test/multipart/binary +0 -0
  112. data/test/multipart/content_type_and_no_filename +0 -6
  113. data/test/multipart/empty +0 -10
  114. data/test/multipart/fail_16384_nofile +0 -814
  115. data/test/multipart/file1.txt +0 -1
  116. data/test/multipart/filename_and_modification_param +0 -7
  117. data/test/multipart/filename_and_no_name +0 -6
  118. data/test/multipart/filename_with_escaped_quotes +0 -6
  119. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  120. data/test/multipart/filename_with_null_byte +0 -7
  121. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  122. data/test/multipart/filename_with_unescaped_percentages +0 -6
  123. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  124. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  125. data/test/multipart/filename_with_unescaped_quotes +0 -6
  126. data/test/multipart/ie +0 -6
  127. data/test/multipart/invalid_character +0 -6
  128. data/test/multipart/mixed_files +0 -21
  129. data/test/multipart/nested +0 -10
  130. data/test/multipart/none +0 -9
  131. data/test/multipart/semicolon +0 -6
  132. data/test/multipart/text +0 -15
  133. data/test/multipart/three_files_three_fields +0 -31
  134. data/test/multipart/webkit +0 -32
  135. data/test/rackup/config.ru +0 -31
  136. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  137. data/test/spec_auth_basic.rb +0 -81
  138. data/test/spec_auth_digest.rb +0 -259
  139. data/test/spec_body_proxy.rb +0 -85
  140. data/test/spec_builder.rb +0 -223
  141. data/test/spec_cascade.rb +0 -61
  142. data/test/spec_cgi.rb +0 -102
  143. data/test/spec_chunked.rb +0 -101
  144. data/test/spec_commonlogger.rb +0 -93
  145. data/test/spec_conditionalget.rb +0 -102
  146. data/test/spec_config.rb +0 -22
  147. data/test/spec_content_length.rb +0 -85
  148. data/test/spec_content_type.rb +0 -45
  149. data/test/spec_deflater.rb +0 -339
  150. data/test/spec_directory.rb +0 -88
  151. data/test/spec_etag.rb +0 -107
  152. data/test/spec_fastcgi.rb +0 -107
  153. data/test/spec_file.rb +0 -221
  154. data/test/spec_handler.rb +0 -72
  155. data/test/spec_head.rb +0 -45
  156. data/test/spec_lint.rb +0 -550
  157. data/test/spec_lobster.rb +0 -58
  158. data/test/spec_lock.rb +0 -164
  159. data/test/spec_logger.rb +0 -23
  160. data/test/spec_methodoverride.rb +0 -111
  161. data/test/spec_mime.rb +0 -51
  162. data/test/spec_mock.rb +0 -297
  163. data/test/spec_mongrel.rb +0 -182
  164. data/test/spec_multipart.rb +0 -600
  165. data/test/spec_nulllogger.rb +0 -20
  166. data/test/spec_recursive.rb +0 -72
  167. data/test/spec_request.rb +0 -1232
  168. data/test/spec_response.rb +0 -407
  169. data/test/spec_rewindable_input.rb +0 -118
  170. data/test/spec_runtime.rb +0 -49
  171. data/test/spec_sendfile.rb +0 -130
  172. data/test/spec_server.rb +0 -167
  173. data/test/spec_session_abstract_id.rb +0 -53
  174. data/test/spec_session_cookie.rb +0 -410
  175. data/test/spec_session_memcache.rb +0 -321
  176. data/test/spec_session_pool.rb +0 -209
  177. data/test/spec_showexceptions.rb +0 -98
  178. data/test/spec_showstatus.rb +0 -103
  179. data/test/spec_static.rb +0 -145
  180. data/test/spec_tempfile_reaper.rb +0 -63
  181. data/test/spec_thin.rb +0 -91
  182. data/test/spec_urlmap.rb +0 -236
  183. data/test/spec_utils.rb +0 -647
  184. data/test/spec_version.rb +0 -17
  185. data/test/spec_webrick.rb +0 -184
  186. data/test/static/another/index.html +0 -1
  187. data/test/static/index.html +0 -1
  188. data/test/testrequest.rb +0 -78
  189. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  190. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -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,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_relative '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
data/lib/rack/files.rb ADDED
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Rack
6
+ # Rack::Files serves files below the +root+ directory given, according to the
7
+ # path info of the Rack request.
8
+ # e.g. when Rack::Files.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::Files, and use mechanisms
12
+ # like sendfile on the +path+.
13
+
14
+ class Files
15
+ ALLOWED_VERBS = %w[GET HEAD OPTIONS]
16
+ ALLOW_HEADER = ALLOWED_VERBS.join(', ')
17
+ MULTIPART_BOUNDARY = 'AaB03x'
18
+
19
+ # @todo remove in 3.0
20
+ def self.method_added(name)
21
+ if name == :response_body
22
+ raise "#{self.class}\#response_body is no longer supported."
23
+ end
24
+ super
25
+ end
26
+
27
+ attr_reader :root
28
+
29
+ def initialize(root, headers = {}, default_mime = 'text/plain')
30
+ @root = (::File.expand_path(root) if root)
31
+ @headers = headers
32
+ @default_mime = default_mime
33
+ @head = Rack::Head.new(lambda { |env| get env })
34
+ end
35
+
36
+ def call(env)
37
+ # HEAD requests drop the response body, including 4xx error messages.
38
+ @head.call env
39
+ end
40
+
41
+ def get(env)
42
+ request = Rack::Request.new env
43
+ unless ALLOWED_VERBS.include? request.request_method
44
+ return fail(405, "Method Not Allowed", { 'Allow' => ALLOW_HEADER })
45
+ end
46
+
47
+ path_info = Utils.unescape_path request.path_info
48
+ return fail(400, "Bad Request") unless Utils.valid_path?(path_info)
49
+
50
+ clean_path_info = Utils.clean_path_info(path_info)
51
+ path = ::File.join(@root, clean_path_info)
52
+
53
+ available = begin
54
+ ::File.file?(path) && ::File.readable?(path)
55
+ rescue SystemCallError
56
+ # Not sure in what conditions this exception can occur, but this
57
+ # is a safe way to handle such an error.
58
+ # :nocov:
59
+ false
60
+ # :nocov:
61
+ end
62
+
63
+ if available
64
+ serving(request, path)
65
+ else
66
+ fail(404, "File not found: #{path_info}")
67
+ end
68
+ end
69
+
70
+ def serving(request, path)
71
+ if request.options?
72
+ return [200, { 'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
73
+ end
74
+ last_modified = ::File.mtime(path).httpdate
75
+ return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
76
+
77
+ headers = { "Last-Modified" => last_modified }
78
+ mime_type = mime_type path, @default_mime
79
+ headers[CONTENT_TYPE] = mime_type if mime_type
80
+
81
+ # Set custom headers
82
+ headers.merge!(@headers) if @headers
83
+
84
+ status = 200
85
+ size = filesize path
86
+
87
+ ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
88
+ if ranges.nil?
89
+ # No ranges:
90
+ ranges = [0..size - 1]
91
+ elsif ranges.empty?
92
+ # Unsatisfiable. Return error, and file size:
93
+ response = fail(416, "Byte range unsatisfiable")
94
+ response[1]["Content-Range"] = "bytes */#{size}"
95
+ return response
96
+ elsif ranges.size >= 1
97
+ # Partial content
98
+ partial_content = true
99
+
100
+ if ranges.size == 1
101
+ range = ranges[0]
102
+ headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
103
+ else
104
+ headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}"
105
+ end
106
+
107
+ status = 206
108
+ body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size)
109
+ size = body.bytesize
110
+ end
111
+
112
+ headers[CONTENT_LENGTH] = size.to_s
113
+
114
+ if request.head?
115
+ body = []
116
+ elsif !partial_content
117
+ body = Iterator.new(path, ranges, mime_type: mime_type, size: size)
118
+ end
119
+
120
+ [status, headers, body]
121
+ end
122
+
123
+ class BaseIterator
124
+ attr_reader :path, :ranges, :options
125
+
126
+ def initialize(path, ranges, options)
127
+ @path = path
128
+ @ranges = ranges
129
+ @options = options
130
+ end
131
+
132
+ def each
133
+ ::File.open(path, "rb") do |file|
134
+ ranges.each do |range|
135
+ yield multipart_heading(range) if multipart?
136
+
137
+ each_range_part(file, range) do |part|
138
+ yield part
139
+ end
140
+ end
141
+
142
+ yield "\r\n--#{MULTIPART_BOUNDARY}--\r\n" if multipart?
143
+ end
144
+ end
145
+
146
+ def bytesize
147
+ size = ranges.inject(0) do |sum, range|
148
+ sum += multipart_heading(range).bytesize if multipart?
149
+ sum += range.size
150
+ end
151
+ size += "\r\n--#{MULTIPART_BOUNDARY}--\r\n".bytesize if multipart?
152
+ size
153
+ end
154
+
155
+ def close; end
156
+
157
+ private
158
+
159
+ def multipart?
160
+ ranges.size > 1
161
+ end
162
+
163
+ def multipart_heading(range)
164
+ <<-EOF
165
+ \r
166
+ --#{MULTIPART_BOUNDARY}\r
167
+ Content-Type: #{options[:mime_type]}\r
168
+ Content-Range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
169
+ \r
170
+ EOF
171
+ end
172
+
173
+ def each_range_part(file, range)
174
+ file.seek(range.begin)
175
+ remaining_len = range.end - range.begin + 1
176
+ while remaining_len > 0
177
+ part = file.read([8192, remaining_len].min)
178
+ break unless part
179
+ remaining_len -= part.length
180
+
181
+ yield part
182
+ end
183
+ end
184
+ end
185
+
186
+ class Iterator < BaseIterator
187
+ alias :to_path :path
188
+ end
189
+
190
+ private
191
+
192
+ def fail(status, body, headers = {})
193
+ body += "\n"
194
+
195
+ [
196
+ status,
197
+ {
198
+ CONTENT_TYPE => "text/plain",
199
+ CONTENT_LENGTH => body.size.to_s,
200
+ "X-Cascade" => "pass"
201
+ }.merge!(headers),
202
+ [body]
203
+ ]
204
+ end
205
+
206
+ # The MIME type for the contents of the file located at @path
207
+ def mime_type(path, default_mime)
208
+ Mime.mime_type(::File.extname(path), default_mime)
209
+ end
210
+
211
+ def filesize(path)
212
+ # We check via File::size? whether this file provides size info
213
+ # via stat (e.g. /proc files often don't), otherwise we have to
214
+ # figure it out by reading the whole file into memory.
215
+ ::File.size?(path) || ::File.read(path).bytesize
216
+ end
217
+ end
218
+ end
@@ -1,10 +1,9 @@
1
- require 'rack/content_length'
2
- require 'rack/rewindable_input'
1
+ # frozen_string_literal: true
3
2
 
4
3
  module Rack
5
4
  module Handler
6
5
  class CGI
7
- def self.run(app, options=nil)
6
+ def self.run(app, **options)
8
7
  $stdin.binmode
9
8
  serve app
10
9
  end
@@ -13,22 +12,21 @@ module Rack
13
12
  env = ENV.to_hash
14
13
  env.delete "HTTP_CONTENT_LENGTH"
15
14
 
16
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
17
-
18
- env.update({"rack.version" => Rack::VERSION,
19
- "rack.input" => Rack::RewindableInput.new($stdin),
20
- "rack.errors" => $stderr,
21
-
22
- "rack.multithread" => false,
23
- "rack.multiprocess" => true,
24
- "rack.run_once" => true,
25
-
26
- "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
27
- })
28
-
29
- env[QUERY_STRING] ||= ""
30
- env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
31
- env["REQUEST_PATH"] ||= "/"
15
+ env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/"
16
+
17
+ env.update(
18
+ RACK_VERSION => Rack::VERSION,
19
+ RACK_INPUT => Rack::RewindableInput.new($stdin),
20
+ RACK_ERRORS => $stderr,
21
+ RACK_MULTITHREAD => false,
22
+ RACK_MULTIPROCESS => true,
23
+ RACK_RUNONCE => true,
24
+ RACK_URL_SCHEME => ["yes", "on", "1"].include?(ENV[HTTPS]) ? "https" : "http"
25
+ )
26
+
27
+ env[QUERY_STRING] ||= ""
28
+ env[HTTP_VERSION] ||= env[SERVER_PROTOCOL]
29
+ env[REQUEST_PATH] ||= "/"
32
30
 
33
31
  status, headers, body = app.call(env)
34
32
  begin
@@ -1,13 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fcgi'
2
4
  require 'socket'
3
- require 'rack/content_length'
4
- require 'rack/rewindable_input'
5
5
 
6
6
  if defined? FCGI::Stream
7
7
  class FCGI::Stream
8
8
  alias _rack_read_without_buffer read
9
9
 
10
- def read(n, buffer=nil)
10
+ def read(n, buffer = nil)
11
11
  buf = _rack_read_without_buffer n
12
12
  buffer.replace(buf.to_s) if buffer
13
13
  buf
@@ -18,7 +18,7 @@ end
18
18
  module Rack
19
19
  module Handler
20
20
  class FastCGI
21
- def self.run(app, options={})
21
+ def self.run(app, **options)
22
22
  if options[:File]
23
23
  STDIN.reopen(UNIXServer.new(options[:File]))
24
24
  elsif options[:Port]
@@ -44,24 +44,23 @@ module Rack
44
44
  env = request.env
45
45
  env.delete "HTTP_CONTENT_LENGTH"
46
46
 
47
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
47
+ env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/"
48
48
 
49
49
  rack_input = RewindableInput.new(request.in)
50
50
 
51
- env.update({"rack.version" => Rack::VERSION,
52
- "rack.input" => rack_input,
53
- "rack.errors" => request.err,
54
-
55
- "rack.multithread" => false,
56
- "rack.multiprocess" => true,
57
- "rack.run_once" => false,
58
-
59
- "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
60
- })
51
+ env.update(
52
+ RACK_VERSION => Rack::VERSION,
53
+ RACK_INPUT => rack_input,
54
+ RACK_ERRORS => request.err,
55
+ RACK_MULTITHREAD => false,
56
+ RACK_MULTIPROCESS => true,
57
+ RACK_RUNONCE => false,
58
+ RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http"
59
+ )
61
60
 
62
- env[QUERY_STRING] ||= ""
63
- env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
64
- env["REQUEST_PATH"] ||= "/"
61
+ env[QUERY_STRING] ||= ""
62
+ env[HTTP_VERSION] ||= env[SERVER_PROTOCOL]
63
+ env[REQUEST_PATH] ||= "/"
65
64
  env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
66
65
  env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
67
66
 
@@ -1,11 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'lsapi'
2
- require 'rack/content_length'
3
- require 'rack/rewindable_input'
4
4
 
5
5
  module Rack
6
6
  module Handler
7
7
  class LSWS
8
- def self.run(app, options=nil)
8
+ def self.run(app, **options)
9
9
  while LSAPI.accept != nil
10
10
  serve app
11
11
  end
@@ -13,23 +13,23 @@ module Rack
13
13
  def self.serve(app)
14
14
  env = ENV.to_hash
15
15
  env.delete "HTTP_CONTENT_LENGTH"
16
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
16
+ env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/"
17
17
 
18
18
  rack_input = RewindableInput.new($stdin.read.to_s)
19
19
 
20
20
  env.update(
21
- "rack.version" => Rack::VERSION,
22
- "rack.input" => rack_input,
23
- "rack.errors" => $stderr,
24
- "rack.multithread" => false,
25
- "rack.multiprocess" => true,
26
- "rack.run_once" => false,
27
- "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
21
+ RACK_VERSION => Rack::VERSION,
22
+ RACK_INPUT => rack_input,
23
+ RACK_ERRORS => $stderr,
24
+ RACK_MULTITHREAD => false,
25
+ RACK_MULTIPROCESS => true,
26
+ RACK_RUNONCE => false,
27
+ RACK_URL_SCHEME => ["yes", "on", "1"].include?(ENV[HTTPS]) ? "https" : "http"
28
28
  )
29
29
 
30
- env[QUERY_STRING] ||= ""
31
- env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
32
- env["REQUEST_PATH"] ||= "/"
30
+ env[QUERY_STRING] ||= ""
31
+ env[HTTP_VERSION] ||= env[SERVER_PROTOCOL]
32
+ env[REQUEST_PATH] ||= "/"
33
33
  status, headers, body = app.call(env)
34
34
  begin
35
35
  send_headers status, headers