rack 2.0.6 → 2.1.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.
- checksums.yaml +4 -4
- data/{HISTORY.md → CHANGELOG.md} +220 -155
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +77 -117
- data/Rakefile +25 -18
- data/SPEC +3 -4
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/auth/basic.rb +4 -1
- data/lib/rack/auth/digest/md5.rb +9 -7
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +4 -2
- data/lib/rack/auth/digest/request.rb +2 -0
- data/lib/rack/body_proxy.rb +3 -6
- data/lib/rack/builder.rb +38 -15
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +29 -6
- data/lib/rack/common_logger.rb +9 -8
- data/lib/rack/conditional_get.rb +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +3 -1
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +28 -17
- data/lib/rack/directory.rb +17 -14
- data/lib/rack/etag.rb +3 -1
- data/lib/rack/events.rb +5 -3
- data/lib/rack/file.rb +5 -173
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler/cgi.rb +3 -1
- data/lib/rack/handler/fastcgi.rb +4 -2
- data/lib/rack/handler/lsws.rb +3 -1
- data/lib/rack/handler/scgi.rb +9 -6
- data/lib/rack/handler/thin.rb +3 -1
- data/lib/rack/handler/webrick.rb +4 -2
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/head.rb +2 -0
- data/lib/rack/lint.rb +14 -11
- data/lib/rack/lobster.rb +7 -5
- data/lib/rack/lock.rb +2 -0
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +74 -15
- data/lib/rack/multipart/generator.rb +6 -7
- data/lib/rack/multipart/parser.rb +55 -52
- data/lib/rack/multipart/uploaded_file.rb +2 -0
- data/lib/rack/multipart.rb +5 -3
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +51 -25
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +80 -27
- data/lib/rack/response.rb +71 -31
- data/lib/rack/rewindable_input.rb +4 -2
- data/lib/rack/runtime.rb +4 -2
- data/lib/rack/sendfile.rb +15 -8
- data/lib/rack/server.rb +88 -16
- data/lib/rack/session/abstract/id.rb +104 -21
- data/lib/rack/session/cookie.rb +21 -11
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +17 -8
- data/lib/rack/show_exceptions.rb +15 -9
- data/lib/rack/show_status.rb +4 -2
- data/lib/rack/static.rb +15 -10
- data/lib/rack/tempfile_reaper.rb +2 -0
- data/lib/rack/urlmap.rb +11 -2
- data/lib/rack/utils.rb +55 -70
- data/lib/rack.rb +63 -60
- data/rack.gemspec +17 -7
- metadata +30 -171
- data/test/builder/an_underscore_app.rb +0 -5
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_encoded_words +0 -7
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_single_quote +0 -7
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -95
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -515
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -722
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1398
- data/test/spec_response.rb +0 -510
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -320
- data/test/spec_session_pool.rb +0 -210
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/builder.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Rack::Builder implements a small DSL to iteratively construct Rack
|
3
5
|
# applications.
|
@@ -29,29 +31,43 @@ module Rack
|
|
29
31
|
# You can use +map+ to construct a Rack::URLMap in a convenient way.
|
30
32
|
|
31
33
|
class Builder
|
34
|
+
|
35
|
+
# https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
|
36
|
+
UTF_8_BOM = '\xef\xbb\xbf'
|
37
|
+
|
32
38
|
def self.parse_file(config, opts = Server::Options.new)
|
33
|
-
|
34
|
-
|
35
|
-
cfgfile = ::File.read(config)
|
36
|
-
if cfgfile[/^#\\(.*)/] && opts
|
37
|
-
options = opts.parse! $1.split(/\s+/)
|
38
|
-
end
|
39
|
-
cfgfile.sub!(/^__END__\n.*\Z/m, '')
|
40
|
-
app = new_from_string cfgfile, config
|
39
|
+
if config.end_with?('.ru')
|
40
|
+
return self.load_file(config, opts)
|
41
41
|
else
|
42
42
|
require config
|
43
43
|
app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
|
44
|
+
return app, {}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.load_file(path, opts = Server::Options.new)
|
49
|
+
options = {}
|
50
|
+
|
51
|
+
cfgfile = ::File.read(path)
|
52
|
+
cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
|
53
|
+
|
54
|
+
if cfgfile[/^#\\(.*)/] && opts
|
55
|
+
options = opts.parse! $1.split(/\s+/)
|
44
56
|
end
|
57
|
+
|
58
|
+
cfgfile.sub!(/^__END__\n.*\Z/m, '')
|
59
|
+
app = new_from_string cfgfile, path
|
60
|
+
|
45
61
|
return app, options
|
46
62
|
end
|
47
63
|
|
48
|
-
def self.new_from_string(builder_script, file="(rackup)")
|
64
|
+
def self.new_from_string(builder_script, file = "(rackup)")
|
49
65
|
eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
|
50
66
|
TOPLEVEL_BINDING, file, 0
|
51
67
|
end
|
52
68
|
|
53
69
|
def initialize(default_app = nil, &block)
|
54
|
-
@use, @map, @run, @warmup = [], nil, default_app, nil
|
70
|
+
@use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
|
55
71
|
instance_eval(&block) if block_given?
|
56
72
|
end
|
57
73
|
|
@@ -81,7 +97,7 @@ module Rack
|
|
81
97
|
def use(middleware, *args, &block)
|
82
98
|
if @map
|
83
99
|
mapping, @map = @map, nil
|
84
|
-
@use << proc { |app| generate_map
|
100
|
+
@use << proc { |app| generate_map(app, mapping) }
|
85
101
|
end
|
86
102
|
@use << proc { |app| middleware.new(app, *args, &block) }
|
87
103
|
end
|
@@ -113,7 +129,7 @@ module Rack
|
|
113
129
|
#
|
114
130
|
# use SomeMiddleware
|
115
131
|
# run MyApp
|
116
|
-
def warmup(prc=nil, &block)
|
132
|
+
def warmup(prc = nil, &block)
|
117
133
|
@warmup = prc || block
|
118
134
|
end
|
119
135
|
|
@@ -141,10 +157,17 @@ module Rack
|
|
141
157
|
@map[path] = block
|
142
158
|
end
|
143
159
|
|
160
|
+
# Freeze the app (set using run) and all middleware instances when building the application
|
161
|
+
# in to_app.
|
162
|
+
def freeze_app
|
163
|
+
@freeze_app = true
|
164
|
+
end
|
165
|
+
|
144
166
|
def to_app
|
145
167
|
app = @map ? generate_map(@run, @map) : @run
|
146
168
|
fail "missing run or map statement" unless app
|
147
|
-
app
|
169
|
+
app.freeze if @freeze_app
|
170
|
+
app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
|
148
171
|
@warmup.call(app) if @warmup
|
149
172
|
app
|
150
173
|
end
|
@@ -156,8 +179,8 @@ module Rack
|
|
156
179
|
private
|
157
180
|
|
158
181
|
def generate_map(default_app, mapping)
|
159
|
-
mapped = default_app ? {'/' => default_app} : {}
|
160
|
-
mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
|
182
|
+
mapped = default_app ? { '/' => default_app } : {}
|
183
|
+
mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
|
161
184
|
URLMap.new(mapped)
|
162
185
|
end
|
163
186
|
end
|
data/lib/rack/cascade.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Rack::Cascade tries a request on several apps, and returns the
|
3
5
|
# first response that is not 404 or 405 (or in a list of configurable
|
4
6
|
# status codes).
|
5
7
|
|
6
8
|
class Cascade
|
7
|
-
NotFound = [404, {CONTENT_TYPE => "text/plain"}, []]
|
9
|
+
NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
|
8
10
|
|
9
11
|
attr_reader :apps
|
10
12
|
|
11
|
-
def initialize(apps, catch=[404, 405])
|
12
|
-
@apps = []
|
13
|
+
def initialize(apps, catch = [404, 405])
|
14
|
+
@apps = []
|
13
15
|
apps.each { |app| add app }
|
14
16
|
|
15
17
|
@catch = {}
|
@@ -39,12 +41,11 @@ module Rack
|
|
39
41
|
end
|
40
42
|
|
41
43
|
def add(app)
|
42
|
-
@has_app[app] = true
|
43
44
|
@apps << app
|
44
45
|
end
|
45
46
|
|
46
47
|
def include?(app)
|
47
|
-
@
|
48
|
+
@apps.include?(app)
|
48
49
|
end
|
49
50
|
|
50
51
|
alias_method :<<, :add
|
data/lib/rack/chunked.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/utils'
|
2
4
|
|
3
5
|
module Rack
|
@@ -10,7 +12,7 @@ module Rack
|
|
10
12
|
# A body wrapper that emits chunked responses
|
11
13
|
class Body
|
12
14
|
TERM = "\r\n"
|
13
|
-
TAIL = "0#{TERM}
|
15
|
+
TAIL = "0#{TERM}"
|
14
16
|
|
15
17
|
include Rack::Utils
|
16
18
|
|
@@ -18,7 +20,7 @@ module Rack
|
|
18
20
|
@body = body
|
19
21
|
end
|
20
22
|
|
21
|
-
def each
|
23
|
+
def each(&block)
|
22
24
|
term = TERM
|
23
25
|
@body.each do |chunk|
|
24
26
|
size = chunk.bytesize
|
@@ -28,11 +30,28 @@ module Rack
|
|
28
30
|
yield [size.to_s(16), term, chunk, term].join
|
29
31
|
end
|
30
32
|
yield TAIL
|
33
|
+
insert_trailers(&block)
|
34
|
+
yield TERM
|
31
35
|
end
|
32
36
|
|
33
37
|
def close
|
34
38
|
@body.close if @body.respond_to?(:close)
|
35
39
|
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def insert_trailers(&block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class TrailerBody < Body
|
48
|
+
private
|
49
|
+
|
50
|
+
def insert_trailers(&block)
|
51
|
+
@body.trailers.each_pair do |k, v|
|
52
|
+
yield "#{k}: #{v}\r\n"
|
53
|
+
end
|
54
|
+
end
|
36
55
|
end
|
37
56
|
|
38
57
|
def initialize(app)
|
@@ -43,7 +62,7 @@ module Rack
|
|
43
62
|
# a version (nor response headers)
|
44
63
|
def chunkable_version?(ver)
|
45
64
|
case ver
|
46
|
-
when
|
65
|
+
when 'HTTP/1.0', nil, 'HTTP/0.9'
|
47
66
|
false
|
48
67
|
else
|
49
68
|
true
|
@@ -54,15 +73,19 @@ module Rack
|
|
54
73
|
status, headers, body = @app.call(env)
|
55
74
|
headers = HeaderHash.new(headers)
|
56
75
|
|
57
|
-
if ! chunkable_version?(env[
|
58
|
-
STATUS_WITH_NO_ENTITY_BODY.
|
76
|
+
if ! chunkable_version?(env[SERVER_PROTOCOL]) ||
|
77
|
+
STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
|
59
78
|
headers[CONTENT_LENGTH] ||
|
60
79
|
headers[TRANSFER_ENCODING]
|
61
80
|
[status, headers, body]
|
62
81
|
else
|
63
82
|
headers.delete(CONTENT_LENGTH)
|
64
83
|
headers[TRANSFER_ENCODING] = 'chunked'
|
65
|
-
|
84
|
+
if headers['Trailer']
|
85
|
+
[status, headers, TrailerBody.new(body)]
|
86
|
+
else
|
87
|
+
[status, headers, Body.new(body)]
|
88
|
+
end
|
66
89
|
end
|
67
90
|
end
|
68
91
|
end
|
data/lib/rack/common_logger.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/body_proxy'
|
2
4
|
|
3
5
|
module Rack
|
@@ -23,13 +25,13 @@ module Rack
|
|
23
25
|
# %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
|
24
26
|
FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
|
25
27
|
|
26
|
-
def initialize(app, logger=nil)
|
28
|
+
def initialize(app, logger = nil)
|
27
29
|
@app = app
|
28
30
|
@logger = logger
|
29
31
|
end
|
30
32
|
|
31
33
|
def call(env)
|
32
|
-
began_at =
|
34
|
+
began_at = Utils.clock_time
|
33
35
|
status, header, body = @app.call(env)
|
34
36
|
header = Utils::HeaderHash.new(header)
|
35
37
|
body = BodyProxy.new(body) { log(env, status, header, began_at) }
|
@@ -39,20 +41,19 @@ module Rack
|
|
39
41
|
private
|
40
42
|
|
41
43
|
def log(env, status, header, began_at)
|
42
|
-
now = Time.now
|
43
44
|
length = extract_content_length(header)
|
44
45
|
|
45
46
|
msg = FORMAT % [
|
46
47
|
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
|
47
48
|
env["REMOTE_USER"] || "-",
|
48
|
-
now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
49
|
+
Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
49
50
|
env[REQUEST_METHOD],
|
50
51
|
env[PATH_INFO],
|
51
52
|
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
52
|
-
env[
|
53
|
+
env[SERVER_PROTOCOL],
|
53
54
|
status.to_s[0..3],
|
54
55
|
length,
|
55
|
-
|
56
|
+
Utils.clock_time - began_at ]
|
56
57
|
|
57
58
|
logger = @logger || env[RACK_ERRORS]
|
58
59
|
# Standard library logger doesn't support write but it supports << which actually
|
@@ -65,8 +66,8 @@ module Rack
|
|
65
66
|
end
|
66
67
|
|
67
68
|
def extract_content_length(headers)
|
68
|
-
value = headers[CONTENT_LENGTH]
|
69
|
-
value.to_s == '0' ? '-' : value
|
69
|
+
value = headers[CONTENT_LENGTH]
|
70
|
+
!value || value.to_s == '0' ? '-' : value
|
70
71
|
end
|
71
72
|
end
|
72
73
|
end
|
data/lib/rack/conditional_get.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/utils'
|
2
4
|
|
3
5
|
module Rack
|
@@ -68,7 +70,7 @@ module Rack
|
|
68
70
|
# anything shorter is invalid, this avoids exceptions for common cases
|
69
71
|
# most common being the empty string
|
70
72
|
if since && since.length >= 16
|
71
|
-
# NOTE: there is no trivial way to write this in a non
|
73
|
+
# NOTE: there is no trivial way to write this in a non exception way
|
72
74
|
# _rfc2822 returns a hash but is not that usable
|
73
75
|
Time.rfc2822(since) rescue nil
|
74
76
|
else
|
data/lib/rack/config.rb
CHANGED
data/lib/rack/content_length.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/utils'
|
2
4
|
require 'rack/body_proxy'
|
3
5
|
|
@@ -15,7 +17,7 @@ module Rack
|
|
15
17
|
status, headers, body = @app.call(env)
|
16
18
|
headers = HeaderHash.new(headers)
|
17
19
|
|
18
|
-
if !STATUS_WITH_NO_ENTITY_BODY.
|
20
|
+
if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
|
19
21
|
!headers[CONTENT_LENGTH] &&
|
20
22
|
!headers[TRANSFER_ENCODING] &&
|
21
23
|
body.respond_to?(:to_ary)
|
data/lib/rack/content_type.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/utils'
|
2
4
|
|
3
5
|
module Rack
|
@@ -19,7 +21,7 @@ module Rack
|
|
19
21
|
status, headers, body = @app.call(env)
|
20
22
|
headers = Utils::HeaderHash.new(headers)
|
21
23
|
|
22
|
-
unless STATUS_WITH_NO_ENTITY_BODY.
|
24
|
+
unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i)
|
23
25
|
headers[CONTENT_TYPE] ||= @content_type
|
24
26
|
end
|
25
27
|
|
@@ -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,7 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "zlib"
|
2
4
|
require "time" # for Time.httpdate
|
3
5
|
require 'rack/utils'
|
4
6
|
|
7
|
+
require_relative 'core_ext/regexp'
|
8
|
+
|
5
9
|
module Rack
|
6
10
|
# This middleware enables compression of http responses.
|
7
11
|
#
|
@@ -15,19 +19,26 @@ module Rack
|
|
15
19
|
# directive of 'no-transform' is present, or when the response status
|
16
20
|
# code is one that doesn't allow an entity body.
|
17
21
|
class Deflater
|
22
|
+
using ::Rack::RegexpExtensions
|
23
|
+
|
18
24
|
##
|
19
25
|
# Creates Rack::Deflater middleware.
|
20
26
|
#
|
21
27
|
# [app] rack app instance
|
22
28
|
# [options] hash of deflater options, i.e.
|
23
29
|
# 'if' - a lambda enabling / disabling deflation based on returned boolean value
|
24
|
-
# e.g use Rack::Deflater, :if => lambda { |
|
30
|
+
# e.g use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }
|
25
31
|
# 'include' - a list of content types that should be compressed
|
32
|
+
# 'sync' - determines if the stream is going to be flushed after every chunk.
|
33
|
+
# Flushing after every chunk reduces latency for
|
34
|
+
# time-sensitive streaming applications, but hurts
|
35
|
+
# compression and throughput. Defaults to `true'.
|
26
36
|
def initialize(app, options = {})
|
27
37
|
@app = app
|
28
38
|
|
29
39
|
@condition = options[:if]
|
30
40
|
@compressible_types = options[:include]
|
41
|
+
@sync = options[:sync] == false ? false : true
|
31
42
|
end
|
32
43
|
|
33
44
|
def call(env)
|
@@ -52,33 +63,34 @@ module Rack
|
|
52
63
|
case encoding
|
53
64
|
when "gzip"
|
54
65
|
headers['Content-Encoding'] = "gzip"
|
55
|
-
headers.delete(
|
56
|
-
mtime = headers
|
57
|
-
|
58
|
-
[status, headers, GzipStream.new(body, mtime)]
|
66
|
+
headers.delete('Content-Length')
|
67
|
+
mtime = headers["Last-Modified"]
|
68
|
+
mtime = Time.httpdate(mtime).to_i if mtime
|
69
|
+
[status, headers, GzipStream.new(body, mtime, @sync)]
|
59
70
|
when "identity"
|
60
71
|
[status, headers, body]
|
61
72
|
when nil
|
62
73
|
message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
|
63
74
|
bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
|
64
|
-
[406, {
|
75
|
+
[406, { 'Content-Type' => "text/plain", 'Content-Length' => message.length.to_s }, bp]
|
65
76
|
end
|
66
77
|
end
|
67
78
|
|
68
79
|
class GzipStream
|
69
|
-
def initialize(body, mtime)
|
80
|
+
def initialize(body, mtime, sync)
|
81
|
+
@sync = sync
|
70
82
|
@body = body
|
71
83
|
@mtime = mtime
|
72
|
-
@closed = false
|
73
84
|
end
|
74
85
|
|
75
86
|
def each(&block)
|
76
87
|
@writer = block
|
77
|
-
gzip
|
78
|
-
gzip.mtime = @mtime
|
88
|
+
gzip = ::Zlib::GzipWriter.new(self)
|
89
|
+
gzip.mtime = @mtime if @mtime
|
79
90
|
@body.each { |part|
|
80
|
-
gzip.write(part)
|
81
|
-
|
91
|
+
len = gzip.write(part)
|
92
|
+
# Flushing empty parts would raise Zlib::BufError.
|
93
|
+
gzip.flush if @sync && len > 0
|
82
94
|
}
|
83
95
|
ensure
|
84
96
|
gzip.close
|
@@ -90,9 +102,8 @@ module Rack
|
|
90
102
|
end
|
91
103
|
|
92
104
|
def close
|
93
|
-
return if @closed
|
94
|
-
@closed = true
|
95
105
|
@body.close if @body.respond_to?(:close)
|
106
|
+
@body = nil
|
96
107
|
end
|
97
108
|
end
|
98
109
|
|
@@ -101,14 +112,14 @@ module Rack
|
|
101
112
|
def should_deflate?(env, status, headers, body)
|
102
113
|
# Skip compressing empty entity body responses and responses with
|
103
114
|
# no-transform set.
|
104
|
-
if Utils::STATUS_WITH_NO_ENTITY_BODY.
|
105
|
-
|
115
|
+
if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
|
116
|
+
/\bno-transform\b/.match?(headers['Cache-Control'].to_s) ||
|
106
117
|
(headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
|
107
118
|
return false
|
108
119
|
end
|
109
120
|
|
110
121
|
# Skip if @compressible_types are given and does not include request's content type
|
111
|
-
return false if @compressible_types && !(headers.has_key?(
|
122
|
+
return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
|
112
123
|
|
113
124
|
# Skip if @condition lambda is given and evaluates to false
|
114
125
|
return false if @condition && !@condition.call(env, status, headers, body)
|
data/lib/rack/directory.rb
CHANGED
@@ -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::
|
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>"
|
@@ -41,9 +44,9 @@ table { width:100%%; }
|
|
41
44
|
|
42
45
|
class DirectoryBody < Struct.new(:root, :path, :files)
|
43
46
|
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
|
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 ]
|
47
50
|
page.each_line{|l| yield l }
|
48
51
|
end
|
49
52
|
|
@@ -56,9 +59,9 @@ table { width:100%%; }
|
|
56
59
|
|
57
60
|
attr_reader :root, :path
|
58
61
|
|
59
|
-
def initialize(root, app=nil)
|
62
|
+
def initialize(root, app = nil)
|
60
63
|
@root = ::File.expand_path(root)
|
61
|
-
@app = app || Rack::
|
64
|
+
@app = app || Rack::Files.new(@root)
|
62
65
|
@head = Rack::Head.new(lambda { |env| get env })
|
63
66
|
end
|
64
67
|
|
@@ -86,9 +89,9 @@ table { width:100%%; }
|
|
86
89
|
|
87
90
|
body = "Bad Request\n"
|
88
91
|
size = body.bytesize
|
89
|
-
return [400, {CONTENT_TYPE => "text/plain",
|
92
|
+
return [400, { CONTENT_TYPE => "text/plain",
|
90
93
|
CONTENT_LENGTH => size.to_s,
|
91
|
-
"X-Cascade" => "pass"}, [body]]
|
94
|
+
"X-Cascade" => "pass" }, [body]]
|
92
95
|
end
|
93
96
|
|
94
97
|
def check_forbidden(path_info)
|
@@ -96,13 +99,13 @@ table { width:100%%; }
|
|
96
99
|
|
97
100
|
body = "Forbidden\n"
|
98
101
|
size = body.bytesize
|
99
|
-
return [403, {CONTENT_TYPE => "text/plain",
|
102
|
+
return [403, { CONTENT_TYPE => "text/plain",
|
100
103
|
CONTENT_LENGTH => size.to_s,
|
101
|
-
"X-Cascade" => "pass"}, [body]]
|
104
|
+
"X-Cascade" => "pass" }, [body]]
|
102
105
|
end
|
103
106
|
|
104
107
|
def list_directory(path_info, path, script_name)
|
105
|
-
files = [['../','Parent Directory','','','']]
|
108
|
+
files = [['../', 'Parent Directory', '', '', '']]
|
106
109
|
glob = ::File.join(path, '*')
|
107
110
|
|
108
111
|
url_head = (script_name.split('/') + path_info.split('/')).map do |part|
|
@@ -126,7 +129,7 @@ table { width:100%%; }
|
|
126
129
|
files << [ url, basename, size, type, mtime ]
|
127
130
|
end
|
128
131
|
|
129
|
-
return [ 200, { CONTENT_TYPE =>'text/html; charset=utf-8'}, DirectoryBody.new(@root, path, files) ]
|
132
|
+
return [ 200, { CONTENT_TYPE => 'text/html; charset=utf-8' }, DirectoryBody.new(@root, path, files) ]
|
130
133
|
end
|
131
134
|
|
132
135
|
def stat(node)
|
@@ -154,9 +157,9 @@ table { width:100%%; }
|
|
154
157
|
def entity_not_found(path_info)
|
155
158
|
body = "Entity not found: #{path_info}\n"
|
156
159
|
size = body.bytesize
|
157
|
-
return [404, {CONTENT_TYPE => "text/plain",
|
160
|
+
return [404, { CONTENT_TYPE => "text/plain",
|
158
161
|
CONTENT_LENGTH => size.to_s,
|
159
|
-
"X-Cascade" => "pass"}, [body]]
|
162
|
+
"X-Cascade" => "pass" }, [body]]
|
160
163
|
end
|
161
164
|
|
162
165
|
# Stolen from Ramaze
|
data/lib/rack/etag.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack'
|
2
4
|
require 'digest/sha2'
|
3
5
|
|
@@ -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"
|
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
|
data/lib/rack/events.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/response'
|
2
4
|
require 'rack/body_proxy'
|
3
5
|
|
4
6
|
module Rack
|
5
7
|
### 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".
|
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".
|
9
11
|
#
|
10
12
|
# The events are:
|
11
13
|
#
|