rack-contrib 2.2.0 → 2.4.0
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.
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
|