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.

Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -1
  3. data/lib/rack/contrib/access.rb +8 -6
  4. data/lib/rack/contrib/backstage.rb +5 -3
  5. data/lib/rack/contrib/bounce_favicon.rb +3 -1
  6. data/lib/rack/contrib/callbacks.rb +2 -0
  7. data/lib/rack/contrib/common_cookies.rb +19 -11
  8. data/lib/rack/contrib/config.rb +3 -15
  9. data/lib/rack/contrib/cookies.rb +2 -0
  10. data/lib/rack/contrib/csshttprequest.rb +11 -5
  11. data/lib/rack/contrib/deflect.rb +35 -33
  12. data/lib/rack/contrib/enforce_valid_encoding.rb +3 -1
  13. data/lib/rack/contrib/evil.rb +2 -0
  14. data/lib/rack/contrib/expectation_cascade.rb +5 -3
  15. data/lib/rack/contrib/garbagecollector.rb +2 -0
  16. data/lib/rack/contrib/host_meta.rb +3 -1
  17. data/lib/rack/contrib/json_body_parser.rb +22 -11
  18. data/lib/rack/contrib/jsonp.rb +12 -7
  19. data/lib/rack/contrib/lazy_conditional_get.rb +13 -4
  20. data/lib/rack/contrib/lighttpd_script_name_fix.rb +2 -0
  21. data/lib/rack/contrib/locale.rb +6 -0
  22. data/lib/rack/contrib/mailexceptions.rb +2 -0
  23. data/lib/rack/contrib/nested_params.rb +3 -1
  24. data/lib/rack/contrib/not_found.rb +3 -1
  25. data/lib/rack/contrib/post_body_content_type_parser.rb +4 -2
  26. data/lib/rack/contrib/printout.rb +3 -1
  27. data/lib/rack/contrib/proctitle.rb +2 -0
  28. data/lib/rack/contrib/profiler.rb +17 -14
  29. data/lib/rack/contrib/relative_redirect.rb +11 -5
  30. data/lib/rack/contrib/response_cache.rb +13 -11
  31. data/lib/rack/contrib/response_headers.rb +8 -2
  32. data/lib/rack/contrib/route_exceptions.rb +2 -0
  33. data/lib/rack/contrib/runtime.rb +3 -30
  34. data/lib/rack/contrib/signals.rb +6 -0
  35. data/lib/rack/contrib/simple_endpoint.rb +3 -1
  36. data/lib/rack/contrib/static_cache.rb +13 -4
  37. data/lib/rack/contrib/time_zone.rb +2 -0
  38. data/lib/rack/contrib/try_static.rb +2 -0
  39. data/lib/rack/contrib/version.rb +5 -0
  40. data/lib/rack/contrib.rb +2 -1
  41. metadata +10 -229
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca58db2b7968ad4691e8bec79eea2088dabc3da5d309358f7df76d668324d824
4
- data.tar.gz: 51816bb6b59a598fea16eb100cc783b3c0ff58eb37aa3585bb8d65141f2aae4f
3
+ metadata.gz: '0328244bd18e70963a841937904e3bf27eebfef5de80a4d3e1e09328e4c93ff8'
4
+ data.tar.gz: 2ab61dc5347ad251d9654e3b94f705358081b8dd932b62a07cc95f42d611c3b3
5
5
  SHA512:
6
- metadata.gz: 7442b5ad170ae2946f017d8ccd007d02455b7939fe42239378505b61983cd9a1f090549e87b07e08323a7cf99d04f026e8df0f326e34be166508187cca3735e7
7
- data.tar.gz: 99985100672c65c9ee559a37281f5dddea4fcdc8481ddffd75c2f6e697197a1070339c5b70733d736f8ba33078d5f699f00097cd6ec96266c8f07edb008b727b
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.
@@ -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('/', '/+')}(.*)", nil, 'n')
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
- @original_request = Request.new(env)
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, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, []]
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(@original_request.ip))
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.exists?(@file)
13
+ if File.exist?(@file)
12
14
  content = File.read(@file)
13
- length = "".respond_to?(:bytesize) ? content.bytesize.to_s : content.size.to_s
14
- [503, {'Content-Type' => 'text/html', 'Content-Length' => length}, [content]]
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, {"Content-Type" => "text/html", "Content-Length" => "0"}, []]
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
  class Callbacks
3
5
  def initialize(&block)
@@ -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).tap do |(status, headers, response)|
14
- @host = env['HTTP_HOST'].sub PORT, ''
15
- share_cookie headers
16
- end
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
- @host =~ DOMAIN_REGEXP
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 @host !~ LOCALHOST_OR_IP_REGEXP
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
@@ -1,16 +1,4 @@
1
- module Rack
1
+ # frozen_string_literal: true
2
2
 
3
- # Rack::Config modifies the environment using the block given during
4
- # initialization.
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'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class Cookies
3
5
  class CookieJar < Hash
@@ -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
- response = encode(response)
18
- modify_headers!(headers, response)
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(response, assembled_body="")
29
- response.each { |s| assembled_body << s.to_s } # call down the stack
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)
@@ -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, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, []]
68
+ [403, { 'content-type' => 'text/html', 'content-length' => '0' }, []]
67
69
  end
68
70
 
69
71
  def deflect? env
70
- @remote_addr = env['REMOTE_ADDR']
71
- return false if options[:whitelist].include? @remote_addr
72
- return true if options[:blacklist].include? @remote_addr
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[@remote_addr] ||= {
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 #{@remote_addr}"
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? @remote_addr
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 #{@remote_addr}" if blocked?
121
- @remote_addr_map.delete @remote_addr
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, {'Content-Type'=>'text/plain'}, ['Bad Request']]
21
+ [400, {'content-type'=>'text/plain'}, ['Bad Request']]
20
22
  end
21
23
  end
22
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class Evil
3
5
  # Lets you return a response to the client immediately from anywhere ( M V or C ) in the code.
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class ExpectationCascade
3
- Expect = "Expect".freeze
5
+ Expect = "HTTP_EXPECT".freeze
4
6
  ContinueExpectation = "100-continue".freeze
5
7
 
6
- ExpectationFailed = [417, {"Content-Type" => "text/html"}, []].freeze
7
- NotFound = [404, {"Content-Type" => "text/html"}, []].freeze
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
  # Forces garbage collection after each request.
3
5
  class GarbageCollector
@@ -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, {'Content-Type' => 'application/host-meta'}, [@response]]
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 Content-Type matches 'json'
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 Content-Type is 'application/json' or 'application/vnd+json'
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
- if @verbs.include?(env[Rack::REQUEST_METHOD]) &&
59
- @matcher.call(@media, env['CONTENT_TYPE'])
58
+ begin
59
+ if @verbs.include?(env[Rack::REQUEST_METHOD]) &&
60
+ @matcher.call(@media, env['CONTENT_TYPE'])
60
61
 
61
- update_form_hash_with_json_body(env)
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 => @parser.call(body_content),
89
+ Rack::RACK_REQUEST_FORM_HASH => parsed_body,
79
90
  Rack::RACK_REQUEST_FORM_INPUT => body
80
91
  )
81
92
  end
@@ -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 = HeaderHash.new(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.to_ary.inject(0) { |len, part| len + part.bytesize }
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, body = "")
91
- response.each do |s|
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
- body << s.to_s.gsub(U2028, '\u2028').gsub(U2029, '\u2029')
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, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ]
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, {'Last-Modified' => env['HTTP_IF_MODIFIED_SINCE']}, []]
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['Last-Modified'] = cached_value if stampable? 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['Rack-Lazy-Conditional-Get'] == 'skip'
99
+ headers['rack-lazy-conditional-get'] == 'skip'
91
100
  end
92
101
 
93
102
  def stampable? headers
94
- !headers.has_key?('Last-Modified') and headers['Rack-Lazy-Conditional-Get'] == 'yes'
103
+ !headers.has_key?('last-modified') and headers['rack-lazy-conditional-get'] == 'yes'
95
104
  end
96
105
 
97
106
  def update_cache
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Lighttpd sets the wrong SCRIPT_NAME and PATH_INFO if you mount your
3
5
  # FastCGI app at "/". This middleware fixes this issue.
@@ -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 'net/smtp'
2
4
  require 'mail'
3
5
  require 'erb'
@@ -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 if post_body.respond_to?(: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, {'Content-Type' => @content_type, 'Content-Length' => @length}, [@content]]
30
+ [404, {'content-type' => @content_type, 'content-length' => @length}, [@content]]
29
31
  end
30
32
  end
31
33
  end