rack-contrib 2.2.0 → 2.4.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-contrib might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +0 -1
- data/lib/rack/contrib/access.rb +8 -6
- data/lib/rack/contrib/backstage.rb +5 -3
- data/lib/rack/contrib/bounce_favicon.rb +3 -1
- data/lib/rack/contrib/callbacks.rb +2 -0
- data/lib/rack/contrib/common_cookies.rb +19 -11
- data/lib/rack/contrib/config.rb +3 -15
- data/lib/rack/contrib/cookies.rb +2 -0
- data/lib/rack/contrib/csshttprequest.rb +11 -5
- data/lib/rack/contrib/deflect.rb +35 -33
- data/lib/rack/contrib/enforce_valid_encoding.rb +3 -1
- data/lib/rack/contrib/evil.rb +2 -0
- data/lib/rack/contrib/expectation_cascade.rb +5 -3
- data/lib/rack/contrib/garbagecollector.rb +2 -0
- data/lib/rack/contrib/host_meta.rb +3 -1
- data/lib/rack/contrib/json_body_parser.rb +22 -11
- data/lib/rack/contrib/jsonp.rb +12 -7
- data/lib/rack/contrib/lazy_conditional_get.rb +13 -4
- data/lib/rack/contrib/lighttpd_script_name_fix.rb +2 -0
- data/lib/rack/contrib/locale.rb +6 -0
- data/lib/rack/contrib/mailexceptions.rb +2 -0
- data/lib/rack/contrib/nested_params.rb +3 -1
- data/lib/rack/contrib/not_found.rb +3 -1
- data/lib/rack/contrib/post_body_content_type_parser.rb +4 -2
- data/lib/rack/contrib/printout.rb +3 -1
- data/lib/rack/contrib/proctitle.rb +2 -0
- data/lib/rack/contrib/profiler.rb +17 -14
- data/lib/rack/contrib/relative_redirect.rb +11 -5
- data/lib/rack/contrib/response_cache.rb +13 -11
- data/lib/rack/contrib/response_headers.rb +8 -2
- data/lib/rack/contrib/route_exceptions.rb +2 -0
- data/lib/rack/contrib/runtime.rb +3 -30
- data/lib/rack/contrib/signals.rb +6 -0
- data/lib/rack/contrib/simple_endpoint.rb +3 -1
- data/lib/rack/contrib/static_cache.rb +13 -4
- data/lib/rack/contrib/time_zone.rb +2 -0
- data/lib/rack/contrib/try_static.rb +2 -0
- data/lib/rack/contrib/version.rb +5 -0
- data/lib/rack/contrib.rb +2 -1
- metadata +10 -229
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0328244bd18e70963a841937904e3bf27eebfef5de80a4d3e1e09328e4c93ff8'
|
4
|
+
data.tar.gz: 2ab61dc5347ad251d9654e3b94f705358081b8dd932b62a07cc95f42d611c3b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a877560e6bfb9df7779a324fcbd803294fe9939563cd1360f27a93f377f567d35bb3ca28e9935e5f7d5babbb221082ba13abb4edf1819cc9e204f229840f41f
|
7
|
+
data.tar.gz: f657d4a1f6846c3e426dea6f5ef3dcb62369c23ae36f4eaa1abe689b6210327fa78aa102058399397f1e1731d60f2070915479578026fa78c8e98470f1b5f843
|
data/README.md
CHANGED
@@ -8,7 +8,6 @@ interface:
|
|
8
8
|
* `Rack::BounceFavicon` - Returns a 404 for requests to `/favicon.ico`
|
9
9
|
* `Rack::CSSHTTPRequest` - Adds CSSHTTPRequest support by encoding responses as CSS for cross-site AJAX-style data loading
|
10
10
|
* `Rack::Callbacks` - Implements DSL for pure before/after filter like Middlewares.
|
11
|
-
* `Rack::Config` - Shared configuration for cooperative middleware.
|
12
11
|
* `Rack::Cookies` - Adds simple cookie jar hash to env
|
13
12
|
* `Rack::Deflect` - Helps protect against DoS attacks.
|
14
13
|
* `Rack::Evil` - Lets the rack application return a response to the client from any place.
|
data/lib/rack/contrib/access.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "ipaddr"
|
2
4
|
|
3
5
|
module Rack
|
@@ -38,7 +40,7 @@ module Rack
|
|
38
40
|
raise ArgumentError, "paths need to start with /"
|
39
41
|
end
|
40
42
|
location = location.chomp('/')
|
41
|
-
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)",
|
43
|
+
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
|
42
44
|
|
43
45
|
ipmasks.collect! do |ipmask|
|
44
46
|
ipmask.is_a?(IPAddr) ? ipmask : IPAddr.new(ipmask)
|
@@ -48,9 +50,9 @@ module Rack
|
|
48
50
|
end
|
49
51
|
|
50
52
|
def call(env)
|
51
|
-
|
53
|
+
request = Request.new(env)
|
52
54
|
ipmasks = ipmasks_for_path(env)
|
53
|
-
return forbidden! unless ip_authorized?(ipmasks)
|
55
|
+
return forbidden! unless ip_authorized?(request, ipmasks)
|
54
56
|
status, headers, body = @app.call(env)
|
55
57
|
[status, headers, body]
|
56
58
|
end
|
@@ -70,14 +72,14 @@ module Rack
|
|
70
72
|
end
|
71
73
|
|
72
74
|
def forbidden!
|
73
|
-
[403, { '
|
75
|
+
[403, { 'content-type' => 'text/html', 'content-length' => '0' }, []]
|
74
76
|
end
|
75
77
|
|
76
|
-
def ip_authorized?(ipmasks)
|
78
|
+
def ip_authorized?(request, ipmasks)
|
77
79
|
return true if ipmasks.nil?
|
78
80
|
|
79
81
|
ipmasks.any? do |ip_mask|
|
80
|
-
ip_mask.include?(IPAddr.new(
|
82
|
+
ip_mask.include?(IPAddr.new(request.ip))
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class Backstage
|
3
5
|
File = ::File
|
@@ -8,10 +10,10 @@ module Rack
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def call(env)
|
11
|
-
if File.
|
13
|
+
if File.exist?(@file)
|
12
14
|
content = File.read(@file)
|
13
|
-
length =
|
14
|
-
[503, {'
|
15
|
+
length = content.bytesize.to_s
|
16
|
+
[503, {'content-type' => 'text/html', 'content-length' => length}, [content]]
|
15
17
|
else
|
16
18
|
@app.call(env)
|
17
19
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Bounce those annoying favicon.ico requests
|
3
5
|
class BounceFavicon
|
@@ -7,7 +9,7 @@ module Rack
|
|
7
9
|
|
8
10
|
def call(env)
|
9
11
|
if env["PATH_INFO"] == "/favicon.ico"
|
10
|
-
[404, {"
|
12
|
+
[404, {"content-type" => "text/html", "content-length" => "0"}, []]
|
11
13
|
else
|
12
14
|
@app.call(env)
|
13
15
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Rack middleware to use common cookies across domain and subdomains.
|
3
5
|
class CommonCookies
|
@@ -5,26 +7,32 @@ module Rack
|
|
5
7
|
LOCALHOST_OR_IP_REGEXP = /^([\d.]+|localhost)$/
|
6
8
|
PORT = /:\d+$/
|
7
9
|
|
10
|
+
HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
|
11
|
+
private_constant :HEADERS_KLASS
|
12
|
+
|
8
13
|
def initialize(app)
|
9
14
|
@app = app
|
10
15
|
end
|
11
16
|
|
12
17
|
def call(env)
|
13
|
-
@app.call(env)
|
14
|
-
|
15
|
-
|
16
|
-
|
18
|
+
status, headers, body = @app.call(env)
|
19
|
+
headers = HEADERS_KLASS.new.merge(headers)
|
20
|
+
|
21
|
+
host = env['HTTP_HOST'].sub PORT, ''
|
22
|
+
share_cookie(headers, host)
|
23
|
+
|
24
|
+
[status, headers, body]
|
17
25
|
end
|
18
26
|
|
19
27
|
private
|
20
28
|
|
21
|
-
def domain
|
22
|
-
|
29
|
+
def domain(host)
|
30
|
+
host =~ DOMAIN_REGEXP
|
23
31
|
".#{$1}.#{$2}"
|
24
32
|
end
|
25
33
|
|
26
|
-
def share_cookie(headers)
|
27
|
-
headers['Set-Cookie'] &&= common_cookie(headers) if
|
34
|
+
def share_cookie(headers, host)
|
35
|
+
headers['Set-Cookie'] &&= common_cookie(headers, host) if host !~ LOCALHOST_OR_IP_REGEXP
|
28
36
|
end
|
29
37
|
|
30
38
|
def cookie(headers)
|
@@ -32,8 +40,8 @@ module Rack
|
|
32
40
|
cookies.is_a?(Array) ? cookies.join("\n") : cookies
|
33
41
|
end
|
34
42
|
|
35
|
-
def common_cookie(headers)
|
36
|
-
cookie(headers).gsub(/; domain=[^;]*/, '').gsub(/$/, "; domain=#{domain}")
|
43
|
+
def common_cookie(headers, host)
|
44
|
+
cookie(headers).gsub(/; domain=[^;]*/, '').gsub(/$/, "; domain=#{domain(host)}")
|
37
45
|
end
|
38
46
|
end
|
39
|
-
end
|
47
|
+
end
|
data/lib/rack/contrib/config.rb
CHANGED
@@ -1,16 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class Config
|
6
|
-
def initialize(app, &block)
|
7
|
-
@app = app
|
8
|
-
@block = block
|
9
|
-
end
|
10
|
-
|
11
|
-
def call(env)
|
12
|
-
@block.call(env)
|
13
|
-
@app.call(env)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
3
|
+
require 'rack'
|
4
|
+
require 'rack/config'
|
data/lib/rack/contrib/cookies.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'csshttprequest'
|
2
4
|
|
3
5
|
module Rack
|
4
6
|
|
5
7
|
# A Rack middleware for providing CSSHTTPRequest responses.
|
6
8
|
class CSSHTTPRequest
|
9
|
+
HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
|
10
|
+
private_constant :HEADERS_KLASS
|
7
11
|
|
8
12
|
def initialize(app)
|
9
13
|
@app = app
|
@@ -13,9 +17,12 @@ module Rack
|
|
13
17
|
# the CSSHTTPRequest encoder
|
14
18
|
def call(env)
|
15
19
|
status, headers, response = @app.call(env)
|
20
|
+
headers = HEADERS_KLASS.new.merge(headers)
|
21
|
+
|
16
22
|
if chr_request?(env)
|
17
|
-
|
18
|
-
modify_headers!(headers,
|
23
|
+
encoded_response = encode(response)
|
24
|
+
modify_headers!(headers, encoded_response)
|
25
|
+
response = [encoded_response]
|
19
26
|
end
|
20
27
|
[status, headers, response]
|
21
28
|
end
|
@@ -25,9 +32,8 @@ module Rack
|
|
25
32
|
!(/\.chr$/.match(env['PATH_INFO'])).nil? || Rack::Request.new(env).params['_format'] == 'chr'
|
26
33
|
end
|
27
34
|
|
28
|
-
def encode(
|
29
|
-
|
30
|
-
return ::CSSHTTPRequest.encode(assembled_body)
|
35
|
+
def encode(body)
|
36
|
+
::CSSHTTPRequest.encode(body.to_enum.to_a.join)
|
31
37
|
end
|
32
38
|
|
33
39
|
def modify_headers!(headers, encoded_response)
|
data/lib/rack/contrib/deflect.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thread'
|
2
4
|
|
3
5
|
# TODO: optional stats
|
@@ -63,14 +65,14 @@ module Rack
|
|
63
65
|
end
|
64
66
|
|
65
67
|
def deflect!
|
66
|
-
[403, { '
|
68
|
+
[403, { 'content-type' => 'text/html', 'content-length' => '0' }, []]
|
67
69
|
end
|
68
70
|
|
69
71
|
def deflect? env
|
70
|
-
|
71
|
-
return false if options[:whitelist].include?
|
72
|
-
return true if options[:blacklist].include?
|
73
|
-
sync { watch }
|
72
|
+
remote_addr = env['REMOTE_ADDR']
|
73
|
+
return false if options[:whitelist].include? remote_addr
|
74
|
+
return true if options[:blacklist].include? remote_addr
|
75
|
+
sync { watch(remote_addr) }
|
74
76
|
end
|
75
77
|
|
76
78
|
def log message
|
@@ -82,55 +84,55 @@ module Rack
|
|
82
84
|
@mutex.synchronize(&block)
|
83
85
|
end
|
84
86
|
|
85
|
-
def map
|
86
|
-
@remote_addr_map[
|
87
|
+
def map(remote_addr)
|
88
|
+
@remote_addr_map[remote_addr] ||= {
|
87
89
|
:expires => Time.now + options[:interval],
|
88
90
|
:requests => 0
|
89
91
|
}
|
90
92
|
end
|
91
93
|
|
92
|
-
def watch
|
93
|
-
increment_requests
|
94
|
-
clear! if watch_expired? and not blocked?
|
95
|
-
clear! if blocked? and block_expired?
|
96
|
-
block! if watching? and exceeded_request_threshold?
|
97
|
-
blocked?
|
94
|
+
def watch(remote_addr)
|
95
|
+
increment_requests(remote_addr)
|
96
|
+
clear!(remote_addr) if watch_expired?(remote_addr) and not blocked?(remote_addr)
|
97
|
+
clear!(remote_addr) if blocked?(remote_addr) and block_expired?(remote_addr)
|
98
|
+
block!(remote_addr) if watching?(remote_addr) and exceeded_request_threshold?(remote_addr)
|
99
|
+
blocked?(remote_addr)
|
98
100
|
end
|
99
101
|
|
100
|
-
def block!
|
101
|
-
return if blocked?
|
102
|
-
log "blocked #{
|
103
|
-
map[:block_expires] = Time.now + options[:block_duration]
|
102
|
+
def block!(remote_addr)
|
103
|
+
return if blocked?(remote_addr)
|
104
|
+
log "blocked #{remote_addr}"
|
105
|
+
map(remote_addr)[:block_expires] = Time.now + options[:block_duration]
|
104
106
|
end
|
105
107
|
|
106
|
-
def blocked?
|
107
|
-
map.has_key? :block_expires
|
108
|
+
def blocked?(remote_addr)
|
109
|
+
map(remote_addr).has_key? :block_expires
|
108
110
|
end
|
109
111
|
|
110
|
-
def block_expired?
|
111
|
-
map[:block_expires] < Time.now rescue false
|
112
|
+
def block_expired?(remote_addr)
|
113
|
+
map(remote_addr)[:block_expires] < Time.now rescue false
|
112
114
|
end
|
113
115
|
|
114
|
-
def watching?
|
115
|
-
@remote_addr_map.has_key?
|
116
|
+
def watching?(remote_addr)
|
117
|
+
@remote_addr_map.has_key? remote_addr
|
116
118
|
end
|
117
119
|
|
118
|
-
def clear!
|
119
|
-
return unless watching?
|
120
|
-
log "released #{
|
121
|
-
@remote_addr_map.delete
|
120
|
+
def clear!(remote_addr)
|
121
|
+
return unless watching?(remote_addr)
|
122
|
+
log "released #{remote_addr}" if blocked?(remote_addr)
|
123
|
+
@remote_addr_map.delete remote_addr
|
122
124
|
end
|
123
125
|
|
124
|
-
def increment_requests
|
125
|
-
map[:requests] += 1
|
126
|
+
def increment_requests(remote_addr)
|
127
|
+
map(remote_addr)[:requests] += 1
|
126
128
|
end
|
127
129
|
|
128
|
-
def exceeded_request_threshold?
|
129
|
-
map[:requests] > options[:request_threshold]
|
130
|
+
def exceeded_request_threshold?(remote_addr)
|
131
|
+
map(remote_addr)[:requests] > options[:request_threshold]
|
130
132
|
end
|
131
133
|
|
132
|
-
def watch_expired?
|
133
|
-
map[:expires] <= Time.now
|
134
|
+
def watch_expired?(remote_addr)
|
135
|
+
map(remote_addr)[:expires] <= Time.now
|
134
136
|
end
|
135
137
|
|
136
138
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Ensure that the path and query string presented to the application
|
3
5
|
# contains only valid characters. If the validation fails, then a
|
@@ -16,7 +18,7 @@ module Rack
|
|
16
18
|
Rack::Utils.unescape(full_path).valid_encoding?
|
17
19
|
@app.call env
|
18
20
|
else
|
19
|
-
[400, {'
|
21
|
+
[400, {'content-type'=>'text/plain'}, ['Bad Request']]
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
data/lib/rack/contrib/evil.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class ExpectationCascade
|
3
|
-
Expect = "
|
5
|
+
Expect = "HTTP_EXPECT".freeze
|
4
6
|
ContinueExpectation = "100-continue".freeze
|
5
7
|
|
6
|
-
ExpectationFailed = [417, {"
|
7
|
-
NotFound = [404, {"
|
8
|
+
ExpectationFailed = [417, {"content-type" => "text/html"}, []]
|
9
|
+
NotFound = [404, {"content-type" => "text/html"}, []]
|
8
10
|
|
9
11
|
attr_reader :apps
|
10
12
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
|
3
5
|
# Rack middleware implementing the IETF draft: "Host Metadata for the Web"
|
@@ -28,7 +30,7 @@ module Rack
|
|
28
30
|
|
29
31
|
def call(env)
|
30
32
|
if env['PATH_INFO'] == '/host-meta'
|
31
|
-
[200, {'
|
33
|
+
[200, {'content-type' => 'application/host-meta'}, [@response]]
|
32
34
|
else
|
33
35
|
@app.call(env)
|
34
36
|
end
|
@@ -14,10 +14,10 @@ module Rack
|
|
14
14
|
# === Parse POST and GET requests only
|
15
15
|
# use Rack::JSONBodyParser, verbs: ['POST', 'GET']
|
16
16
|
#
|
17
|
-
# === Parse POST|PATCH|PUT requests whose
|
17
|
+
# === Parse POST|PATCH|PUT requests whose content-type matches 'json'
|
18
18
|
# use Rack::JSONBodyParser, media: /json/
|
19
19
|
#
|
20
|
-
# === Parse POST requests whose
|
20
|
+
# === Parse POST requests whose content-type is 'application/json' or 'application/vnd+json'
|
21
21
|
# use Rack::JSONBodyParser, verbs: ['POST'], media: ['application/json', 'application/vnd.api+json']
|
22
22
|
#
|
23
23
|
class JSONBodyParser
|
@@ -55,27 +55,38 @@ module Rack
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def call(env)
|
58
|
-
|
59
|
-
|
58
|
+
begin
|
59
|
+
if @verbs.include?(env[Rack::REQUEST_METHOD]) &&
|
60
|
+
@matcher.call(@media, env['CONTENT_TYPE'])
|
60
61
|
|
61
|
-
|
62
|
+
update_form_hash_with_json_body(env)
|
63
|
+
end
|
64
|
+
rescue ParserError
|
65
|
+
body = { error: 'Failed to parse body as JSON' }.to_json
|
66
|
+
header = { 'content-type' => 'application/json' }
|
67
|
+
return Rack::Response.new(body, 400, header).finish
|
62
68
|
end
|
63
69
|
@app.call(env)
|
64
|
-
rescue JSON::ParserError
|
65
|
-
body = { error: 'Failed to parse body as JSON' }.to_json
|
66
|
-
header = { 'Content-Type' => 'application/json' }
|
67
|
-
Rack::Response.new(body, 400, header).finish
|
68
70
|
end
|
69
71
|
|
70
72
|
private
|
71
73
|
|
74
|
+
class ParserError < StandardError; end
|
75
|
+
|
72
76
|
def update_form_hash_with_json_body(env)
|
73
77
|
body = env[Rack::RACK_INPUT]
|
74
78
|
return unless (body_content = body.read) && !body_content.empty?
|
75
79
|
|
76
|
-
body.rewind # somebody might try to read this stream
|
80
|
+
body.rewind if body.respond_to?(:rewind) # somebody might try to read this stream
|
81
|
+
|
82
|
+
begin
|
83
|
+
parsed_body = @parser.call(body_content)
|
84
|
+
rescue StandardError
|
85
|
+
raise ParserError
|
86
|
+
end
|
87
|
+
|
77
88
|
env.update(
|
78
|
-
Rack::RACK_REQUEST_FORM_HASH =>
|
89
|
+
Rack::RACK_REQUEST_FORM_HASH => parsed_body,
|
79
90
|
Rack::RACK_REQUEST_FORM_INPUT => body
|
80
91
|
)
|
81
92
|
end
|
data/lib/rack/contrib/jsonp.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
|
3
5
|
# A Rack middleware for providing JSON-P support.
|
@@ -21,6 +23,9 @@ module Rack
|
|
21
23
|
# "\342\200\251" # => "\u2029"
|
22
24
|
U2028, U2029 = ("\u2028" == 'u2028') ? ["\342\200\250", "\342\200\251"] : ["\u2028", "\u2029"]
|
23
25
|
|
26
|
+
HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
|
27
|
+
private_constant :HEADERS_KLASS
|
28
|
+
|
24
29
|
def initialize(app)
|
25
30
|
@app = app
|
26
31
|
end
|
@@ -40,7 +45,7 @@ module Rack
|
|
40
45
|
return status, headers, response
|
41
46
|
end
|
42
47
|
|
43
|
-
headers =
|
48
|
+
headers = HEADERS_KLASS.new.merge(headers)
|
44
49
|
|
45
50
|
if is_json?(headers) && has_callback?(request)
|
46
51
|
callback = request.params['callback']
|
@@ -53,7 +58,7 @@ module Rack
|
|
53
58
|
|
54
59
|
# Set new Content-Length, if it was set before we mutated the response body
|
55
60
|
if headers['Content-Length']
|
56
|
-
length = response.
|
61
|
+
length = response.map(&:bytesize).reduce(0, :+)
|
57
62
|
headers['Content-Length'] = length.to_s
|
58
63
|
end
|
59
64
|
end
|
@@ -87,8 +92,8 @@ module Rack
|
|
87
92
|
# method of combining all of the data into a single string makes sense
|
88
93
|
# since JSON is returned as a full string.
|
89
94
|
#
|
90
|
-
def pad(callback, response
|
91
|
-
response.
|
95
|
+
def pad(callback, response)
|
96
|
+
body = response.to_enum.map do |s|
|
92
97
|
# U+2028 and U+2029 are allowed inside strings in JSON (as all literal
|
93
98
|
# Unicode characters) but JavaScript defines them as newline
|
94
99
|
# seperators. Because no literal newlines are allowed in a string, this
|
@@ -96,8 +101,8 @@ module Rack
|
|
96
101
|
# replacing them with the escaped version. This should be safe because
|
97
102
|
# according to the JSON spec, these characters are *only* valid inside
|
98
103
|
# a string and should therefore not be present any other places.
|
99
|
-
|
100
|
-
end
|
104
|
+
s.gsub(U2028, '\u2028').gsub(U2029, '\u2029')
|
105
|
+
end.join
|
101
106
|
|
102
107
|
# https://github.com/rack/rack-contrib/issues/46
|
103
108
|
response.close if response.respond_to?(:close)
|
@@ -106,7 +111,7 @@ module Rack
|
|
106
111
|
end
|
107
112
|
|
108
113
|
def bad_request(body = "Bad Request")
|
109
|
-
[ 400, { '
|
114
|
+
[ 400, { 'content-type' => 'text/plain', 'content-length' => body.bytesize.to_s }, [body] ]
|
110
115
|
end
|
111
116
|
|
112
117
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
|
3
5
|
##
|
@@ -43,6 +45,10 @@ module Rack
|
|
43
45
|
# know for sure that it does not modify the cached content, you can set the
|
44
46
|
# `Rack-Lazy-Conditional-Get` on response to `skip`. This will not update the
|
45
47
|
# global modification date.
|
48
|
+
#
|
49
|
+
# NOTE: This will not work properly in a multi-threaded environment with
|
50
|
+
# default cache object. A provided cache object should ensure thread-safety
|
51
|
+
# of the `get`/`set`/`[]`/`[]=` methods.
|
46
52
|
|
47
53
|
class LazyConditionalGet
|
48
54
|
|
@@ -68,11 +74,14 @@ module Rack
|
|
68
74
|
|
69
75
|
def call env
|
70
76
|
if reading? env and fresh? env
|
71
|
-
return [304, {'
|
77
|
+
return [304, {'last-modified' => env['HTTP_IF_MODIFIED_SINCE']}, []]
|
72
78
|
end
|
79
|
+
|
73
80
|
status, headers, body = @app.call env
|
81
|
+
headers = Rack.release < "3" ? Utils::HeaderHash.new(headers) : headers
|
82
|
+
|
74
83
|
update_cache unless (reading?(env) or skipping?(headers))
|
75
|
-
headers['
|
84
|
+
headers['last-modified'] = cached_value if stampable? headers
|
76
85
|
[status, headers, body]
|
77
86
|
end
|
78
87
|
|
@@ -87,11 +96,11 @@ module Rack
|
|
87
96
|
end
|
88
97
|
|
89
98
|
def skipping? headers
|
90
|
-
headers['
|
99
|
+
headers['rack-lazy-conditional-get'] == 'skip'
|
91
100
|
end
|
92
101
|
|
93
102
|
def stampable? headers
|
94
|
-
!headers.has_key?('
|
103
|
+
!headers.has_key?('last-modified') and headers['rack-lazy-conditional-get'] == 'yes'
|
95
104
|
end
|
96
105
|
|
97
106
|
def update_cache
|
data/lib/rack/contrib/locale.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'i18n'
|
2
4
|
|
3
5
|
module Rack
|
4
6
|
class Locale
|
7
|
+
HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
|
8
|
+
private_constant :HEADERS_KLASS
|
9
|
+
|
5
10
|
def initialize(app)
|
6
11
|
@app = app
|
7
12
|
end
|
@@ -14,6 +19,7 @@ module Rack
|
|
14
19
|
|
15
20
|
env['rack.locale'] = I18n.locale = locale.to_s
|
16
21
|
status, headers, body = @app.call(env)
|
22
|
+
headers = HEADERS_KLASS.new.merge(headers)
|
17
23
|
|
18
24
|
unless headers['Content-Language']
|
19
25
|
headers['Content-Language'] = locale.to_s
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/utils'
|
2
4
|
|
3
5
|
module Rack
|
@@ -24,7 +26,7 @@ module Rack
|
|
24
26
|
post_body = env[POST_BODY]
|
25
27
|
env[FORM_INPUT] = post_body
|
26
28
|
env[FORM_HASH] = Rack::Utils.parse_nested_query(post_body.read)
|
27
|
-
post_body.rewind
|
29
|
+
post_body.rewind
|
28
30
|
end
|
29
31
|
@app.call(env)
|
30
32
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Rack::NotFound is a default endpoint. Optionally initialize with the
|
3
5
|
# path to a custom 404 page, to override the standard response body.
|
@@ -25,7 +27,7 @@ module Rack
|
|
25
27
|
end
|
26
28
|
|
27
29
|
def call(env)
|
28
|
-
[404, {'
|
30
|
+
[404, {'content-type' => @content_type, 'content-length' => @length}, [@content]]
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|