rack-contrib 1.8.0 → 2.3.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 +5 -5
- data/README.md +17 -10
- data/lib/rack/contrib.rb +3 -3
- data/lib/rack/contrib/access.rb +6 -4
- data/lib/rack/contrib/backstage.rb +3 -1
- data/lib/rack/contrib/bounce_favicon.rb +2 -0
- data/lib/rack/contrib/callbacks.rb +2 -0
- data/lib/rack/contrib/common_cookies.rb +16 -11
- data/lib/rack/contrib/config.rb +3 -15
- data/lib/rack/contrib/cookies.rb +2 -0
- data/lib/rack/contrib/csshttprequest.rb +10 -6
- data/lib/rack/contrib/deflect.rb +34 -32
- data/lib/rack/contrib/enforce_valid_encoding.rb +2 -0
- data/lib/rack/contrib/evil.rb +2 -0
- data/lib/rack/contrib/expectation_cascade.rb +3 -1
- data/lib/rack/contrib/garbagecollector.rb +2 -0
- data/lib/rack/contrib/host_meta.rb +2 -0
- data/lib/rack/contrib/json_body_parser.rb +85 -0
- data/lib/rack/contrib/jsonp.rb +8 -6
- data/lib/rack/contrib/lazy_conditional_get.rb +9 -0
- data/lib/rack/contrib/lighttpd_script_name_fix.rb +2 -0
- data/lib/rack/contrib/locale.rb +65 -30
- data/lib/rack/contrib/mailexceptions.rb +4 -2
- data/lib/rack/contrib/nested_params.rb +6 -121
- data/lib/rack/contrib/not_found.rb +3 -1
- data/lib/rack/contrib/post_body_content_type_parser.rb +49 -4
- data/lib/rack/contrib/printout.rb +2 -0
- data/lib/rack/contrib/proctitle.rb +2 -0
- data/lib/rack/contrib/profiler.rb +6 -1
- data/lib/rack/contrib/relative_redirect.rb +10 -5
- data/lib/rack/contrib/response_cache.rb +22 -11
- data/lib/rack/contrib/response_headers.rb +2 -0
- 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 +17 -8
- data/lib/rack/contrib/time_zone.rb +2 -0
- data/lib/rack/contrib/try_static.rb +2 -0
- metadata +50 -32
- data/lib/rack/contrib/accept_format.rb +0 -66
- data/lib/rack/contrib/sendfile.rb +0 -138
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.
|
@@ -53,7 +55,7 @@ module Rack
|
|
53
55
|
|
54
56
|
# Set new Content-Length, if it was set before we mutated the response body
|
55
57
|
if headers['Content-Length']
|
56
|
-
length = response.
|
58
|
+
length = response.map(&:bytesize).reduce(0, :+)
|
57
59
|
headers['Content-Length'] = length.to_s
|
58
60
|
end
|
59
61
|
end
|
@@ -87,8 +89,8 @@ module Rack
|
|
87
89
|
# method of combining all of the data into a single string makes sense
|
88
90
|
# since JSON is returned as a full string.
|
89
91
|
#
|
90
|
-
def pad(callback, response
|
91
|
-
response.
|
92
|
+
def pad(callback, response)
|
93
|
+
body = response.to_enum.map do |s|
|
92
94
|
# U+2028 and U+2029 are allowed inside strings in JSON (as all literal
|
93
95
|
# Unicode characters) but JavaScript defines them as newline
|
94
96
|
# seperators. Because no literal newlines are allowed in a string, this
|
@@ -96,8 +98,8 @@ module Rack
|
|
96
98
|
# replacing them with the escaped version. This should be safe because
|
97
99
|
# according to the JSON spec, these characters are *only* valid inside
|
98
100
|
# a string and should therefore not be present any other places.
|
99
|
-
|
100
|
-
end
|
101
|
+
s.gsub(U2028, '\u2028').gsub(U2029, '\u2029')
|
102
|
+
end.join
|
101
103
|
|
102
104
|
# https://github.com/rack/rack-contrib/issues/46
|
103
105
|
response.close if response.respond_to?(:close)
|
@@ -106,7 +108,7 @@ module Rack
|
|
106
108
|
end
|
107
109
|
|
108
110
|
def bad_request(body = "Bad Request")
|
109
|
-
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.
|
111
|
+
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ]
|
110
112
|
end
|
111
113
|
|
112
114
|
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
|
|
@@ -70,7 +76,10 @@ module Rack
|
|
70
76
|
if reading? env and fresh? env
|
71
77
|
return [304, {'Last-Modified' => env['HTTP_IF_MODIFIED_SINCE']}, []]
|
72
78
|
end
|
79
|
+
|
73
80
|
status, headers, body = @app.call env
|
81
|
+
headers = Utils::HeaderHash.new(headers)
|
82
|
+
|
74
83
|
update_cache unless (reading?(env) or skipping?(headers))
|
75
84
|
headers['Last-Modified'] = cached_value if stampable? headers
|
76
85
|
[status, headers, body]
|
data/lib/rack/contrib/locale.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'i18n'
|
2
4
|
|
3
5
|
module Rack
|
@@ -7,43 +9,76 @@ module Rack
|
|
7
9
|
end
|
8
10
|
|
9
11
|
def call(env)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
locale_to_restore = I18n.locale
|
13
|
+
|
14
|
+
locale = user_preferred_locale(env["HTTP_ACCEPT_LANGUAGE"])
|
15
|
+
locale ||= I18n.default_locale
|
16
|
+
|
17
|
+
env['rack.locale'] = I18n.locale = locale.to_s
|
18
|
+
status, headers, body = @app.call(env)
|
19
|
+
headers = Utils::HeaderHash.new(headers)
|
20
|
+
|
21
|
+
unless headers['Content-Language']
|
22
|
+
headers['Content-Language'] = locale.to_s
|
20
23
|
end
|
24
|
+
|
25
|
+
[status, headers, body]
|
26
|
+
ensure
|
27
|
+
I18n.locale = locale_to_restore
|
21
28
|
end
|
22
29
|
|
23
30
|
private
|
24
31
|
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
32
|
+
# Accept-Language header is covered mainly by RFC 7231
|
33
|
+
# https://tools.ietf.org/html/rfc7231
|
34
|
+
#
|
35
|
+
# Related sections:
|
36
|
+
#
|
37
|
+
# * https://tools.ietf.org/html/rfc7231#section-5.3.1
|
38
|
+
# * https://tools.ietf.org/html/rfc7231#section-5.3.5
|
39
|
+
# * https://tools.ietf.org/html/rfc4647#section-3.4
|
40
|
+
#
|
41
|
+
# There is an obsolete RFC 2616 (https://tools.ietf.org/html/rfc2616)
|
42
|
+
#
|
43
|
+
# Edge cases:
|
44
|
+
#
|
45
|
+
# * Value can be a comma separated list with optional whitespaces:
|
46
|
+
# Accept-Language: da, en-gb;q=0.8, en;q=0.7
|
47
|
+
#
|
48
|
+
# * Quality value can contain optional whitespaces as well:
|
49
|
+
# Accept-Language: ru-UA, ru; q=0.8, uk; q=0.6, en-US; q=0.4, en; q=0.2
|
50
|
+
#
|
51
|
+
# * Quality prefix 'q=' can be in upper case (Q=)
|
52
|
+
#
|
53
|
+
# * Ignore case when match locale with I18n available locales
|
54
|
+
#
|
55
|
+
def user_preferred_locale(header)
|
56
|
+
return if header.nil?
|
57
|
+
|
58
|
+
locales = header.gsub(/\s+/, '').split(",").map do |language_tag|
|
59
|
+
locale, quality = language_tag.split(/;q=/i)
|
60
|
+
quality = quality ? quality.to_f : 1.0
|
61
|
+
[locale, quality]
|
62
|
+
end.reject do |(locale, quality)|
|
63
|
+
locale == '*' || quality == 0
|
64
|
+
end.sort_by do |(_, quality)|
|
65
|
+
quality
|
66
|
+
end.map(&:first)
|
67
|
+
|
68
|
+
return if locales.empty?
|
69
|
+
|
70
|
+
if I18n.enforce_available_locales
|
71
|
+
locale = locales.reverse.find { |locale| I18n.available_locales.any? { |al| match?(al, locale) } }
|
72
|
+
if locale
|
73
|
+
I18n.available_locales.find { |al| match?(al, locale) }
|
42
74
|
end
|
43
|
-
|
75
|
+
else
|
76
|
+
locales.last
|
77
|
+
end
|
78
|
+
end
|
44
79
|
|
45
|
-
|
46
|
-
|
80
|
+
def match?(s1, s2)
|
81
|
+
s1.to_s.casecmp(s2.to_s) == 0
|
47
82
|
end
|
48
83
|
end
|
49
84
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'net/smtp'
|
2
4
|
require 'mail'
|
3
5
|
require 'erb'
|
@@ -90,7 +92,7 @@ module Rack
|
|
90
92
|
mail.delivery_method :test
|
91
93
|
elsif config[:smtp]
|
92
94
|
smtp = config[:smtp]
|
93
|
-
# for backward
|
95
|
+
# for backward compatibility, replace the :server key with :address
|
94
96
|
address = smtp.delete :server
|
95
97
|
smtp[:address] = address if address
|
96
98
|
mail.delivery_method :smtp, smtp
|
@@ -104,7 +106,7 @@ module Rack
|
|
104
106
|
|
105
107
|
def extract_body(env)
|
106
108
|
if io = env['rack.input']
|
107
|
-
io.rewind
|
109
|
+
io.rewind
|
108
110
|
io.read
|
109
111
|
end
|
110
112
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack/utils'
|
3
4
|
|
4
5
|
module Rack
|
5
6
|
# Rack middleware for parsing POST/PUT body data into nested parameters
|
@@ -20,130 +21,14 @@ module Rack
|
|
20
21
|
|
21
22
|
def call(env)
|
22
23
|
if form_vars = env[FORM_VARS]
|
23
|
-
|
24
|
+
Rack::Utils.parse_nested_query(form_vars)
|
24
25
|
elsif env[CONTENT_TYPE] == URL_ENCODED
|
25
26
|
post_body = env[POST_BODY]
|
26
27
|
env[FORM_INPUT] = post_body
|
27
|
-
env[FORM_HASH] =
|
28
|
-
post_body.rewind
|
28
|
+
env[FORM_HASH] = Rack::Utils.parse_nested_query(post_body.read)
|
29
|
+
post_body.rewind
|
29
30
|
end
|
30
31
|
@app.call(env)
|
31
32
|
end
|
32
|
-
|
33
|
-
## the rest is nabbed from Rails ##
|
34
|
-
|
35
|
-
def parse_query_parameters(query_string)
|
36
|
-
return {} if query_string.nil? or query_string.empty?
|
37
|
-
|
38
|
-
pairs = query_string.split('&').collect do |chunk|
|
39
|
-
next if chunk.empty?
|
40
|
-
key, value = chunk.split('=', 2)
|
41
|
-
next if key.empty?
|
42
|
-
value = value.nil? ? nil : CGI.unescape(value)
|
43
|
-
[ CGI.unescape(key), value ]
|
44
|
-
end.compact
|
45
|
-
|
46
|
-
UrlEncodedPairParser.new(pairs).result
|
47
|
-
end
|
48
|
-
|
49
|
-
class UrlEncodedPairParser < StringScanner
|
50
|
-
attr_reader :top, :parent, :result
|
51
|
-
|
52
|
-
def initialize(pairs = [])
|
53
|
-
super('')
|
54
|
-
@result = {}
|
55
|
-
pairs.each { |key, value| parse(key, value) }
|
56
|
-
end
|
57
|
-
|
58
|
-
KEY_REGEXP = %r{([^\[\]=&]+)}
|
59
|
-
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
|
60
|
-
|
61
|
-
# Parse the query string
|
62
|
-
def parse(key, value)
|
63
|
-
self.string = key
|
64
|
-
@top, @parent = result, nil
|
65
|
-
|
66
|
-
# First scan the bare key
|
67
|
-
key = scan(KEY_REGEXP) or return
|
68
|
-
key = post_key_check(key)
|
69
|
-
|
70
|
-
# Then scan as many nestings as present
|
71
|
-
until eos?
|
72
|
-
r = scan(BRACKETED_KEY_REGEXP) or return
|
73
|
-
key = self[1]
|
74
|
-
key = post_key_check(key)
|
75
|
-
end
|
76
|
-
|
77
|
-
bind(key, value)
|
78
|
-
end
|
79
|
-
|
80
|
-
private
|
81
|
-
# After we see a key, we must look ahead to determine our next action. Cases:
|
82
|
-
#
|
83
|
-
# [] follows the key. Then the value must be an array.
|
84
|
-
# = follows the key. (A value comes next)
|
85
|
-
# & or the end of string follows the key. Then the key is a flag.
|
86
|
-
# otherwise, a hash follows the key.
|
87
|
-
def post_key_check(key)
|
88
|
-
if scan(/\[\]/) # a[b][] indicates that b is an array
|
89
|
-
container(key, Array)
|
90
|
-
nil
|
91
|
-
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
|
92
|
-
container(key, Hash)
|
93
|
-
nil
|
94
|
-
else # End of key? We do nothing.
|
95
|
-
key
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Add a container to the stack.
|
100
|
-
def container(key, klass)
|
101
|
-
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
|
102
|
-
value = bind(key, klass.new)
|
103
|
-
type_conflict! klass, value unless value.is_a?(klass)
|
104
|
-
push(value)
|
105
|
-
end
|
106
|
-
|
107
|
-
# Push a value onto the 'stack', which is actually only the top 2 items.
|
108
|
-
def push(value)
|
109
|
-
@parent, @top = @top, value
|
110
|
-
end
|
111
|
-
|
112
|
-
# Bind a key (which may be nil for items in an array) to the provided value.
|
113
|
-
def bind(key, value)
|
114
|
-
if top.is_a? Array
|
115
|
-
if key
|
116
|
-
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
|
117
|
-
top[-1][key] = value
|
118
|
-
else
|
119
|
-
top << {key => value}
|
120
|
-
end
|
121
|
-
push top.last
|
122
|
-
return top[key]
|
123
|
-
else
|
124
|
-
top << value
|
125
|
-
return value
|
126
|
-
end
|
127
|
-
elsif top.is_a? Hash
|
128
|
-
key = CGI.unescape(key)
|
129
|
-
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
|
130
|
-
|
131
|
-
if top.include?(key) && value.is_a?(String)
|
132
|
-
warn "Rack::NestedParams' parsing behavior will change in rack-contrib 2.0"
|
133
|
-
warn "Currently 'foo=1&foo=2' is parsed to {'foo' => '1'}, in 2.0 it will be parsed to {'foo' => '2'}"
|
134
|
-
end
|
135
|
-
|
136
|
-
top[key] ||= value
|
137
|
-
return top[key]
|
138
|
-
else
|
139
|
-
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def type_conflict!(klass, value)
|
144
|
-
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
33
|
end
|
149
34
|
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.
|
@@ -19,7 +21,7 @@ module Rack
|
|
19
21
|
else
|
20
22
|
@content = F.read(path)
|
21
23
|
end
|
22
|
-
@length = @content.
|
24
|
+
@length = @content.bytesize.to_s
|
23
25
|
|
24
26
|
@content_type = content_type
|
25
27
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'json'
|
3
5
|
rescue LoadError => e
|
@@ -6,10 +8,51 @@ end
|
|
6
8
|
|
7
9
|
module Rack
|
8
10
|
|
11
|
+
# <b>DEPRECATED:</b> <tt>JSONBodyParser</tt> is a drop-in replacement that is faster and more configurable.
|
12
|
+
#
|
9
13
|
# A Rack middleware for parsing POST/PUT body data when Content-Type is
|
10
14
|
# not one of the standard supported types, like <tt>application/json</tt>.
|
11
15
|
#
|
12
|
-
#
|
16
|
+
# === How to use the middleware
|
17
|
+
#
|
18
|
+
# Example of simple +config.ru+ file:
|
19
|
+
#
|
20
|
+
# require 'rack'
|
21
|
+
# require 'rack/contrib'
|
22
|
+
#
|
23
|
+
# use ::Rack::PostBodyContentTypeParser
|
24
|
+
#
|
25
|
+
# app = lambda do |env|
|
26
|
+
# request = Rack::Request.new(env)
|
27
|
+
# body = "Hello #{request.params['name']}"
|
28
|
+
# [200, {'Content-Type' => 'text/plain'}, [body]]
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# run app
|
32
|
+
#
|
33
|
+
# Example with passing block argument:
|
34
|
+
#
|
35
|
+
# use ::Rack::PostBodyContentTypeParser do |body|
|
36
|
+
# { 'params' => JSON.parse(body) }
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Example with passing proc argument:
|
40
|
+
#
|
41
|
+
# parser = ->(body) { { 'params' => JSON.parse(body) } }
|
42
|
+
# use ::Rack::PostBodyContentTypeParser, &parser
|
43
|
+
#
|
44
|
+
#
|
45
|
+
# === Failed JSON parsing
|
46
|
+
#
|
47
|
+
# Returns "400 Bad request" response if invalid JSON document was sent:
|
48
|
+
#
|
49
|
+
# Raw HTTP response:
|
50
|
+
#
|
51
|
+
# HTTP/1.1 400 Bad Request
|
52
|
+
# Content-Type: text/plain
|
53
|
+
# Content-Length: 28
|
54
|
+
#
|
55
|
+
# failed to parse body as JSON
|
13
56
|
#
|
14
57
|
class PostBodyContentTypeParser
|
15
58
|
|
@@ -24,14 +67,16 @@ module Rack
|
|
24
67
|
#
|
25
68
|
APPLICATION_JSON = 'application/json'.freeze
|
26
69
|
|
27
|
-
def initialize(app)
|
70
|
+
def initialize(app, &block)
|
71
|
+
warn "[DEPRECATION] `PostBodyContentTypeParser` is deprecated. Use `JSONBodyParser` as a drop-in replacement."
|
28
72
|
@app = app
|
73
|
+
@block = block || Proc.new { |body| JSON.parse(body, :create_additions => false) }
|
29
74
|
end
|
30
75
|
|
31
76
|
def call(env)
|
32
77
|
if Rack::Request.new(env).media_type == APPLICATION_JSON && (body = env[POST_BODY].read).length != 0
|
33
78
|
env[POST_BODY].rewind # somebody might try to read this stream
|
34
|
-
env.update(FORM_HASH =>
|
79
|
+
env.update(FORM_HASH => @block.call(body), FORM_INPUT => env[POST_BODY])
|
35
80
|
end
|
36
81
|
@app.call(env)
|
37
82
|
rescue JSON::ParserError
|
@@ -39,7 +84,7 @@ module Rack
|
|
39
84
|
end
|
40
85
|
|
41
86
|
def bad_request(body = 'Bad Request')
|
42
|
-
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.
|
87
|
+
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ]
|
43
88
|
end
|
44
89
|
end
|
45
90
|
end
|