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.

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