rack-contrib 2.0.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +12 -8
- 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 +12 -6
- 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 +94 -0
- 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 +75 -30
- 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 +50 -5
- data/lib/rack/contrib/printout.rb +3 -1
- data/lib/rack/contrib/proctitle.rb +2 -0
- data/lib/rack/contrib/profiler.rb +44 -17
- data/lib/rack/contrib/relative_redirect.rb +11 -5
- data/lib/rack/contrib/response_cache.rb +21 -9
- 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 +18 -7
- 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 +3 -1
- metadata +13 -212
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1b83b23d3e67c0a24d65f6da2406e8430b003100bae5b5e8f3b20486384804e0
|
4
|
+
data.tar.gz: 4e2b5a6b94dbd7be2b10a6dae026509166d236e0b0e842f0a127b59d28298ae9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a12773ffdd9646740002b8124c2bd02bdc92d738ced7c4bb270e6003661a259d4dc8e34925bd909805bd1e40a0d381b9efa59c3d2d02d1fe5f9dcd57af8809c9
|
7
|
+
data.tar.gz: c9955bcfb8787a930d9590cc984ab16a25211e2f52b9fe76b7d357088bbc68503e1c2fc362c46ba0c8febb08aa73eb67ed04bd03ddbdea39bd489e336211d385
|
data/README.md
CHANGED
@@ -3,17 +3,16 @@
|
|
3
3
|
This package includes a variety of add-on components for Rack, a Ruby web server
|
4
4
|
interface:
|
5
5
|
|
6
|
-
* `Rack::AcceptFormat` - Adds a format extension at the end of the URI when there is none, corresponding to the mime-type given in the Accept HTTP header.
|
7
6
|
* `Rack::Access` - Limits access based on IP address
|
8
7
|
* `Rack::Backstage` - Returns content of specified file if it exists, which makes it convenient for putting up maintenance pages.
|
9
8
|
* `Rack::BounceFavicon` - Returns a 404 for requests to `/favicon.ico`
|
10
9
|
* `Rack::CSSHTTPRequest` - Adds CSSHTTPRequest support by encoding responses as CSS for cross-site AJAX-style data loading
|
11
10
|
* `Rack::Callbacks` - Implements DSL for pure before/after filter like Middlewares.
|
12
|
-
* `Rack::Config` - Shared configuration for cooperative middleware.
|
13
11
|
* `Rack::Cookies` - Adds simple cookie jar hash to env
|
14
12
|
* `Rack::Deflect` - Helps protect against DoS attacks.
|
15
13
|
* `Rack::Evil` - Lets the rack application return a response to the client from any place.
|
16
14
|
* `Rack::HostMeta` - Configures `/host-meta` using a block
|
15
|
+
* `Rack::JSONBodyParser` - Adds JSON request bodies to the Rack parameters hash.
|
17
16
|
* `Rack::JSONP` - Adds JSON-P support by stripping out the callback param and padding the response with the appropriate callback format.
|
18
17
|
* `Rack::LazyConditionalGet` - Caches a global `Last-Modified` date and updates it each time there is a request that is not `GET` or `HEAD`.
|
19
18
|
* `Rack::LighttpdScriptNameFix` - Fixes how lighttpd sets the `SCRIPT_NAME` and `PATH_INFO` variables in certain configurations.
|
@@ -21,14 +20,13 @@ interface:
|
|
21
20
|
* `Rack::MailExceptions` - Rescues exceptions raised from the app and sends a useful email with the exception, stacktrace, and contents of the environment.
|
22
21
|
* `Rack::NestedParams` - parses form params with subscripts (e.g., * "`post[title]=Hello`") into a nested/recursive Hash structure (based on Rails' implementation).
|
23
22
|
* `Rack::NotFound` - A default 404 application.
|
24
|
-
* `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
|
23
|
+
* `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
|
25
24
|
* `Rack::Printout` - Prints the environment and the response per request
|
26
25
|
* `Rack::ProcTitle` - Displays request information in process title (`$0`) for monitoring/inspection with ps(1).
|
27
26
|
* `Rack::Profiler` - Uses ruby-prof to measure request time.
|
28
27
|
* `Rack::RelativeRedirect` - Transforms relative paths in redirects to absolute URLs.
|
29
|
-
* `Rack::ResponseCache` - Caches responses to requests without query strings to Disk or a user
|
28
|
+
* `Rack::ResponseCache` - Caches responses to requests without query strings to Disk or a user provided Ruby object. Similar to Rails' page caching.
|
30
29
|
* `Rack::ResponseHeaders` - Manipulates response headers object at runtime
|
31
|
-
* `Rack::Sendfile` - Enables `X-Sendfile` support for bodies that can be served from file.
|
32
30
|
* `Rack::Signals` - Installs signal handlers that are safely processed after a request
|
33
31
|
* `Rack::SimpleEndpoint` - Creates simple endpoints with routing rules, similar to Sinatra actions
|
34
32
|
* `Rack::StaticCache` - Modifies the response headers to facilitiate client and proxy caching for static files that minimizes http requests and improves overall load times for second time visitors.
|
@@ -78,7 +76,7 @@ To contribute to the project, begin by cloning the repo and installing the neces
|
|
78
76
|
|
79
77
|
gem install json rack ruby-prof test-spec test-unit
|
80
78
|
|
81
|
-
To run the entire test suite, run
|
79
|
+
To run the entire test suite, run
|
82
80
|
|
83
81
|
rake test
|
84
82
|
|
@@ -86,7 +84,7 @@ To run a specific component's tests run
|
|
86
84
|
|
87
85
|
specrb -Ilib:test -w test/spec_rack_thecomponent.rb
|
88
86
|
|
89
|
-
This works on ruby 1.8.7 but has problems under ruby 1.9.x.
|
87
|
+
This works on ruby 1.8.7 but has problems under ruby 1.9.x.
|
90
88
|
|
91
89
|
TODO: instructions for 1.9.x and include bundler
|
92
90
|
|
@@ -100,10 +98,16 @@ The criteria for middleware being included in this project are roughly as follow
|
|
100
98
|
These criteria were introduced several years after the start of the project, so some of the included middleware may not meet all of them. In particular, several middleware have external dependencies. It is possible that in some future release of rack-contrib, middleware with external depencies will be removed from the project.
|
101
99
|
|
102
100
|
When submitting code keep the above criteria in mind and also see the code
|
103
|
-
guidelines in CONTRIBUTING.md.
|
101
|
+
guidelines in CONTRIBUTING.md.
|
104
102
|
|
105
103
|
### Links
|
106
104
|
|
107
105
|
* rack-contrib on GitHub:: <https://github.com/rack/rack-contrib>
|
108
106
|
* Rack:: <https://rack.github.io/>
|
109
107
|
* Rack On GitHub:: <https://github.com/rack/rack>
|
108
|
+
|
109
|
+
|
110
|
+
### Security Reporting
|
111
|
+
|
112
|
+
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
|
113
|
+
Tidelift will coordinate the fix and disclosure.
|
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,13 +32,12 @@ 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)
|
34
|
-
headers['Content-Length'] = encoded_response.
|
40
|
+
headers['Content-Length'] = encoded_response.bytesize.to_s
|
35
41
|
headers['Content-Type'] = 'text/css'
|
36
42
|
nil
|
37
43
|
end
|
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
|
@@ -0,0 +1,94 @@
|
|
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
|
+
begin
|
59
|
+
if @verbs.include?(env[Rack::REQUEST_METHOD]) &&
|
60
|
+
@matcher.call(@media, env['CONTENT_TYPE'])
|
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
|
68
|
+
end
|
69
|
+
@app.call(env)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
class ParserError < StandardError; end
|
75
|
+
|
76
|
+
def update_form_hash_with_json_body(env)
|
77
|
+
body = env[Rack::RACK_INPUT]
|
78
|
+
return unless (body_content = body.read) && !body_content.empty?
|
79
|
+
|
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
|
+
|
88
|
+
env.update(
|
89
|
+
Rack::RACK_REQUEST_FORM_HASH => parsed_body,
|
90
|
+
Rack::RACK_REQUEST_FORM_INPUT => body
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
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
|