rack-contrib 2.1.0 → 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-contrib might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/lib/rack/contrib.rb +1 -0
- data/lib/rack/contrib/csshttprequest.rb +1 -1
- data/lib/rack/contrib/json_body_parser.rb +83 -0
- data/lib/rack/contrib/jsonp.rb +1 -1
- data/lib/rack/contrib/locale.rb +62 -30
- data/lib/rack/contrib/post_body_content_type_parser.rb +44 -2
- data/lib/rack/contrib/response_cache.rb +14 -4
- data/lib/rack/contrib/static_cache.rb +5 -3
- metadata +26 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca58db2b7968ad4691e8bec79eea2088dabc3da5d309358f7df76d668324d824
|
4
|
+
data.tar.gz: 51816bb6b59a598fea16eb100cc783b3c0ff58eb37aa3585bb8d65141f2aae4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7442b5ad170ae2946f017d8ccd007d02455b7939fe42239378505b61983cd9a1f090549e87b07e08323a7cf99d04f026e8df0f326e34be166508187cca3735e7
|
7
|
+
data.tar.gz: 99985100672c65c9ee559a37281f5dddea4fcdc8481ddffd75c2f6e697197a1070339c5b70733d736f8ba33078d5f699f00097cd6ec96266c8f07edb008b727b
|
data/README.md
CHANGED
@@ -13,6 +13,7 @@ interface:
|
|
13
13
|
* `Rack::Deflect` - Helps protect against DoS attacks.
|
14
14
|
* `Rack::Evil` - Lets the rack application return a response to the client from any place.
|
15
15
|
* `Rack::HostMeta` - Configures `/host-meta` using a block
|
16
|
+
* `Rack::JSONBodyParser` - Adds JSON request bodies to the Rack parameters hash.
|
16
17
|
* `Rack::JSONP` - Adds JSON-P support by stripping out the callback param and padding the response with the appropriate callback format.
|
17
18
|
* `Rack::LazyConditionalGet` - Caches a global `Last-Modified` date and updates it each time there is a request that is not `GET` or `HEAD`.
|
18
19
|
* `Rack::LighttpdScriptNameFix` - Fixes how lighttpd sets the `SCRIPT_NAME` and `PATH_INFO` variables in certain configurations.
|
@@ -20,7 +21,7 @@ interface:
|
|
20
21
|
* `Rack::MailExceptions` - Rescues exceptions raised from the app and sends a useful email with the exception, stacktrace, and contents of the environment.
|
21
22
|
* `Rack::NestedParams` - parses form params with subscripts (e.g., * "`post[title]=Hello`") into a nested/recursive Hash structure (based on Rails' implementation).
|
22
23
|
* `Rack::NotFound` - A default 404 application.
|
23
|
-
* `Rack::PostBodyContentTypeParser` - Adds support for JSON request bodies. The Rack parameter hash is populated by deserializing the JSON data provided in the request body when the Content-Type is application/json
|
24
|
+
* `Rack::PostBodyContentTypeParser` - [Deprecated]: Adds support for JSON request bodies. The Rack parameter hash is populated by deserializing the JSON data provided in the request body when the Content-Type is application/json
|
24
25
|
* `Rack::Printout` - Prints the environment and the response per request
|
25
26
|
* `Rack::ProcTitle` - Displays request information in process title (`$0`) for monitoring/inspection with ps(1).
|
26
27
|
* `Rack::Profiler` - Uses ruby-prof to measure request time.
|
data/lib/rack/contrib.rb
CHANGED
@@ -24,6 +24,7 @@ module Rack
|
|
24
24
|
autoload :HostMeta, "rack/contrib/host_meta"
|
25
25
|
autoload :GarbageCollector, "rack/contrib/garbagecollector"
|
26
26
|
autoload :JSONP, "rack/contrib/jsonp"
|
27
|
+
autoload :JSONBodyParser, "rack/contrib/json_body_parser"
|
27
28
|
autoload :LazyConditionalGet, "rack/contrib/lazy_conditional_get"
|
28
29
|
autoload :LighttpdScriptNameFix, "rack/contrib/lighttpd_script_name_fix"
|
29
30
|
autoload :Locale, "rack/contrib/locale"
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# A Rack middleware that makes JSON-encoded request bodies available in the
|
7
|
+
# request.params hash. By default it parses POST, PATCH, and PUT requests
|
8
|
+
# whose media type is <tt>application/json</tt>. You can configure it to match
|
9
|
+
# any verb or media type via the <tt>:verbs</tt> and <tt>:media</tt> options.
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# == Examples:
|
13
|
+
#
|
14
|
+
# === Parse POST and GET requests only
|
15
|
+
# use Rack::JSONBodyParser, verbs: ['POST', 'GET']
|
16
|
+
#
|
17
|
+
# === Parse POST|PATCH|PUT requests whose Content-Type matches 'json'
|
18
|
+
# use Rack::JSONBodyParser, media: /json/
|
19
|
+
#
|
20
|
+
# === Parse POST requests whose Content-Type is 'application/json' or 'application/vnd+json'
|
21
|
+
# use Rack::JSONBodyParser, verbs: ['POST'], media: ['application/json', 'application/vnd.api+json']
|
22
|
+
#
|
23
|
+
class JSONBodyParser
|
24
|
+
CONTENT_TYPE_MATCHERS = {
|
25
|
+
String => lambda { |option, header|
|
26
|
+
Rack::MediaType.type(header) == option
|
27
|
+
},
|
28
|
+
Array => lambda { |options, header|
|
29
|
+
media_type = Rack::MediaType.type(header)
|
30
|
+
options.any? { |opt| media_type == opt }
|
31
|
+
},
|
32
|
+
Regexp => lambda {
|
33
|
+
if //.respond_to?(:match?)
|
34
|
+
# Use Ruby's fast regex matcher when available
|
35
|
+
->(option, header) { option.match? header }
|
36
|
+
else
|
37
|
+
# Fall back to the slower matcher for rubies older than 2.4
|
38
|
+
->(option, header) { option.match header }
|
39
|
+
end
|
40
|
+
}.call(),
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
DEFAULT_PARSER = ->(body) { JSON.parse(body, create_additions: false) }
|
44
|
+
|
45
|
+
def initialize(
|
46
|
+
app,
|
47
|
+
verbs: %w[POST PATCH PUT],
|
48
|
+
media: 'application/json',
|
49
|
+
&block
|
50
|
+
)
|
51
|
+
@app = app
|
52
|
+
@verbs, @media = verbs, media
|
53
|
+
@matcher = CONTENT_TYPE_MATCHERS.fetch(@media.class)
|
54
|
+
@parser = block || DEFAULT_PARSER
|
55
|
+
end
|
56
|
+
|
57
|
+
def call(env)
|
58
|
+
if @verbs.include?(env[Rack::REQUEST_METHOD]) &&
|
59
|
+
@matcher.call(@media, env['CONTENT_TYPE'])
|
60
|
+
|
61
|
+
update_form_hash_with_json_body(env)
|
62
|
+
end
|
63
|
+
@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
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def update_form_hash_with_json_body(env)
|
73
|
+
body = env[Rack::RACK_INPUT]
|
74
|
+
return unless (body_content = body.read) && !body_content.empty?
|
75
|
+
|
76
|
+
body.rewind # somebody might try to read this stream
|
77
|
+
env.update(
|
78
|
+
Rack::RACK_REQUEST_FORM_HASH => @parser.call(body_content),
|
79
|
+
Rack::RACK_REQUEST_FORM_INPUT => body
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/rack/contrib/jsonp.rb
CHANGED
@@ -106,7 +106,7 @@ module Rack
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def bad_request(body = "Bad Request")
|
109
|
-
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.
|
109
|
+
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ]
|
110
110
|
end
|
111
111
|
|
112
112
|
end
|
data/lib/rack/contrib/locale.rb
CHANGED
@@ -7,43 +7,75 @@ module Rack
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def call(env)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
locale_to_restore = I18n.locale
|
11
|
+
|
12
|
+
locale = user_preferred_locale(env["HTTP_ACCEPT_LANGUAGE"])
|
13
|
+
locale ||= I18n.default_locale
|
14
|
+
|
15
|
+
env['rack.locale'] = I18n.locale = locale.to_s
|
16
|
+
status, headers, body = @app.call(env)
|
17
|
+
|
18
|
+
unless headers['Content-Language']
|
19
|
+
headers['Content-Language'] = locale.to_s
|
20
20
|
end
|
21
|
+
|
22
|
+
[status, headers, body]
|
23
|
+
ensure
|
24
|
+
I18n.locale = locale_to_restore
|
21
25
|
end
|
22
26
|
|
23
27
|
private
|
24
28
|
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
29
|
+
# Accept-Language header is covered mainly by RFC 7231
|
30
|
+
# https://tools.ietf.org/html/rfc7231
|
31
|
+
#
|
32
|
+
# Related sections:
|
33
|
+
#
|
34
|
+
# * https://tools.ietf.org/html/rfc7231#section-5.3.1
|
35
|
+
# * https://tools.ietf.org/html/rfc7231#section-5.3.5
|
36
|
+
# * https://tools.ietf.org/html/rfc4647#section-3.4
|
37
|
+
#
|
38
|
+
# There is an obsolete RFC 2616 (https://tools.ietf.org/html/rfc2616)
|
39
|
+
#
|
40
|
+
# Edge cases:
|
41
|
+
#
|
42
|
+
# * Value can be a comma separated list with optional whitespaces:
|
43
|
+
# Accept-Language: da, en-gb;q=0.8, en;q=0.7
|
44
|
+
#
|
45
|
+
# * Quality value can contain optional whitespaces as well:
|
46
|
+
# Accept-Language: ru-UA, ru; q=0.8, uk; q=0.6, en-US; q=0.4, en; q=0.2
|
47
|
+
#
|
48
|
+
# * Quality prefix 'q=' can be in upper case (Q=)
|
49
|
+
#
|
50
|
+
# * Ignore case when match locale with I18n available locales
|
51
|
+
#
|
52
|
+
def user_preferred_locale(header)
|
53
|
+
return if header.nil?
|
54
|
+
|
55
|
+
locales = header.gsub(/\s+/, '').split(",").map do |language_tag|
|
56
|
+
locale, quality = language_tag.split(/;q=/i)
|
57
|
+
quality = quality ? quality.to_f : 1.0
|
58
|
+
[locale, quality]
|
59
|
+
end.reject do |(locale, quality)|
|
60
|
+
locale == '*' || quality == 0
|
61
|
+
end.sort_by do |(_, quality)|
|
62
|
+
quality
|
63
|
+
end.map(&:first)
|
64
|
+
|
65
|
+
return if locales.empty?
|
66
|
+
|
67
|
+
if I18n.enforce_available_locales
|
68
|
+
locale = locales.reverse.find { |locale| I18n.available_locales.any? { |al| match?(al, locale) } }
|
69
|
+
if locale
|
70
|
+
I18n.available_locales.find { |al| match?(al, locale) }
|
42
71
|
end
|
43
|
-
|
72
|
+
else
|
73
|
+
locales.last
|
74
|
+
end
|
75
|
+
end
|
44
76
|
|
45
|
-
|
46
|
-
|
77
|
+
def match?(s1, s2)
|
78
|
+
s1.to_s.casecmp(s2.to_s) == 0
|
47
79
|
end
|
48
80
|
end
|
49
81
|
end
|
@@ -6,10 +6,51 @@ end
|
|
6
6
|
|
7
7
|
module Rack
|
8
8
|
|
9
|
+
# <b>DEPRECATED:</b> <tt>JSONBodyParser</tt> is a drop-in replacement that is faster and more configurable.
|
10
|
+
#
|
9
11
|
# A Rack middleware for parsing POST/PUT body data when Content-Type is
|
10
12
|
# not one of the standard supported types, like <tt>application/json</tt>.
|
11
13
|
#
|
12
|
-
#
|
14
|
+
# === How to use the middleware
|
15
|
+
#
|
16
|
+
# Example of simple +config.ru+ file:
|
17
|
+
#
|
18
|
+
# require 'rack'
|
19
|
+
# require 'rack/contrib'
|
20
|
+
#
|
21
|
+
# use ::Rack::PostBodyContentTypeParser
|
22
|
+
#
|
23
|
+
# app = lambda do |env|
|
24
|
+
# request = Rack::Request.new(env)
|
25
|
+
# body = "Hello #{request.params['name']}"
|
26
|
+
# [200, {'Content-Type' => 'text/plain'}, [body]]
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# run app
|
30
|
+
#
|
31
|
+
# Example with passing block argument:
|
32
|
+
#
|
33
|
+
# use ::Rack::PostBodyContentTypeParser do |body|
|
34
|
+
# { 'params' => JSON.parse(body) }
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# Example with passing proc argument:
|
38
|
+
#
|
39
|
+
# parser = ->(body) { { 'params' => JSON.parse(body) } }
|
40
|
+
# use ::Rack::PostBodyContentTypeParser, &parser
|
41
|
+
#
|
42
|
+
#
|
43
|
+
# === Failed JSON parsing
|
44
|
+
#
|
45
|
+
# Returns "400 Bad request" response if invalid JSON document was sent:
|
46
|
+
#
|
47
|
+
# Raw HTTP response:
|
48
|
+
#
|
49
|
+
# HTTP/1.1 400 Bad Request
|
50
|
+
# Content-Type: text/plain
|
51
|
+
# Content-Length: 28
|
52
|
+
#
|
53
|
+
# failed to parse body as JSON
|
13
54
|
#
|
14
55
|
class PostBodyContentTypeParser
|
15
56
|
|
@@ -25,6 +66,7 @@ module Rack
|
|
25
66
|
APPLICATION_JSON = 'application/json'.freeze
|
26
67
|
|
27
68
|
def initialize(app, &block)
|
69
|
+
warn "[DEPRECATION] `PostBodyContentTypeParser` is deprecated. Use `JSONBodyParser` as a drop-in replacement."
|
28
70
|
@app = app
|
29
71
|
@block = block || Proc.new { |body| JSON.parse(body, :create_additions => false) }
|
30
72
|
end
|
@@ -40,7 +82,7 @@ module Rack
|
|
40
82
|
end
|
41
83
|
|
42
84
|
def bad_request(body = 'Bad Request')
|
43
|
-
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.
|
85
|
+
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ]
|
44
86
|
end
|
45
87
|
end
|
46
88
|
end
|
@@ -3,9 +3,12 @@ require 'rack'
|
|
3
3
|
|
4
4
|
# Rack::ResponseCache is a Rack middleware that caches responses for successful
|
5
5
|
# GET requests with no query string to disk or any ruby object that has an
|
6
|
-
# []= method (so it works with memcached).
|
7
|
-
#
|
8
|
-
# be served
|
6
|
+
# []= method (so it works with memcached). As with Rails' page caching, this
|
7
|
+
# middleware only writes to the cache -- it never reads. The logic of whether a
|
8
|
+
# cached response should be served is left either to your web server, via
|
9
|
+
# something like the <tt>try_files</tt> directive in nginx, or to your
|
10
|
+
# cache-reading middleware of choice, mounted before Rack::ResponseCache in the
|
11
|
+
# stack.
|
9
12
|
class Rack::ResponseCache
|
10
13
|
# The default proc used if a block is not provided to .new
|
11
14
|
# It unescapes the PATH_INFO of the environment, and makes sure that it doesn't
|
@@ -15,7 +18,14 @@ class Rack::ResponseCache
|
|
15
18
|
# of the path to index.html.
|
16
19
|
DEFAULT_PATH_PROC = proc do |env, res|
|
17
20
|
path = Rack::Utils.unescape(env['PATH_INFO'])
|
18
|
-
|
21
|
+
headers = res[1]
|
22
|
+
# Content-Type is almost always at headers['Content-Type'], but to fully
|
23
|
+
# comply with HTTP RFC 7230, we fall back to a case-insensitive lookup
|
24
|
+
content_type = headers.fetch('Content-Type') do |titlecase_key|
|
25
|
+
_, val = headers.find { |key, _| key.casecmp(titlecase_key) == 0 }
|
26
|
+
val
|
27
|
+
end
|
28
|
+
if !path.include?('..') and match = /text\/((?:x|ht)ml|css)/o.match(content_type)
|
19
29
|
type = match[1]
|
20
30
|
path = "#{path}.#{type}" unless /\.#{type}\z/.match(path)
|
21
31
|
path = File.join(File.dirname(path), 'index.html') if type == 'html' and File.basename(path) == '.html'
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
|
3
5
|
#
|
@@ -68,7 +70,6 @@ module Rack
|
|
68
70
|
@version_regex = options.fetch(:version_regex, /-[\d.]+([.][a-zA-Z][\w]+)?$/)
|
69
71
|
end
|
70
72
|
@duration_in_seconds = self.duration_in_seconds
|
71
|
-
@duration_in_words = self.duration_in_words
|
72
73
|
end
|
73
74
|
|
74
75
|
def call(env)
|
@@ -83,14 +84,15 @@ module Rack
|
|
83
84
|
status, headers, body = @file_server.call(env)
|
84
85
|
if @no_cache[url].nil?
|
85
86
|
headers['Cache-Control'] ="max-age=#{@duration_in_seconds}, public"
|
86
|
-
headers['Expires'] =
|
87
|
+
headers['Expires'] = duration_in_words
|
87
88
|
end
|
89
|
+
headers['Date'] = Time.now.httpdate
|
88
90
|
[status, headers, body]
|
89
91
|
end
|
90
92
|
end
|
91
93
|
|
92
94
|
def duration_in_words
|
93
|
-
(Time.now + self.duration_in_seconds).
|
95
|
+
(Time.now.utc + self.duration_in_seconds).httpdate
|
94
96
|
end
|
95
97
|
|
96
98
|
def duration_in_seconds
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-contrib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- rack-devel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -28,16 +28,22 @@ dependencies:
|
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.0'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '3'
|
34
37
|
type: :development
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
|
-
- - "
|
41
|
+
- - ">="
|
39
42
|
- !ruby/object:Gem::Version
|
40
43
|
version: '1.0'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3'
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: git-version-bump
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -224,6 +230,20 @@ dependencies:
|
|
224
230
|
- - "~>"
|
225
231
|
- !ruby/object:Gem::Version
|
226
232
|
version: '0.17'
|
233
|
+
- !ruby/object:Gem::Dependency
|
234
|
+
name: timecop
|
235
|
+
requirement: !ruby/object:Gem::Requirement
|
236
|
+
requirements:
|
237
|
+
- - "~>"
|
238
|
+
- !ruby/object:Gem::Version
|
239
|
+
version: '0.9'
|
240
|
+
type: :development
|
241
|
+
prerelease: false
|
242
|
+
version_requirements: !ruby/object:Gem::Requirement
|
243
|
+
requirements:
|
244
|
+
- - "~>"
|
245
|
+
- !ruby/object:Gem::Version
|
246
|
+
version: '0.9'
|
227
247
|
description: Contributed Rack Middleware and Utilities
|
228
248
|
email: rack-devel@googlegroups.com
|
229
249
|
executables: []
|
@@ -250,6 +270,7 @@ files:
|
|
250
270
|
- lib/rack/contrib/expectation_cascade.rb
|
251
271
|
- lib/rack/contrib/garbagecollector.rb
|
252
272
|
- lib/rack/contrib/host_meta.rb
|
273
|
+
- lib/rack/contrib/json_body_parser.rb
|
253
274
|
- lib/rack/contrib/jsonp.rb
|
254
275
|
- lib/rack/contrib/lazy_conditional_get.rb
|
255
276
|
- lib/rack/contrib/lighttpd_script_name_fix.rb
|
@@ -296,8 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
296
317
|
- !ruby/object:Gem::Version
|
297
318
|
version: '0'
|
298
319
|
requirements: []
|
299
|
-
|
300
|
-
rubygems_version: 2.7.7
|
320
|
+
rubygems_version: 3.0.3
|
301
321
|
signing_key:
|
302
322
|
specification_version: 2
|
303
323
|
summary: Contributed Rack Middleware and Utilities
|