sidekiq 7.0.9 → 7.3.10

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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +263 -0
  3. data/README.md +5 -5
  4. data/bin/multi_queue_bench +271 -0
  5. data/bin/sidekiqload +41 -14
  6. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
  7. data/lib/generators/sidekiq/job_generator.rb +2 -0
  8. data/lib/sidekiq/api.rb +170 -52
  9. data/lib/sidekiq/capsule.rb +8 -3
  10. data/lib/sidekiq/cli.rb +6 -2
  11. data/lib/sidekiq/client.rb +58 -22
  12. data/lib/sidekiq/component.rb +23 -1
  13. data/lib/sidekiq/config.rb +52 -11
  14. data/lib/sidekiq/deploy.rb +4 -2
  15. data/lib/sidekiq/embedded.rb +2 -0
  16. data/lib/sidekiq/fetch.rb +2 -1
  17. data/lib/sidekiq/iterable_job.rb +55 -0
  18. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  19. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  20. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  21. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  22. data/lib/sidekiq/job/iterable.rb +294 -0
  23. data/lib/sidekiq/job.rb +15 -8
  24. data/lib/sidekiq/job_logger.rb +7 -6
  25. data/lib/sidekiq/job_retry.rb +30 -8
  26. data/lib/sidekiq/job_util.rb +6 -2
  27. data/lib/sidekiq/launcher.rb +8 -6
  28. data/lib/sidekiq/logger.rb +1 -1
  29. data/lib/sidekiq/metrics/query.rb +6 -1
  30. data/lib/sidekiq/metrics/shared.rb +16 -5
  31. data/lib/sidekiq/metrics/tracking.rb +20 -8
  32. data/lib/sidekiq/middleware/current_attributes.rb +88 -16
  33. data/lib/sidekiq/middleware/i18n.rb +2 -0
  34. data/lib/sidekiq/middleware/modules.rb +2 -0
  35. data/lib/sidekiq/monitor.rb +2 -1
  36. data/lib/sidekiq/paginator.rb +8 -2
  37. data/lib/sidekiq/processor.rb +44 -33
  38. data/lib/sidekiq/rails.rb +28 -7
  39. data/lib/sidekiq/redis_client_adapter.rb +30 -31
  40. data/lib/sidekiq/redis_connection.rb +49 -9
  41. data/lib/sidekiq/ring_buffer.rb +3 -0
  42. data/lib/sidekiq/scheduled.rb +3 -3
  43. data/lib/sidekiq/systemd.rb +2 -0
  44. data/lib/sidekiq/testing.rb +32 -13
  45. data/lib/sidekiq/transaction_aware_client.rb +20 -5
  46. data/lib/sidekiq/version.rb +5 -1
  47. data/lib/sidekiq/web/action.rb +29 -7
  48. data/lib/sidekiq/web/application.rb +64 -25
  49. data/lib/sidekiq/web/csrf_protection.rb +9 -6
  50. data/lib/sidekiq/web/helpers.rb +95 -35
  51. data/lib/sidekiq/web/router.rb +5 -2
  52. data/lib/sidekiq/web.rb +67 -3
  53. data/lib/sidekiq.rb +5 -3
  54. data/sidekiq.gemspec +5 -13
  55. data/web/assets/javascripts/application.js +27 -0
  56. data/web/assets/javascripts/dashboard-charts.js +40 -12
  57. data/web/assets/javascripts/dashboard.js +14 -10
  58. data/web/assets/javascripts/metrics.js +34 -0
  59. data/web/assets/stylesheets/application-rtl.css +10 -0
  60. data/web/assets/stylesheets/application.css +38 -3
  61. data/web/locales/en.yml +5 -1
  62. data/web/locales/fr.yml +13 -0
  63. data/web/locales/gd.yml +0 -1
  64. data/web/locales/it.yml +32 -1
  65. data/web/locales/ja.yml +0 -1
  66. data/web/locales/pt-br.yml +20 -1
  67. data/web/locales/tr.yml +100 -0
  68. data/web/locales/uk.yml +24 -1
  69. data/web/locales/zh-cn.yml +0 -1
  70. data/web/locales/zh-tw.yml +0 -1
  71. data/web/views/_footer.erb +12 -1
  72. data/web/views/_job_info.erb +1 -1
  73. data/web/views/_metrics_period_select.erb +1 -1
  74. data/web/views/_summary.erb +7 -7
  75. data/web/views/busy.erb +7 -7
  76. data/web/views/dashboard.erb +29 -36
  77. data/web/views/filtering.erb +6 -0
  78. data/web/views/layout.erb +6 -6
  79. data/web/views/metrics.erb +38 -30
  80. data/web/views/metrics_for_job.erb +30 -39
  81. data/web/views/morgue.erb +2 -2
  82. data/web/views/queue.erb +1 -1
  83. data/web/views/queues.erb +6 -2
  84. metadata +52 -21
@@ -12,7 +12,7 @@ module Sidekiq
12
12
 
13
13
  LUA_ZPOPBYSCORE = <<~LUA
14
14
  local key, now = KEYS[1], ARGV[1]
15
- local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
15
+ local jobs = redis.call("zrange", key, "-inf", now, "byscore", "limit", 0, 1)
16
16
  if jobs[1] then
17
17
  redis.call("zrem", key, jobs[1])
18
18
  return jobs[1]
@@ -144,7 +144,7 @@ module Sidekiq
144
144
  # In the example above, each process should schedule every 10 seconds on average. We special
145
145
  # case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
146
146
  # As we run more processes, the scheduling interval average will approach an even spread
147
- # between 0 and poll interval so we don't need this artifical boost.
147
+ # between 0 and poll interval so we don't need this artificial boost.
148
148
  #
149
149
  count = process_count
150
150
  interval = poll_interval_average(count)
@@ -193,7 +193,7 @@ module Sidekiq
193
193
  # should never depend on sidekiq/api.
194
194
  def cleanup
195
195
  # dont run cleanup more than once per minute
196
- return 0 unless redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) }
196
+ return 0 unless redis { |conn| conn.set("process_cleanup", "1", "NX", "EX", "60") }
197
197
 
198
198
  count = 0
199
199
  redis do |conn|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Sidekiq's systemd integration allows Sidekiq to inform systemd:
3
5
  # 1. when it has successfully started
@@ -5,23 +5,42 @@ require "sidekiq"
5
5
 
6
6
  module Sidekiq
7
7
  class Testing
8
+ class TestModeAlreadySetError < RuntimeError; end
8
9
  class << self
9
- attr_accessor :__test_mode
10
+ attr_accessor :__global_test_mode
10
11
 
12
+ # Calling without a block sets the global test mode, affecting
13
+ # all threads. Calling with a block only affects the current Thread.
11
14
  def __set_test_mode(mode)
12
15
  if block_given?
13
- current_mode = __test_mode
16
+ # Reentrant testing modes will lead to a rat's nest of code which is
17
+ # hard to reason about. You can set the testing mode once globally and
18
+ # you can override that global setting once per-thread.
19
+ raise TestModeAlreadySetError, "Nesting test modes is not supported" if __local_test_mode
20
+
21
+ self.__local_test_mode = mode
14
22
  begin
15
- self.__test_mode = mode
16
23
  yield
17
24
  ensure
18
- self.__test_mode = current_mode
25
+ self.__local_test_mode = nil
19
26
  end
20
27
  else
21
- self.__test_mode = mode
28
+ self.__global_test_mode = mode
22
29
  end
23
30
  end
24
31
 
32
+ def __test_mode
33
+ __local_test_mode || __global_test_mode
34
+ end
35
+
36
+ def __local_test_mode
37
+ Thread.current[:__sidekiq_test_mode]
38
+ end
39
+
40
+ def __local_test_mode=(value)
41
+ Thread.current[:__sidekiq_test_mode] = value
42
+ end
43
+
25
44
  def disable!(&block)
26
45
  __set_test_mode(:disable, &block)
27
46
  end
@@ -64,7 +83,7 @@ module Sidekiq
64
83
  class EmptyQueueError < RuntimeError; end
65
84
 
66
85
  module TestingClient
67
- def raw_push(payloads)
86
+ def atomic_push(conn, payloads)
68
87
  if Sidekiq::Testing.fake?
69
88
  payloads.each do |job|
70
89
  job = Sidekiq.load_json(Sidekiq.dump_json(job))
@@ -93,7 +112,7 @@ module Sidekiq
93
112
  # The Queues class is only for testing the fake queue implementation.
94
113
  # There are 2 data structures involved in tandem. This is due to the
95
114
  # Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
96
- # to the array. Because the array was dervied from a filter of the total
115
+ # to the array. Because the array was derived from a filter of the total
97
116
  # jobs enqueued, it appeared as though the array didn't change.
98
117
  #
99
118
  # To solve this, we'll keep 2 hashes containing the jobs. One with keys based
@@ -259,16 +278,16 @@ module Sidekiq
259
278
  def perform_one
260
279
  raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
261
280
  next_job = jobs.first
262
- Queues.delete_for(next_job["jid"], queue, to_s)
281
+ Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
263
282
  process_job(next_job)
264
283
  end
265
284
 
266
285
  def process_job(job)
267
- inst = new
268
- inst.jid = job["jid"]
269
- inst.bid = job["bid"] if inst.respond_to?(:bid=)
270
- Sidekiq::Testing.server_middleware.invoke(inst, job, job["queue"]) do
271
- execute_job(inst, job["args"])
286
+ instance = new
287
+ instance.jid = job["jid"]
288
+ instance.bid = job["bid"] if instance.respond_to?(:bid=)
289
+ Sidekiq::Testing.server_middleware.invoke(instance, job, job["queue"]) do
290
+ execute_job(instance, job["args"])
272
291
  end
273
292
  end
274
293
 
@@ -7,13 +7,26 @@ module Sidekiq
7
7
  class TransactionAwareClient
8
8
  def initialize(pool: nil, config: nil)
9
9
  @redis_client = Client.new(pool: pool, config: config)
10
+ @transaction_backend =
11
+ if ActiveRecord.version >= Gem::Version.new("7.2")
12
+ ActiveRecord.method(:after_all_transactions_commit)
13
+ else
14
+ AfterCommitEverywhere.method(:after_commit)
15
+ end
16
+ end
17
+
18
+ def batching?
19
+ Thread.current[:sidekiq_batch]
10
20
  end
11
21
 
12
22
  def push(item)
23
+ # 6160 we can't support both Sidekiq::Batch and transactions.
24
+ return @redis_client.push(item) if batching?
25
+
13
26
  # pre-allocate the JID so we can return it immediately and
14
27
  # save it to the database as part of the transaction.
15
28
  item["jid"] ||= SecureRandom.hex(12)
16
- AfterCommitEverywhere.after_commit { @redis_client.push(item) }
29
+ @transaction_backend.call { @redis_client.push(item) }
17
30
  item["jid"]
18
31
  end
19
32
 
@@ -31,10 +44,12 @@ end
31
44
  # Use `Sidekiq.transactional_push!` in your sidekiq.rb initializer
32
45
  module Sidekiq
33
46
  def self.transactional_push!
34
- begin
35
- require "after_commit_everywhere"
36
- rescue LoadError
37
- raise %q(You need to add `gem "after_commit_everywhere"` to your Gemfile to use Sidekiq's transactional client)
47
+ if ActiveRecord.version < Gem::Version.new("7.2")
48
+ begin
49
+ require "after_commit_everywhere"
50
+ rescue LoadError
51
+ raise %q(You need ActiveRecord >= 7.2 or to add `gem "after_commit_everywhere"` to your Gemfile to use Sidekiq's transactional client)
52
+ end
38
53
  end
39
54
 
40
55
  Sidekiq.default_job_options["client_class"] = Sidekiq::TransactionAwareClient
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "7.0.9"
4
+ VERSION = "7.3.10"
5
5
  MAJOR = 7
6
+
7
+ def self.gem_version
8
+ Gem::Version.new(VERSION)
9
+ end
6
10
  end
@@ -15,13 +15,19 @@ module Sidekiq
15
15
  end
16
16
 
17
17
  def halt(res)
18
- throw :halt, [res, {"content-type" => "text/plain"}, [res.to_s]]
18
+ throw :halt, [res, {Rack::CONTENT_TYPE => "text/plain"}, [res.to_s]]
19
19
  end
20
20
 
21
21
  def redirect(location)
22
- throw :halt, [302, {"location" => "#{request.base_url}#{location}"}, []]
22
+ throw :halt, [302, {Web::LOCATION => "#{request.base_url}#{location}"}, []]
23
23
  end
24
24
 
25
+ def reload_page
26
+ current_location = request.referer.gsub(request.base_url, "")
27
+ redirect current_location
28
+ end
29
+
30
+ # deprecated, will warn in 8.0
25
31
  def params
26
32
  indifferent_hash = Hash.new { |hash, key| hash[key.to_s] if Symbol === key }
27
33
 
@@ -31,8 +37,19 @@ module Sidekiq
31
37
  indifferent_hash
32
38
  end
33
39
 
34
- def route_params
35
- env[WebRouter::ROUTE_PARAMS]
40
+ # Use like `url_params("page")` within your action blocks
41
+ def url_params(key)
42
+ request.params[key]
43
+ end
44
+
45
+ # Use like `route_params(:name)` within your action blocks
46
+ # key is required in 8.0, nil is only used for backwards compatibility
47
+ def route_params(key = nil)
48
+ if key
49
+ env[WebRouter::ROUTE_PARAMS][key]
50
+ else
51
+ env[WebRouter::ROUTE_PARAMS]
52
+ end
36
53
  end
37
54
 
38
55
  def session
@@ -42,8 +59,13 @@ module Sidekiq
42
59
  def erb(content, options = {})
43
60
  if content.is_a? Symbol
44
61
  unless respond_to?(:"_erb_#{content}")
45
- src = ERB.new(File.read("#{Web.settings.views}/#{content}.erb")).src
46
- WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
62
+ views = options[:views] || Web.settings.views
63
+ filename = "#{views}/#{content}.erb"
64
+ src = ERB.new(File.read(filename)).src
65
+
66
+ # Need to use lineno less by 1 because erb generates a
67
+ # comment before the source code.
68
+ WebAction.class_eval <<-RUBY, filename, -1 # standard:disable Style/EvalWithLocation
47
69
  def _erb_#{content}
48
70
  #{src}
49
71
  end
@@ -68,7 +90,7 @@ module Sidekiq
68
90
  end
69
91
 
70
92
  def json(payload)
71
- [200, {"content-type" => "application/json", "cache-control" => "private, no-store"}, [Sidekiq.dump_json(payload)]]
93
+ [200, {Rack::CONTENT_TYPE => "application/json", Rack::CACHE_CONTROL => "private, no-store"}, [Sidekiq.dump_json(payload)]]
72
94
  end
73
95
 
74
96
  def initialize(env, block)
@@ -5,7 +5,7 @@ module Sidekiq
5
5
  extend WebRouter
6
6
 
7
7
  REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
8
- CSP_HEADER = [
8
+ CSP_HEADER_TEMPLATE = [
9
9
  "default-src 'self' https: http:",
10
10
  "child-src 'self'",
11
11
  "connect-src 'self' https: http: wss: ws:",
@@ -15,8 +15,8 @@ module Sidekiq
15
15
  "manifest-src 'self'",
16
16
  "media-src 'self'",
17
17
  "object-src 'none'",
18
- "script-src 'self' https: http: 'unsafe-inline'",
19
- "style-src 'self' https: http: 'unsafe-inline'",
18
+ "script-src 'self' 'nonce-!placeholder!'",
19
+ "style-src 'self' https: http: 'unsafe-inline'", # TODO Nonce in 8.0
20
20
  "worker-src 'self'",
21
21
  "base-uri 'self'"
22
22
  ].join("; ").freeze
@@ -49,13 +49,13 @@ module Sidekiq
49
49
 
50
50
  head "/" do
51
51
  # HEAD / is the cheapest heartbeat possible,
52
- # it hits Redis to ensure connectivity
53
- Sidekiq.redis { |c| c.llen("queue:default") }
54
- ""
52
+ # it hits Redis to ensure connectivity and returns
53
+ # the size of the default queue
54
+ Sidekiq.redis { |c| c.llen("queue:default") }.to_s
55
55
  end
56
56
 
57
57
  get "/" do
58
- @redis_info = redis_info.select { |k, v| REDIS_KEYS.include? k }
58
+ @redis_info = redis_info.slice(*REDIS_KEYS)
59
59
  days = (params["days"] || 30).to_i
60
60
  return halt(401) if days < 1 || days > 180
61
61
 
@@ -67,11 +67,15 @@ module Sidekiq
67
67
  end
68
68
 
69
69
  get "/metrics" do
70
+ x = params[:substr]
71
+ class_filter = (x.nil? || x == "") ? nil : Regexp.new(Regexp.escape(x), Regexp::IGNORECASE)
72
+
70
73
  q = Sidekiq::Metrics::Query.new
71
74
  @period = h((params[:period] || "")[0..1])
72
75
  @periods = METRICS_PERIODS
73
76
  minutes = @periods.fetch(@period, @periods.values.first)
74
- @query_result = q.top_jobs(minutes: minutes)
77
+ @query_result = q.top_jobs(minutes: minutes, class_filter: class_filter)
78
+
75
79
  erb(:metrics)
76
80
  end
77
81
 
@@ -153,9 +157,15 @@ module Sidekiq
153
157
  end
154
158
 
155
159
  get "/morgue" do
156
- @count = (params["count"] || 25).to_i
157
- (@current_page, @total_size, @dead) = page("dead", params["page"], @count, reverse: true)
158
- @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
160
+ x = params[:substr]
161
+
162
+ if x && x != ""
163
+ @dead = search(Sidekiq::DeadSet.new, x)
164
+ else
165
+ @count = (params["count"] || 25).to_i
166
+ (@current_page, @total_size, @dead) = page("dead", params["page"], @count, reverse: true)
167
+ @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
168
+ end
159
169
 
160
170
  erb(:morgue)
161
171
  end
@@ -174,7 +184,7 @@ module Sidekiq
174
184
  end
175
185
 
176
186
  post "/morgue" do
177
- redirect(request.path) unless params["key"]
187
+ redirect(request.path) unless url_params("key")
178
188
 
179
189
  params["key"].each do |key|
180
190
  job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
@@ -197,7 +207,7 @@ module Sidekiq
197
207
  end
198
208
 
199
209
  post "/morgue/:key" do
200
- key = route_params[:key]
210
+ key = route_params(:key)
201
211
  halt(404) unless key
202
212
 
203
213
  job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
@@ -207,9 +217,15 @@ module Sidekiq
207
217
  end
208
218
 
209
219
  get "/retries" do
210
- @count = (params["count"] || 25).to_i
211
- (@current_page, @total_size, @retries) = page("retry", params["page"], @count)
212
- @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
220
+ x = url_params("substr")
221
+
222
+ if x && x != ""
223
+ @retries = search(Sidekiq::RetrySet.new, x)
224
+ else
225
+ @count = (params["count"] || 25).to_i
226
+ (@current_page, @total_size, @retries) = page("retry", params["page"], @count)
227
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
228
+ end
213
229
 
214
230
  erb(:retries)
215
231
  end
@@ -262,9 +278,15 @@ module Sidekiq
262
278
  end
263
279
 
264
280
  get "/scheduled" do
265
- @count = (params["count"] || 25).to_i
266
- (@current_page, @total_size, @scheduled) = page("schedule", params["page"], @count)
267
- @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
281
+ x = params[:substr]
282
+
283
+ if x && x != ""
284
+ @scheduled = search(Sidekiq::ScheduledSet.new, x)
285
+ else
286
+ @count = (params["count"] || 25).to_i
287
+ (@current_page, @total_size, @scheduled) = page("schedule", params["page"], @count)
288
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
289
+ end
268
290
 
269
291
  erb(:scheduled)
270
292
  end
@@ -306,7 +328,7 @@ module Sidekiq
306
328
 
307
329
  get "/stats" do
308
330
  sidekiq_stats = Sidekiq::Stats.new
309
- redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
331
+ redis_stats = redis_info.slice(*REDIS_KEYS)
310
332
  json(
311
333
  sidekiq: {
312
334
  processed: sidekiq_stats.processed,
@@ -328,9 +350,21 @@ module Sidekiq
328
350
  json Sidekiq::Stats.new.queues
329
351
  end
330
352
 
353
+ post "/change_locale" do
354
+ locale = params["locale"]
355
+
356
+ match = available_locales.find { |available|
357
+ locale == available
358
+ }
359
+
360
+ session[:locale] = match if match
361
+
362
+ reload_page
363
+ end
364
+
331
365
  def call(env)
332
366
  action = self.class.match(env)
333
- return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
367
+ return [404, {Rack::CONTENT_TYPE => "text/plain", Web::X_CASCADE => "pass"}, ["Not Found"]] unless action
334
368
 
335
369
  app = @klass
336
370
  resp = catch(:halt) do
@@ -347,16 +381,21 @@ module Sidekiq
347
381
  else
348
382
  # rendered content goes here
349
383
  headers = {
350
- "content-type" => "text/html",
351
- "cache-control" => "private, no-store",
352
- "content-language" => action.locale,
353
- "content-security-policy" => CSP_HEADER
384
+ Rack::CONTENT_TYPE => "text/html",
385
+ Rack::CACHE_CONTROL => "private, no-store",
386
+ Web::CONTENT_LANGUAGE => action.locale,
387
+ Web::CONTENT_SECURITY_POLICY => process_csp(env, CSP_HEADER_TEMPLATE),
388
+ Web::X_CONTENT_TYPE_OPTIONS => "nosniff"
354
389
  }
355
390
  # we'll let Rack calculate Content-Length for us.
356
391
  [200, headers, [resp]]
357
392
  end
358
393
  end
359
394
 
395
+ def process_csp(env, input)
396
+ input.gsub("!placeholder!", env[:csp_nonce])
397
+ end
398
+
360
399
  def self.helpers(mod = nil, &block)
361
400
  if block
362
401
  WebAction.class_eval(&block)
@@ -27,7 +27,6 @@
27
27
  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
28
 
29
29
  require "securerandom"
30
- require "base64"
31
30
  require "rack/request"
32
31
 
33
32
  module Sidekiq
@@ -57,12 +56,12 @@ module Sidekiq
57
56
  end
58
57
 
59
58
  def logger(env)
60
- @logger ||= (env["rack.logger"] || ::Logger.new(env["rack.errors"]))
59
+ @logger ||= env["rack.logger"] || ::Logger.new(env["rack.errors"])
61
60
  end
62
61
 
63
62
  def deny(env)
64
63
  logger(env).warn "attack prevented by #{self.class}"
65
- [403, {"Content-Type" => "text/plain"}, ["Forbidden"]]
64
+ [403, {Rack::CONTENT_TYPE => "text/plain"}, ["Forbidden"]]
66
65
  end
67
66
 
68
67
  def session(env)
@@ -116,7 +115,7 @@ module Sidekiq
116
115
  sess = session(env)
117
116
  localtoken = sess[:csrf]
118
117
 
119
- # Checks that Rack::Session::Cookie actualy contains the csrf toekn
118
+ # Checks that Rack::Session::Cookie actually contains the csrf token
120
119
  return false if localtoken.nil?
121
120
 
122
121
  # Rotate the session token after every use
@@ -143,7 +142,7 @@ module Sidekiq
143
142
  one_time_pad = SecureRandom.random_bytes(token.length)
144
143
  encrypted_token = xor_byte_strings(one_time_pad, token)
145
144
  masked_token = one_time_pad + encrypted_token
146
- Base64.urlsafe_encode64(masked_token)
145
+ encode_token(masked_token)
147
146
  end
148
147
 
149
148
  # Essentially the inverse of +mask_token+.
@@ -168,8 +167,12 @@ module Sidekiq
168
167
  ::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
169
168
  end
170
169
 
170
+ def encode_token(token)
171
+ [token].pack("m0").tr("+/", "-_")
172
+ end
173
+
171
174
  def decode_token(token)
172
- Base64.urlsafe_decode64(token)
175
+ token.tr("-_", "+/").unpack1("m0")
173
176
  end
174
177
 
175
178
  def xor_byte_strings(s1, s2)