rack-protection 2.1.0 → 4.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3268bb2b60f8095b38658717f5e267da2e1dfbee57f487baf39a185d3cf9266
4
- data.tar.gz: fc40122b95963a81333da038536782d85a9abdc92b823eb5d3044ef3c5c807c4
3
+ metadata.gz: 98553e74b3c41ed1630ad332c07ac4be4d2cf7f9f7d42c4ea383ed10b36cfda8
4
+ data.tar.gz: d1af8ebd50af6447a3879ae531de1dd5e664cb7493919f2af94c4d2a5d0036d4
5
5
  SHA512:
6
- metadata.gz: 7b381c903bb99d1e8cfcd00554642aafc2644f4432987be95e094a84b0f020648efb92b4a48e082cda5749045c44d14e5f08d97a1a733bf2b1e850eeaf69d67b
7
- data.tar.gz: bfb60cf9484528f0096cd68a6da55b66fa3471407a120caff83ee961b54043f3e4aca45a219273e7e48cc85ce70742ee75dab750609239fb887dfc35dfbcae59
6
+ metadata.gz: 7c38bcefaa24abe73b6d19562e3ac028aa857458ba9a60230e94b383c94b5493e1e0488ec08f4afdd6d09fd4a6cf088662be0f48e21da55cbadf45eaf1599f47
7
+ data.tar.gz: ddaf456e0d4d72f433f1d08f7dcf0c12280b228f43aaca4119ed60201170bcf57ba3cb9383be0b86e179ac664b568bb7fa37dbb5695d6f2a8583e8edc8b706c7
data/Gemfile CHANGED
@@ -1,13 +1,13 @@
1
- source "https://rubygems.org"
2
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gemspec
3
5
 
4
6
  gem 'rake'
7
+ gem 'rspec', '~> 3'
8
+ gem 'rack-test'
5
9
 
6
10
  rack_version = ENV['rack'].to_s
7
- rack_version = nil if rack_version.empty? or rack_version == 'stable'
8
- rack_version = {:github => 'rack/rack'} if rack_version == 'master'
11
+ rack_version = nil if rack_version.empty? || (rack_version == 'stable')
12
+ rack_version = { github: 'rack/rack' } if rack_version == 'head'
9
13
  gem 'rack', rack_version
10
-
11
- gem 'sinatra', path: '..'
12
-
13
- gemspec
data/README.md CHANGED
@@ -34,6 +34,10 @@ run MyApp
34
34
 
35
35
  # Prevented Attacks
36
36
 
37
+ ## DNS rebinding and other Host header attacks
38
+
39
+ * [`Rack::Protection::HostAuthorization`][host-authorization] (not included by `use Rack::Protection`)
40
+
37
41
  ## Cross Site Request Forgery
38
42
 
39
43
  Prevented by:
@@ -69,11 +73,12 @@ Prevented by:
69
73
 
70
74
  Prevented by:
71
75
 
72
- * [`Rack::Protection::SessionHijacking`][session-hijacking]
76
+ * [`Rack::Protection::SessionHijacking`][session-hijacking] (not included by `use Rack::Protection`)
73
77
 
74
78
  ## Cookie Tossing
75
79
 
76
80
  Prevented by:
81
+
77
82
  * [`Rack::Protection::CookieTossing`][cookie-tossing] (not included by `use Rack::Protection`)
78
83
 
79
84
  ## IP Spoofing
@@ -95,6 +100,7 @@ Prevented by:
95
100
  # Instrumentation
96
101
 
97
102
  Instrumentation is enabled by passing in an instrumenter as an option.
103
+
98
104
  ```
99
105
  use Rack::Protection, instrumenter: ActiveSupport::Notifications
100
106
  ```
@@ -107,6 +113,7 @@ The instrumenter is passed a namespace (String) and environment (Hash). The name
107
113
  [escaped-params]: http://www.sinatrarb.com/protection/escaped_params
108
114
  [form-token]: http://www.sinatrarb.com/protection/form_token
109
115
  [frame-options]: http://www.sinatrarb.com/protection/frame_options
116
+ [host-authorization]: https://github.com/sinatra/sinatra/blob/main/rack-protection/lib/rack/protection/host_authorization.rb
110
117
  [http-origin]: http://www.sinatrarb.com/protection/http_origin
111
118
  [ip-spoofing]: http://www.sinatrarb.com/protection/ip_spoofing
112
119
  [json-csrf]: http://www.sinatrarb.com/protection/json_csrf
data/Rakefile CHANGED
@@ -1,53 +1,55 @@
1
- # encoding: utf-8
2
- $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
3
4
 
4
5
  begin
5
6
  require 'bundler'
6
7
  Bundler::GemHelper.install_tasks
7
8
  rescue LoadError => e
8
- $stderr.puts e
9
+ warn e
9
10
  end
10
11
 
11
- desc "run specs"
12
- task(:spec) { ruby '-S rspec spec' }
12
+ desc 'run specs'
13
+ task(:spec) { ruby '-S rspec' }
13
14
 
14
15
  namespace :doc do
15
16
  task :readmes do
16
17
  Dir.glob 'lib/rack/protection/*.rb' do |file|
17
18
  excluded_files = %w[lib/rack/protection/base.rb lib/rack/protection/version.rb]
18
19
  next if excluded_files.include?(file)
20
+
19
21
  doc = File.read(file)[/^ module Protection(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n")
20
- file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc"
21
- Dir.mkdir "doc" unless File.directory? "doc"
22
+ file = "doc/#{file[4..-4].tr('/_', '-')}.rdoc"
23
+ Dir.mkdir 'doc' unless File.directory? 'doc'
22
24
  puts "writing #{file}"
23
- File.open(file, "w") { |f| f << doc }
25
+ File.open(file, 'w') { |f| f << doc }
24
26
  end
25
27
  end
26
28
 
27
29
  task :index do
28
- doc = File.read("README.md")
29
- file = "doc/rack-protection-readme.md"
30
- Dir.mkdir "doc" unless File.directory? "doc"
30
+ doc = File.read('README.md')
31
+ file = 'doc/rack-protection-readme.md'
32
+ Dir.mkdir 'doc' unless File.directory? 'doc'
31
33
  puts "writing #{file}"
32
- File.open(file, "w") { |f| f << doc }
34
+ File.open(file, 'w') { |f| f << doc }
33
35
  end
34
36
 
35
- task :all => [:readmes, :index]
37
+ task all: %i[readmes index]
36
38
  end
37
39
 
38
- desc "generate documentation"
39
- task :doc => 'doc:all'
40
+ desc 'generate documentation'
41
+ task doc: 'doc:all'
40
42
 
41
- desc "generate gemspec"
43
+ desc 'generate gemspec'
42
44
  task 'rack-protection.gemspec' do
43
45
  require 'rack/protection/version'
44
46
  content = File.binread 'rack-protection.gemspec'
45
47
 
46
48
  # fetch data
47
49
  fields = {
48
- :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/),
49
- :email => ["mail@zzak.io", "konstantin.haase@gmail.com"],
50
- :files => %w(License README.md Rakefile Gemfile rack-protection.gemspec) + Dir['lib/**/*']
50
+ authors: `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/),
51
+ email: ['mail@zzak.io', 'konstantin.haase@gmail.com'],
52
+ files: %w[License README.md Rakefile Gemfile rack-protection.gemspec] + Dir['lib/**/*']
51
53
  }
52
54
 
53
55
  # insert data
@@ -67,6 +69,6 @@ task 'rack-protection.gemspec' do
67
69
  File.open('rack-protection.gemspec', 'w') { |f| f << content }
68
70
  end
69
71
 
70
- task :gemspec => 'rack-protection.gemspec'
71
- task :default => :spec
72
- task :test => :spec
72
+ task gemspec: 'rack-protection.gemspec'
73
+ task default: :spec
74
+ task test: :spec
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
  require 'securerandom'
5
+ require 'openssl'
3
6
  require 'base64'
4
7
 
5
8
  module Rack
@@ -16,7 +19,10 @@ module Rack
16
19
  # It checks the <tt>X-CSRF-Token</tt> header and the <tt>POST</tt> form
17
20
  # data.
18
21
  #
19
- # Compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem.
22
+ # It is not OOTB-compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem.
23
+ # For that, the following patch needs to be applied:
24
+ #
25
+ # Rack::Protection::AuthenticityToken.default_options(key: "csrf.token", authenticity_param: "_csrf")
20
26
  #
21
27
  # == Options
22
28
  #
@@ -24,6 +30,13 @@ module Rack
24
30
  # the token on a request. Default value:
25
31
  # <tt>"authenticity_token"</tt>
26
32
  #
33
+ # [<tt>:key</tt>] the name of the param that should contain
34
+ # the token in the session. Default value:
35
+ # <tt>:csrf</tt>
36
+ #
37
+ # [<tt>:allow_if</tt>] a proc for custom allow/deny logic. Default value:
38
+ # <tt>nil</tt>
39
+ #
27
40
  # == Example: Forms application
28
41
  #
29
42
  # To show what the AuthenticityToken does, this section includes a sample
@@ -33,14 +46,15 @@ module Rack
33
46
  # Install the gem, then run the program:
34
47
  #
35
48
  # gem install 'rack-protection'
36
- # ruby server.rb
49
+ # puma server.ru
37
50
  #
38
- # Here is <tt>server.rb</tt>:
51
+ # Here is <tt>server.ru</tt>:
39
52
  #
40
53
  # require 'rack/protection'
54
+ # require 'rack/session'
41
55
  #
42
56
  # app = Rack::Builder.app do
43
- # use Rack::Session::Cookie, secret: 'secret'
57
+ # use Rack::Session::Cookie, secret: 'CHANGEMEaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
44
58
  # use Rack::Protection::AuthenticityToken
45
59
  #
46
60
  # run -> (env) do
@@ -74,7 +88,7 @@ module Rack
74
88
  # end
75
89
  # end
76
90
  #
77
- # Rack::Handler::WEBrick.run app
91
+ # run app
78
92
  #
79
93
  # == Example: Customize which POST parameter holds the token
80
94
  #
@@ -84,42 +98,57 @@ module Rack
84
98
  class AuthenticityToken < Base
85
99
  TOKEN_LENGTH = 32
86
100
 
87
- default_options :authenticity_param => 'authenticity_token',
88
- :allow_if => nil
101
+ default_options authenticity_param: 'authenticity_token',
102
+ key: :csrf,
103
+ allow_if: nil
89
104
 
90
- def self.token(session)
91
- self.new(nil).mask_authenticity_token(session)
105
+ def self.token(session, path: nil, method: :post)
106
+ new(nil).mask_authenticity_token(session, path: path, method: method)
92
107
  end
93
108
 
94
109
  def self.random_token
95
- SecureRandom.base64(TOKEN_LENGTH)
110
+ SecureRandom.urlsafe_base64(TOKEN_LENGTH, padding: false)
96
111
  end
97
112
 
98
113
  def accepts?(env)
99
- session = session env
114
+ session = session(env)
100
115
  set_token(session)
101
116
 
102
117
  safe?(env) ||
103
- valid_token?(session, env['HTTP_X_CSRF_TOKEN']) ||
104
- valid_token?(session, Request.new(env).params[options[:authenticity_param]]) ||
105
- ( options[:allow_if] && options[:allow_if].call(env) )
118
+ valid_token?(env, env['HTTP_X_CSRF_TOKEN']) ||
119
+ valid_token?(env, Request.new(env).params[options[:authenticity_param]]) ||
120
+ options[:allow_if]&.call(env)
121
+ rescue StandardError
122
+ false
106
123
  end
107
124
 
108
- def mask_authenticity_token(session)
109
- token = set_token(session)
125
+ def mask_authenticity_token(session, path: nil, method: :post)
126
+ set_token(session)
127
+
128
+ token = if path && method
129
+ per_form_token(session, path, method)
130
+ else
131
+ global_token(session)
132
+ end
133
+
110
134
  mask_token(token)
111
135
  end
112
136
 
137
+ GLOBAL_TOKEN_IDENTIFIER = '!real_csrf_token'
138
+ private_constant :GLOBAL_TOKEN_IDENTIFIER
139
+
113
140
  private
114
141
 
115
142
  def set_token(session)
116
- session[:csrf] ||= self.class.random_token
143
+ session[options[:key]] ||= self.class.random_token
117
144
  end
118
145
 
119
146
  # Checks the client's masked token to see if it matches the
120
147
  # session token.
121
- def valid_token?(session, token)
122
- return false if token.nil? || token.empty?
148
+ def valid_token?(env, token)
149
+ return false if token.nil? || !token.is_a?(String) || token.empty?
150
+
151
+ session = session(env)
123
152
 
124
153
  begin
125
154
  token = decode_token(token)
@@ -131,13 +160,13 @@ module Rack
131
160
  # to handle any unmasked tokens that we've issued without error.
132
161
 
133
162
  if unmasked_token?(token)
134
- compare_with_real_token token, session
135
-
163
+ compare_with_real_token(token, session)
136
164
  elsif masked_token?(token)
137
165
  token = unmask_token(token)
138
166
 
139
- compare_with_real_token token, session
140
-
167
+ compare_with_global_token(token, session) ||
168
+ compare_with_real_token(token, session) ||
169
+ compare_with_per_form_token(token, session, Request.new(env))
141
170
  else
142
171
  false # Token is malformed
143
172
  end
@@ -147,7 +176,6 @@ module Rack
147
176
  # on each request. The masking is used to mitigate SSL attacks
148
177
  # like BREACH.
149
178
  def mask_token(token)
150
- token = decode_token(token)
151
179
  one_time_pad = SecureRandom.random_bytes(token.length)
152
180
  encrypted_token = xor_byte_strings(one_time_pad, token)
153
181
  masked_token = one_time_pad + encrypted_token
@@ -160,7 +188,7 @@ module Rack
160
188
  # value and decrypt it
161
189
  token_length = masked_token.length / 2
162
190
  one_time_pad = masked_token[0...token_length]
163
- encrypted_token = masked_token[token_length..-1]
191
+ encrypted_token = masked_token[token_length..]
164
192
  xor_byte_strings(one_time_pad, encrypted_token)
165
193
  end
166
194
 
@@ -176,16 +204,41 @@ module Rack
176
204
  secure_compare(token, real_token(session))
177
205
  end
178
206
 
207
+ def compare_with_global_token(token, session)
208
+ secure_compare(token, global_token(session))
209
+ end
210
+
211
+ def compare_with_per_form_token(token, session, request)
212
+ secure_compare(token,
213
+ per_form_token(session, request.path.chomp('/'), request.request_method))
214
+ end
215
+
179
216
  def real_token(session)
180
- decode_token(session[:csrf])
217
+ decode_token(session[options[:key]])
218
+ end
219
+
220
+ def global_token(session)
221
+ token_hmac(session, GLOBAL_TOKEN_IDENTIFIER)
222
+ end
223
+
224
+ def per_form_token(session, path, method)
225
+ token_hmac(session, "#{path}##{method.downcase}")
181
226
  end
182
227
 
183
228
  def encode_token(token)
184
- Base64.strict_encode64(token)
229
+ Base64.urlsafe_encode64(token)
185
230
  end
186
231
 
187
232
  def decode_token(token)
188
- Base64.strict_decode64(token)
233
+ Base64.urlsafe_decode64(token)
234
+ end
235
+
236
+ def token_hmac(session, identifier)
237
+ OpenSSL::HMAC.digest(
238
+ OpenSSL::Digest.new('SHA256'),
239
+ real_token(session),
240
+ identifier
241
+ )
189
242
  end
190
243
 
191
244
  def xor_byte_strings(s1, s2)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
  require 'rack/utils'
3
5
  require 'digest'
@@ -8,12 +10,12 @@ module Rack
8
10
  module Protection
9
11
  class Base
10
12
  DEFAULT_OPTIONS = {
11
- :reaction => :default_reaction, :logging => true,
12
- :message => 'Forbidden', :encryptor => Digest::SHA1,
13
- :session_key => 'rack.session', :status => 403,
14
- :allow_empty_referrer => true,
15
- :report_key => "protection.failed",
16
- :html_types => %w[text/html application/xhtml text/xml application/xml]
13
+ reaction: :default_reaction, logging: true,
14
+ message: 'Forbidden', encryptor: Digest::SHA1,
15
+ session_key: 'rack.session', status: 403,
16
+ allow_empty_referrer: true,
17
+ report_key: 'protection.failed',
18
+ html_types: %w[text/html application/xhtml text/xml application/xml]
17
19
  }
18
20
 
19
21
  attr_reader :app, :options
@@ -31,7 +33,8 @@ module Rack
31
33
  end
32
34
 
33
35
  def initialize(app, options = {})
34
- @app, @options = app, default_options.merge(options)
36
+ @app = app
37
+ @options = default_options.merge(options)
35
38
  end
36
39
 
37
40
  def safe?(env)
@@ -52,24 +55,33 @@ module Rack
52
55
 
53
56
  def react(env)
54
57
  result = send(options[:reaction], env)
55
- result if Array === result and result.size == 3
58
+ result if (Array === result) && (result.size == 3)
59
+ end
60
+
61
+ def debug(env, message)
62
+ return unless options[:logging]
63
+
64
+ l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors'])
65
+ l.debug(message)
56
66
  end
57
67
 
58
68
  def warn(env, message)
59
69
  return unless options[:logging]
70
+
60
71
  l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors'])
61
72
  l.warn(message)
62
73
  end
63
74
 
64
75
  def instrument(env)
65
- return unless i = options[:instrumenter]
76
+ return unless (i = options[:instrumenter])
77
+
66
78
  env['rack.protection.attack'] = self.class.name.split('::').last.downcase
67
79
  i.instrument('rack.protection', env)
68
80
  end
69
81
 
70
82
  def deny(env)
71
83
  warn env, "attack prevented by #{self.class}"
72
- [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]]
84
+ [options[:status], { 'content-type' => 'text/plain' }, [options[:message]]]
73
85
  end
74
86
 
75
87
  def report(env)
@@ -83,16 +95,24 @@ module Rack
83
95
 
84
96
  def session(env)
85
97
  return env[options[:session_key]] if session? env
86
- fail "you need to set up a session middleware *before* #{self.class}"
98
+
99
+ raise "you need to set up a session middleware *before* #{self.class}"
87
100
  end
88
101
 
89
102
  def drop_session(env)
90
- session(env).clear if session? env
103
+ return unless session? env
104
+
105
+ session(env).clear
106
+
107
+ return if ["1", "true"].include?(ENV["RACK_PROTECTION_SILENCE_DROP_SESSION_WARNING"])
108
+
109
+ warn env, "session dropped by #{self.class}"
91
110
  end
92
111
 
93
112
  def referrer(env)
94
113
  ref = env['HTTP_REFERER'].to_s
95
- return if !options[:allow_empty_referrer] and ref.empty?
114
+ return if !options[:allow_empty_referrer] && ref.empty?
115
+
96
116
  URI.parse(ref).host || Request.new(env).host
97
117
  rescue URI::InvalidURIError
98
118
  end
@@ -102,7 +122,7 @@ module Rack
102
122
  end
103
123
 
104
124
  def random_string(secure = defined? SecureRandom)
105
- secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1)
125
+ secure ? SecureRandom.hex(16) : '%032x' % rand((2**128) - 1)
106
126
  rescue NotImplementedError
107
127
  random_string false
108
128
  end
@@ -118,8 +138,9 @@ module Rack
118
138
  alias default_reaction deny
119
139
 
120
140
  def html?(headers)
121
- return false unless header = headers.detect { |k,v| k.downcase == 'content-type' }
122
- options[:html_types].include? header.last[/^\w+\/\w+/]
141
+ return false unless (header = headers.detect { |k, _v| k.downcase == 'content-type' })
142
+
143
+ options[:html_types].include? header.last[%r{^\w+/\w+}]
123
144
  end
124
145
  end
125
146
  end
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  require 'rack/protection'
3
4
 
4
5
  module Rack
@@ -25,7 +26,7 @@ module Rack
25
26
  # https://scotthelme.co.uk/csp-cheat-sheet/
26
27
  # http://www.html5rocks.com/en/tutorials/security/content-security-policy/
27
28
  #
28
- # Sets the 'Content-Security-Policy[-Report-Only]' header.
29
+ # Sets the 'content-security-policy[-report-only]' header.
29
30
  #
30
31
  # Options: ContentSecurityPolicy configuration is a complex topic with
31
32
  # several levels of support that has evolved over time.
@@ -38,16 +39,16 @@ module Rack
38
39
  class ContentSecurityPolicy < Base
39
40
  default_options default_src: "'self'", report_only: false
40
41
 
41
- DIRECTIVES = %i(base_uri child_src connect_src default_src
42
+ DIRECTIVES = %i[base_uri child_src connect_src default_src
42
43
  font_src form_action frame_ancestors frame_src
43
44
  img_src manifest_src media_src object_src
44
45
  plugin_types referrer reflected_xss report_to
45
46
  report_uri require_sri_for sandbox script_src
46
47
  style_src worker_src webrtc_src navigate_to
47
- prefetch_src).freeze
48
+ prefetch_src].freeze
48
49
 
49
- NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener
50
- upgrade_insecure_requests).freeze
50
+ NO_ARG_DIRECTIVES = %i[block_all_mixed_content disown_opener
51
+ upgrade_insecure_requests].freeze
51
52
 
52
53
  def csp_policy
53
54
  directives = []
@@ -70,7 +71,7 @@ module Rack
70
71
 
71
72
  def call(env)
72
73
  status, headers, body = @app.call(env)
73
- header = options[:report_only] ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
74
+ header = options[:report_only] ? 'content-security-policy-report-only' : 'content-security-policy'
74
75
  headers[header] ||= csp_policy if html? headers
75
76
  [status, headers, body]
76
77
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
  require 'pathname'
3
5
 
@@ -29,9 +31,8 @@ module Rack
29
31
  cookie_header = env['HTTP_COOKIE']
30
32
  cookies = Rack::Utils.parse_query(cookie_header, ';,') { |s| s }
31
33
  cookies.each do |k, v|
32
- if k == session_key && Array(v).size > 1
33
- bad_cookies << k
34
- elsif k != session_key && Rack::Utils.unescape(k) == session_key
34
+ if (k == session_key && Array(v).size > 1) ||
35
+ (k != session_key && Rack::Utils.unescape(k) == session_key)
35
36
  bad_cookies << k
36
37
  end
37
38
  end
@@ -40,6 +41,7 @@ module Rack
40
41
 
41
42
  def remove_bad_cookies(request, response)
42
43
  return if bad_cookies.empty?
44
+
43
45
  paths = cookie_paths(request.path)
44
46
  bad_cookies.each do |name|
45
47
  paths.each { |path| response.set_cookie name, empty_cookie(request.host, path) }
@@ -49,7 +51,7 @@ module Rack
49
51
  def redirect(env)
50
52
  request = Request.new(env)
51
53
  warn env, "attack prevented by #{self.class}"
52
- [302, {'Content-Type' => 'text/html', 'Location' => request.path}, []]
54
+ [302, { 'content-type' => 'text/html', 'location' => request.path }, []]
53
55
  end
54
56
 
55
57
  def bad_cookies
@@ -64,7 +66,7 @@ module Rack
64
66
  end
65
67
 
66
68
  def empty_cookie(host, path)
67
- {:value => '', :domain => host, :path => path, :expires => Time.at(0)}
69
+ { value: '', domain: host, path: path, expires: Time.at(0) }
68
70
  end
69
71
 
70
72
  def session_key
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
  require 'rack/utils'
3
5
  require 'tempfile'
@@ -15,8 +17,7 @@ module Rack
15
17
  # More infos:: http://en.wikipedia.org/wiki/Cross-site_scripting
16
18
  #
17
19
  # Automatically escapes Rack::Request#params so they can be embedded in HTML
18
- # or JavaScript without any further issues. Calls +html_safe+ on the escaped
19
- # strings if defined, to avoid double-escaping in Rails.
20
+ # or JavaScript without any further issues.
20
21
  #
21
22
  # Options:
22
23
  # escape:: What escaping modes to use, should be Symbol or Array of Symbols.
@@ -29,8 +30,8 @@ module Rack
29
30
  public :escape_html
30
31
  end
31
32
 
32
- default_options :escape => :html,
33
- :escaper => defined?(EscapeUtils) ? EscapeUtils : self
33
+ default_options escape: :html,
34
+ escaper: defined?(EscapeUtils) ? EscapeUtils : self
34
35
 
35
36
  def initialize(*)
36
37
  super
@@ -41,15 +42,19 @@ module Rack
41
42
  @javascript = modes.include? :javascript
42
43
  @url = modes.include? :url
43
44
 
44
- if @javascript and not @escaper.respond_to? :escape_javascript
45
- fail("Use EscapeUtils for JavaScript escaping.")
46
- end
45
+ return unless @javascript && (!@escaper.respond_to? :escape_javascript)
46
+
47
+ raise('Use EscapeUtils for JavaScript escaping.')
47
48
  end
48
49
 
49
50
  def call(env)
50
51
  request = Request.new(env)
51
52
  get_was = handle(request.GET)
52
- post_was = handle(request.POST) rescue nil
53
+ post_was = begin
54
+ handle(request.POST)
55
+ rescue StandardError
56
+ nil
57
+ end
53
58
  app.call env
54
59
  ensure
55
60
  request.GET.replace get_was if get_was
@@ -68,13 +73,12 @@ module Rack
68
73
  when Array then object.map { |o| escape(o) }
69
74
  when String then escape_string(object)
70
75
  when Tempfile then object
71
- else nil
72
76
  end
73
77
  end
74
78
 
75
79
  def escape_hash(hash)
76
80
  hash = hash.dup
77
- hash.each { |k,v| hash[k] = escape(v) }
81
+ hash.each { |k, v| hash[k] = escape(v) }
78
82
  hash
79
83
  end
80
84
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -16,7 +18,7 @@ module Rack
16
18
  # Compatible with rack-csrf.
17
19
  class FormToken < AuthenticityToken
18
20
  def accepts?(env)
19
- env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super
21
+ env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' or super
20
22
  end
21
23
  end
22
24
  end