diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..9dccdf7a32 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,151 @@ +# The behavior of RuboCop can be controlled via the .rubocop.yml +# configuration file. It makes it possible to enable/disable +# certain cops (checks) and to alter their behavior if they accept +# any parameters. The file can be placed either in your home +# directory or in some project directory. +# +# RuboCop will start looking for the configuration file in the directory +# where the inspected file is and continue its way up to the root directory. +# +# See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md +AllCops: + TargetRubyVersion: 2.6 + SuggestExtensions: false + NewCops: enable + Exclude: + - 'test/**/*' + - 'rack-protection/**/*' + - 'sinatra-contrib/**/*' + - vendor/bundle/**/* + +Layout/ExtraSpacing: + AllowForAlignment: true + AllowBeforeTrailingComments: true + +# Temporary disable cops because warnings are fixed +Style/SingleLineMethods: + Enabled: false + +Style/MutableConstant: + Enabled: false + +Lint/AmbiguousBlockAssociation: + Enabled: false + +Style/CaseEquality: + Enabled: false + +Style/PerlBackrefs: + Enabled: false + +Style/Documentation: + Enabled: false + +Lint/IneffectiveAccessModifier: + Enabled: false + +Lint/RescueException: + Enabled: false + +Style/SpecialGlobalVars: + Enabled: false + +Bundler/DuplicatedGem: + Enabled: false + +Layout/HeredocIndentation: + Enabled: false + +Style/FormatStringToken: + Enabled: false + +Lint/UselessAccessModifier: + Enabled: false + +Style/ClassVars: + Enabled: false + +Lint/UselessAssignment: + Enabled: false + +Style/EmptyLiteral: + Enabled: false + +Layout/LineLength: + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false + +Lint/SuppressedException: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/BlockLength: + Enabled: false + +Metrics/ModuleLength: + Enabled: false + +Lint/AmbiguousRegexpLiteral: + Enabled: false + +Style/AccessModifierDeclarations: + Enabled: false + +Style/ClassAndModuleChildren: + Enabled: false + +Style/EvalWithLocation: + Enabled: false + +Lint/MissingSuper: + Enabled: false + +Style/MissingRespondToMissing: + Enabled: false + +Style/MixinUsage: + Enabled: false + +Style/MultilineTernaryOperator: + Enabled: false + +Style/StructInheritance: + Enabled: false + +Style/SymbolProc: + Enabled: false + +Style/IfUnlessModifier: + Enabled: false + +Style/OptionalBooleanParameter: + Enabled: false + +Style/DocumentDynamicEvalDefinition: + Enabled: false + +Lint/ToEnumArguments: + Enabled: false + +Naming/MethodParameterName: + Enabled: false + +Naming/AccessorMethodName: + Enabled: false + +Style/SlicingWithRange: + Enabled: false + diff --git a/Gemfile b/Gemfile index 3e9072a88c..783f91972c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Why use bundler? # Well, not all development dependencies install on all rubies. Moreover, `gem # install sinatra --development` doesn't work, as it will also try to install @@ -12,40 +14,40 @@ gemspec gem 'rake' rack_version = ENV['rack'].to_s -rack_version = nil if rack_version.empty? or rack_version == 'stable' -rack_version = {:github => 'rack/rack'} if rack_version == 'master' +rack_version = nil if rack_version.empty? || (rack_version == 'stable') +rack_version = { github: 'rack/rack' } if rack_version == 'master' gem 'rack', rack_version +gem 'minitest', '~> 5.0' gem 'rack-test', github: 'rack/rack-test' -gem "minitest", "~> 5.0" +gem 'rubocop', '~> 1.32.0', require: false gem 'yard' -gem "rack-protection", path: "rack-protection" -gem "sinatra-contrib", path: "sinatra-contrib" - +gem 'rack-protection', path: 'rack-protection' +gem 'sinatra-contrib', path: 'sinatra-contrib' -gem "activesupport", "~> 6.1" +gem 'activesupport', '~> 6.1' -gem 'redcarpet', platforms: [ :ruby ] -gem 'rdiscount', platforms: [ :ruby ] -gem 'puma' -gem 'falcon', '~> 0.40', platforms: [ :ruby ] -gem 'yajl-ruby', platforms: [ :ruby ] -gem 'nokogiri', '> 1.5.0' -gem 'rainbows', platforms: [ :mri ] # uses #fork -gem 'eventmachine' -gem 'slim', '~> 4' -gem 'rdoc' -gem 'kramdown' -gem 'markaby' gem 'asciidoctor' -gem 'liquid' -gem 'rabl' gem 'builder' +gem 'commonmarker', '~> 0.20.0', platforms: [:ruby] gem 'erubi' +gem 'eventmachine' +gem 'falcon', '~> 0.40', platforms: [:ruby] gem 'haml', '~> 5' -gem 'commonmarker', '~> 0.20.0', platforms: [ :ruby ] +gem 'kramdown' +gem 'liquid' +gem 'markaby' +gem 'nokogiri', '> 1.5.0' gem 'pandoc-ruby', '~> 2.0.2' +gem 'puma' +gem 'rabl' +gem 'rainbows', platforms: [:mri] # uses #fork +gem 'rdiscount', platforms: [:ruby] +gem 'rdoc' +gem 'redcarpet', platforms: [:ruby] gem 'simplecov', require: false +gem 'slim', '~> 4' +gem 'yajl-ruby', platforms: [:ruby] -gem 'json', platforms: [ :jruby, :mri ] +gem 'json', platforms: %i[jruby mri] diff --git a/Rakefile b/Rakefile index 3fb9707f81..7bbd08ca7a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,15 +1,17 @@ +# frozen_string_literal: true + require 'rake/clean' require 'rake/testtask' require 'fileutils' require 'date' -task :default => :test -task :spec => :test +task default: :test +task spec: :test -CLEAN.include "**/*.rbc" +CLEAN.include '**/*.rbc' def source_version - @source_version ||= File.read(File.expand_path("VERSION", __dir__)).strip + @source_version ||= File.read(File.expand_path('VERSION', __dir__)).strip end def prev_feature @@ -17,7 +19,8 @@ def prev_feature end def prev_version - return prev_feature + '.0' if source_version.end_with? '.0' + return "#{prev_feature}.0" if source_version.end_with? '.0' + source_version.gsub(/\d+$/) { |s| s.to_i - 1 } end @@ -29,13 +32,15 @@ Rake::TestTask.new(:test) do |t| t.warning = true end -Rake::TestTask.new(:"test:core") do |t| - core_tests = %w[base delegator encoding extensions filter - helpers mapped_error middleware rdoc - readme request response result route_added_hook - routing server settings sinatra static templates] - t.test_files = core_tests.map {|n| "test/#{n}_test.rb"} - t.ruby_opts = ["-r rubygems"] if defined? Gem +Rake::TestTask.new(:'test:core') do |t| + core_tests = %w[ + base delegator encoding extensions filter + helpers mapped_error middleware rdoc + readme request response result route_added_hook + routing server settings sinatra static templates + ] + t.test_files = core_tests.map { |n| "test/#{n}_test.rb" } + t.ruby_opts = ['-r rubygems'] if defined? Gem t.warning = true end @@ -44,7 +49,7 @@ end namespace :test do desc 'Measures test coverage' task :coverage do - rm_f "coverage" + rm_f 'coverage' ENV['COVERAGE'] = '1' Rake::Task['test'].invoke end @@ -53,26 +58,26 @@ end # Website ============================================================= desc 'Generate RDoc under doc/api' -task 'doc' => ['doc:api'] -task('doc:api') { sh "yardoc -o doc/api" } +task 'doc' => ['doc:api'] +task('doc:api') { sh 'yardoc -o doc/api' } CLEAN.include 'doc/api' # README =============================================================== -task :add_template, [:name] do |t, args| +task :add_template, [:name] do |_t, args| Dir.glob('README.*') do |file| code = File.read(file) if code =~ /^===.*#{args.name.capitalize}/ puts "Already covered in #{file}" else - template = code[/===[^\n]*Liquid.*index\.liquid<\/tt>[^\n]*/m] - if !template - puts "Liquid not found in #{file}" - else + template = code[%r{===[^\n]*Liquid.*index\.liquid[^\n]*}m] + if template puts "Adding section to #{file}" template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase) code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1" - File.open(file, "w") { |f| f << code } + File.open(file, 'w') { |f| f << code } + else + puts "Liquid not found in #{file}" end end end @@ -80,29 +85,31 @@ end # Thanks in announcement =============================================== -team = ["Ryan Tomayko", "Blake Mizerany", "Simon Rozet", "Konstantin Haase", "Zachary Scott"] -desc "list of contributors" -task :thanks, ['release:all', :backports] do |t, a| - a.with_defaults :release => "#{prev_version}..HEAD", - :backports => "#{prev_feature}.0..#{prev_feature}.x" +team = ['Ryan Tomayko', 'Blake Mizerany', 'Simon Rozet', 'Konstantin Haase', 'Zachary Scott'] +desc 'list of contributors' +task :thanks, ['release:all', :backports] do |_t, a| + a.with_defaults release: "#{prev_version}..HEAD", + backports: "#{prev_feature}.0..#{prev_feature}.x" + included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.map { |l| l.force_encoding('binary') } excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.map { |l| l.force_encoding('binary') } commits = (included - excluded).group_by { |c| c[/^[^\t]+/] } authors = commits.keys.sort_by { |n| - commits[n].size } - team - puts authors[0..-2].join(', ') << " and " << authors.last, - "(based on commits included in #{a.release}, but not in #{a.backports})" + puts authors[0..-2].join(', ') << ' and ' << authors.last, + "(based on commits included in #{a.release}, but not in #{a.backports})" end -desc "list of authors" -task :authors, [:commit_range, :format, :sep] do |t, a| - a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all' +desc 'list of authors' +task :authors, [:commit_range, :format, :sep] do |_t, a| + a.with_defaults format: '%s (%d)', sep: ', ', commit_range: '--all' authors = Hash.new(0) - blake = "Blake Mizerany" + blake = 'Blake Mizerany' overall = 0 mapping = { - "blake.mizerany@gmail.com" => blake, "bmizerany" => blake, - "a_user@mac.com" => blake, "ichverstehe" => "Harry Vangberg", - "Wu Jiang (nouse)" => "Wu Jiang" } + 'blake.mizerany@gmail.com' => blake, 'bmizerany' => blake, + 'a_user@mac.com' => blake, 'ichverstehe' => 'Harry Vangberg', + 'Wu Jiang (nouse)' => 'Wu Jiang' + } `git shortlog -s #{a.commit_range}`.lines.map do |line| line = line.force_encoding 'binary' if line.respond_to? :force_encoding num, name = line.split("\t", 2).map(&:strip) @@ -110,18 +117,18 @@ task :authors, [:commit_range, :format, :sep] do |t, a| overall += num.to_i end puts "#{overall} commits by #{authors.count} authors:" - puts authors.sort_by { |n,c| -c }.map { |e| a.format % e }.join(a.sep) + puts authors.sort_by { |_n, c| -c }.map { |e| a.format % e }.join(a.sep) end -desc "generates TOC" -task :toc, [:readme] do |t, a| - a.with_defaults :readme => 'README.md' +desc 'generates TOC' +task :toc, [:readme] do |_t, a| + a.with_defaults readme: 'README.md' def self.link(title) title.downcase.gsub(/(?!-)\W /, '-').gsub(' ', '-').gsub(/(?!-)\W/, '') end - puts "* [Sinatra](#sinatra)" + puts '* [Sinatra](#sinatra)' title = Regexp.new('(?<=\* )(.*)') # so Ruby 1.8 doesn't complain File.binread(a.readme).scan(/^##.*/) do |line| puts line.gsub(/#(?=#)/, ' ').gsub('#', '*').gsub(title) { "[#{$1}](##{link($1)})" } @@ -132,12 +139,12 @@ end if defined?(Gem) GEMS_AND_ROOT_DIRECTORIES = { - "sinatra" => ".", - "sinatra-contrib" => "./sinatra-contrib", - "rack-protection" => "./rack-protection" - } + 'sinatra' => '.', + 'sinatra-contrib' => './sinatra-contrib', + 'rack-protection' => './rack-protection' + }.freeze - def package(gem, ext='') + def package(gem, ext = '') "pkg/#{gem}-#{source_version}" + ext end @@ -145,12 +152,12 @@ if defined?(Gem) CLOBBER.include('pkg') GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| - file package(gem, '.gem') => ["pkg/", "#{directory + '/' + gem}.gemspec"] do |f| + file package(gem, '.gem') => ['pkg/', "#{"#{directory}/#{gem}"}.gemspec"] do |f| sh "cd #{directory} && gem build #{gem}.gemspec" - mv directory + "/" + File.basename(f.name), f.name + mv "#{directory}/#{File.basename(f.name)}", f.name end - file package(gem, '.tar.gz') => ["pkg/"] do |f| + file package(gem, '.tar.gz') => ['pkg/'] do |f| sh <<-SH git archive \ --prefix=#{gem}-#{source_version}/ \ @@ -161,29 +168,29 @@ if defined?(Gem) end namespace :package do - GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + GEMS_AND_ROOT_DIRECTORIES.each do |gem, _directory| desc "Build #{gem} packages" task gem => %w[.gem .tar.gz].map { |e| package(gem, e) } end - desc "Build all packages" - task :all => GEMS_AND_ROOT_DIRECTORIES.keys + desc 'Build all packages' + task all: GEMS_AND_ROOT_DIRECTORIES.keys end namespace :install do - GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + GEMS_AND_ROOT_DIRECTORIES.each do |gem, _directory| desc "Build and install #{gem} as local gem" task gem => package(gem, '.gem') do sh "gem install #{package(gem, '.gem')}" end end - desc "Build and install all of the gems as local gems" - task :all => GEMS_AND_ROOT_DIRECTORIES.keys + desc 'Build and install all of the gems as local gems' + task all: GEMS_AND_ROOT_DIRECTORIES.keys end namespace :release do - GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + GEMS_AND_ROOT_DIRECTORIES.each do |gem, _directory| desc "Release #{gem} as a package" task gem => "package:#{gem}" do sh <<-SH @@ -193,7 +200,7 @@ if defined?(Gem) end end - desc "Commits the version to github repository" + desc 'Commits the version to github repository' task :commit_version do %w[ lib/sinatra @@ -212,7 +219,7 @@ if defined?(Gem) SH end - desc "Release all gems as packages" - task :all => [:test, :commit_version] + GEMS_AND_ROOT_DIRECTORIES.keys + desc 'Release all gems as packages' + task all: %i[test commit_version] + GEMS_AND_ROOT_DIRECTORIES.keys end end diff --git a/examples/chat.rb b/examples/chat.rb index 4d03bd1c42..9e5f8f895d 100755 --- a/examples/chat.rb +++ b/examples/chat.rb @@ -1,16 +1,18 @@ #!/usr/bin/env ruby -I ../lib -I lib -# coding: utf-8 +# frozen_string_literal: true + require_relative 'rainbows' + require 'sinatra' set :server, :rainbows connections = [] get '/' do halt erb(:login) unless params[:user] - erb :chat, :locals => { :user => params[:user].gsub(/\W/, '') } + erb :chat, locals: { user: params[:user].gsub(/\W/, '') } end -get '/stream', :provides => 'text/event-stream' do +get '/stream', provides: 'text/event-stream' do stream :keep_open do |out| connections << out out.callback { connections.delete(out) } diff --git a/examples/rainbows.rb b/examples/rainbows.rb index 895e19a2be..4dab26444e 100644 --- a/examples/rainbows.rb +++ b/examples/rainbows.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rainbows' module Rack @@ -8,7 +10,7 @@ def self.run(app, **options) listeners: ["#{options[:Host]}:#{options[:Port]}"], worker_processes: 1, timeout: 30, - config_file: ::File.expand_path('rainbows.conf', __dir__), + config_file: ::File.expand_path('rainbows.conf', __dir__) } ::Rainbows::HttpServer.new(app, rainbows_options).start.join diff --git a/examples/simple.rb b/examples/simple.rb index 2697f94bf2..31b3fcf96a 100755 --- a/examples/simple.rb +++ b/examples/simple.rb @@ -1,3 +1,5 @@ #!/usr/bin/env ruby -I ../lib -I lib +# frozen_string_literal: true + require 'sinatra' get('/') { 'this is a simple app' } diff --git a/examples/stream.ru b/examples/stream.ru index 74af0a6148..ee1615d65e 100644 --- a/examples/stream.ru +++ b/examples/stream.ru @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # this example does *not* work properly with WEBrick # # run *one* of these: diff --git a/lib/sinatra.rb b/lib/sinatra.rb index 68261380cb..fabdc41b57 100644 --- a/lib/sinatra.rb +++ b/lib/sinatra.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sinatra/main' enable :inline_templates diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index 4947e6b15b..63a90cd4e4 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -1,4 +1,3 @@ -# coding: utf-8 # frozen_string_literal: true # external dependencies @@ -10,7 +9,6 @@ require 'mustermann/regular' # stdlib dependencies -require 'thread' require 'time' require 'uri' @@ -23,19 +21,20 @@ module Sinatra # The request object. See Rack::Request for more info: # http://rubydoc.info/github/rack/rack/master/Rack/Request class Request < Rack::Request - HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/ - HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/ + HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze + HEADER_VALUE_WITH_PARAMS = %r{(?:(?:\w+|\*)/(?:\w+(?:\.|-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*}.freeze # Returns an array of acceptable media types for the response def accept - @env['sinatra.accept'] ||= begin - if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != '' - @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS). - map! { |e| AcceptEntry.new(e) }.sort - else - [AcceptEntry.new('*/*')] - end - end + @env['sinatra.accept'] ||= if @env.include?('HTTP_ACCEPT') && (@env['HTTP_ACCEPT'].to_s != '') + @env['HTTP_ACCEPT'] + .to_s + .scan(HEADER_VALUE_WITH_PARAMS) + .map! { |e| AcceptEntry.new(e) } + .sort + else + [AcceptEntry.new('*/*')] + end end def accept?(type) @@ -44,8 +43,10 @@ def accept?(type) def preferred_type(*types) return accept.first if types.empty? + types.flatten! return types.first if accept.empty? + accept.detect do |accept_header| type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) } return type if type @@ -55,23 +56,23 @@ def preferred_type(*types) alias secure? ssl? def forwarded? - @env.include? "HTTP_X_FORWARDED_HOST" + @env.include? 'HTTP_X_FORWARDED_HOST' end def safe? - get? or head? or options? or trace? + get? || head? || options? || trace? end def idempotent? - safe? or put? or delete? or link? or unlink? + safe? || put? || delete? || link? || unlink? end def link? - request_method == "LINK" + request_method == 'LINK' end def unlink? - request_method == "UNLINK" + request_method == 'UNLINK' end def params @@ -95,17 +96,17 @@ def initialize(entry) @entry = entry @type = entry[/[^;]+/].delete(' ') - @params = Hash[params] + @params = params.to_h @q = @params.delete('q') { 1.0 }.to_f end def <=>(other) - other.priority <=> self.priority + other.priority <=> priority end def priority # We sort in descending order; better matches should be higher. - [ @q, -@type.count('*'), @params.size ] + [@q, -@type.count('*'), @params.size] end def to_str @@ -117,7 +118,7 @@ def to_s(full = false) end def respond_to?(*args) - super or to_str.respond_to?(*args) + super || to_str.respond_to?(*args) end def method_missing(*args, &block) @@ -136,7 +137,7 @@ def initialize(entry) end @type = entry[/[^;]+/].delete(' ') - @params = Hash[params] + @params = params.to_h end def accepts?(entry) @@ -150,7 +151,7 @@ def to_str def matches_params?(params) return true if @params.empty? - params.all? { |k,v| !@params.has_key?(k) || @params[k] == v } + params.all? { |k, v| !@params.key?(k) || @params[k] == v } end end end @@ -160,7 +161,7 @@ def matches_params?(params) # http://rubydoc.info/github/rack/rack/master/Rack/Response # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers class Response < Rack::Response - DROP_BODY_RESPONSES = [204, 304] + DROP_BODY_RESPONSES = [204, 304].freeze def body=(value) value = value.body while Rack::Response === value @@ -175,8 +176,8 @@ def finish result = body if drop_content_info? - headers.delete "Content-Length" - headers.delete "Content-Type" + headers.delete 'Content-Length' + headers.delete 'Content-Type' end if drop_body? @@ -187,7 +188,7 @@ def finish if calculate_content_length? # if some other code has already set Content-Length, don't muck with it # currently, this would be the static file-handler - headers["Content-Length"] = body.map(&:bytesize).reduce(0, :+).to_s + headers['Content-Length'] = body.map(&:bytesize).reduce(0, :+).to_s end [status, headers, result] @@ -196,11 +197,11 @@ def finish private def calculate_content_length? - headers["Content-Type"] and not headers["Content-Length"] and Array === body + headers['Content-Type'] && !headers['Content-Length'] && (Array === body) end def drop_content_info? - informational? or drop_body? + informational? || drop_body? end def drop_body? @@ -215,8 +216,10 @@ def drop_body? # still be able to run. class ExtendedRack < Struct.new(:app) def call(env) - result, callback = app.call(env), env['async.callback'] - return result unless callback and async?(*result) + result = app.call(env) + callback = env['async.callback'] + return result unless callback && async?(*result) + after_response { callback.call result } setup_close(env, *result) throw :async @@ -224,20 +227,23 @@ def call(env) private - def setup_close(env, status, headers, body) - return unless body.respond_to? :close and env.include? 'async.close' + def setup_close(env, _status, _headers, body) + return unless body.respond_to?(:close) && env.include?('async.close') + env['async.close'].callback { body.close } env['async.close'].errback { body.close } end def after_response(&block) - raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine + raise NotImplementedError, 'only supports EventMachine at the moment' unless defined? EventMachine + EventMachine.next_tick(&block) end - def async?(status, headers, body) + def async?(status, _headers, body) return true if status == -1 - body.respond_to? :callback and body.respond_to? :errback + + body.respond_to?(:callback) && body.respond_to?(:errback) end end @@ -249,7 +255,7 @@ def call(env) end superclass.class_eval do - alias call_without_check call unless method_defined? :call_without_check + alias_method :call_without_check, :call unless method_defined? :call_without_check def call(env) env['sinatra.commonlogger'] = true call_without_check(env) @@ -257,14 +263,14 @@ def call(env) end end - class Error < StandardError #:nodoc: + class Error < StandardError # :nodoc: end - class BadRequest < Error #:nodoc: + class BadRequest < Error # :nodoc: def http_status; 400 end end - class NotFound < Error #:nodoc: + class NotFound < Error # :nodoc: def http_status; 404 end end @@ -296,7 +302,7 @@ def block.each; yield(call) end # Halt processing and redirect to the URI provided. def redirect(uri, *args) - if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET' + if (env['HTTP_VERSION'] == 'HTTP/1.1') && (env['REQUEST_METHOD'] != 'GET') status 303 else status 302 @@ -311,18 +317,19 @@ def redirect(uri, *args) # Generates the absolute URI for a given path in the app. # Takes Rack routers and reverse proxies into account. def uri(addr = nil, absolute = true, add_script_name = true) - return addr if addr =~ /\A[a-z][a-z0-9\+\.\-]*:/i + return addr if addr =~ /\A[a-z][a-z0-9+.\-]*:/i + uri = [host = String.new] if absolute host << "http#{'s' if request.secure?}://" - if request.forwarded? or request.port != (request.secure? ? 443 : 80) - host << request.host_with_port - else - host << request.host - end + host << if request.forwarded? || (request.port != (request.secure? ? 443 : 80)) + request.host_with_port + else + request.host + end end uri << request.script_name.to_s if add_script_name - uri << (addr ? addr : request.path_info).to_s + uri << (addr || request.path_info).to_s File.join uri end @@ -331,7 +338,10 @@ def uri(addr = nil, absolute = true, add_script_name = true) # Halt processing and return the error status provided. def error(code, body = nil) - code, body = 500, code.to_str if code.respond_to? :to_str + if code.respond_to? :to_str + body = code.to_str + code = 500 + end response.body = body unless body.nil? halt code end @@ -366,11 +376,13 @@ def mime_type(type) # extension. def content_type(type = nil, params = {}) return response['Content-Type'] unless type + default = params.delete :default mime_type = mime_type(type) || default - fail "Unknown media type: %p" % type if mime_type.nil? + raise format('Unknown media type: %p', type) if mime_type.nil? + mime_type = mime_type.dup - unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type } + unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) } params[:charset] = params.delete('charset') || settings.default_encoding end params.delete :charset if mime_type.include? 'charset' @@ -388,23 +400,23 @@ def content_type(type = nil, params = {}) # instructing the user agents to prompt to save. def attachment(filename = nil, disposition = :attachment) response['Content-Disposition'] = disposition.to_s.dup - if filename - params = '; filename="%s"' % File.basename(filename) - response['Content-Disposition'] << params - ext = File.extname(filename) - content_type(ext) unless response['Content-Type'] or ext.empty? - end + return unless filename + + params = format('; filename="%s"', File.basename(filename)) + response['Content-Disposition'] << params + ext = File.extname(filename) + content_type(ext) unless response['Content-Type'] || ext.empty? end # Use the contents of the file at +path+ as the response body. def send_file(path, opts = {}) - if opts[:type] or not response['Content-Type'] - content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' + if opts[:type] || !response['Content-Type'] + content_type opts[:type] || File.extname(path), default: 'application/octet-stream' end disposition = opts[:disposition] filename = opts[:filename] - disposition = :attachment if disposition.nil? and filename + disposition = :attachment if disposition.nil? && filename filename = path if filename.nil? attachment(filename, disposition) if disposition @@ -413,7 +425,7 @@ def send_file(path, opts = {}) file = Rack::File.new(File.dirname(settings.app_file)) result = file.serving(request, path) - result[1].each { |k,v| headers[k] ||= v } + result[1].each { |k, v| headers[k] ||= v } headers['Content-Length'] = result[1]['Content-Length'] opts[:status] &&= Integer(opts[:status]) halt (opts[:status] || result[0]), result[2] @@ -434,12 +446,16 @@ def self.schedule(*) yield end def self.defer(*) yield end def initialize(scheduler = self.class, keep_open = false, &back) - @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open - @callbacks, @closed = [], false + @back = back.to_proc + @scheduler = scheduler + @keep_open = keep_open + @callbacks = [] + @closed = false end def close return if closed? + @closed = true @scheduler.schedule { @callbacks.each { |c| c.call } } end @@ -463,6 +479,7 @@ def <<(data) def callback(&block) return yield if closed? + @callbacks << block end @@ -496,18 +513,18 @@ def stream(keep_open = false) # See RFC 2616 / 14.9 for more on standard cache control directives: # http://tools.ietf.org/html/rfc2616#section-14.9.1 def cache_control(*values) - if values.last.kind_of?(Hash) + if values.last.is_a?(Hash) hash = values.pop - hash.reject! { |k, v| v == false } + hash.reject! { |_k, v| v == false } hash.reject! { |k, v| values << k if v == true } else hash = {} end - values.map! { |value| value.to_s.tr('_','-') } + values.map! { |value| value.to_s.tr('_', '-') } hash.each do |key, value| key = key.to_s.tr('_', '-') - value = value.to_i if ['max-age', 's-maxage'].include? key + value = value.to_i if %w[max-age s-maxage].include? key values << "#{key}=#{value}" end @@ -524,7 +541,7 @@ def cache_control(*values) # => Expires: Mon, 08 Jun 2009 08:50:17 GMT # def expires(amount, *values) - values << {} unless values.last.kind_of?(Hash) + values << {} unless values.last.is_a?(Hash) if amount.is_a? Integer time = Time.now + amount.to_i @@ -534,7 +551,7 @@ def expires(amount, *values) max_age = time - Time.now end - values.last.merge!(:max_age => max_age) { |key, v1, v2| v1 || v2 } + values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 } cache_control(*values) response['Expires'] = time.httpdate @@ -549,17 +566,18 @@ def expires(amount, *values) # with a '304 Not Modified' response. def last_modified(time) return unless time + time = time_for time response['Last-Modified'] = time.httpdate return if env['HTTP_IF_NONE_MATCH'] - if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] + if (status == 200) && env['HTTP_IF_MODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i halt 304 if since >= time.to_i end - if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] + if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i halt 412 if since < time.to_i @@ -567,7 +585,7 @@ def last_modified(time) rescue ArgumentError end - ETAG_KINDS = [:strong, :weak] + ETAG_KINDS = %i[strong weak].freeze # Set the response entity tag (HTTP 'ETag' header) and halt if conditional # GET matches. The +value+ argument is an identifier that uniquely # identifies the current version of the resource. The +kind+ argument @@ -579,27 +597,31 @@ def last_modified(time) # GET or HEAD, a '304 Not Modified' response is sent. def etag(value, options = {}) # Before touching this code, please double check RFC 2616 14.24 and 14.26. - options = {:kind => options} unless Hash === options + options = { kind: options } unless Hash === options kind = options[:kind] || :strong new_resource = options.fetch(:new_resource) { request.post? } unless ETAG_KINDS.include?(kind) - raise ArgumentError, ":strong or :weak expected" + raise ArgumentError, ':strong or :weak expected' end - value = '"%s"' % value + value = format('"%s"', value) value = "W/#{value}" if kind == :weak response['ETag'] = value - if success? or status == 304 - if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource - halt(request.safe? ? 304 : 412) - end + return unless success? || status == 304 - if env['HTTP_IF_MATCH'] - halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource - end + if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource) + halt(request.safe? ? 304 : 412) + end + + if env['HTTP_IF_MATCH'] + return if etag_matches?(env['HTTP_IF_MATCH'], new_resource) + + halt 412 end + + nil end # Sugar for redirect (example: redirect back) @@ -652,8 +674,8 @@ def time_for(value) else value.to_time end - rescue ArgumentError => boom - raise boom + rescue ArgumentError => e + raise e rescue Exception raise ArgumentError, "unable to convert #{value.inspect} to a Time object" end @@ -663,11 +685,13 @@ def time_for(value) # Helper method checking if a ETag value list includes the current ETag. def etag_matches?(list, new_resource = request.post?) return !new_resource if list == '*' + list.to_s.split(/\s*,\s*/).include? response['ETag'] end def with_params(temp_params) - original, @params = @params, temp_params + original = @params + @params = temp_params yield ensure @params = original if original @@ -770,24 +794,27 @@ def find_template(views, name, engine) # logic shared between builder and nokogiri def render_ruby(engine, template, options = {}, locals = {}, &block) - options, template = template, nil if template.is_a?(Hash) - template = Proc.new { block } if template.nil? + if template.is_a?(Hash) + options = template + template = nil + end + template = proc { block } if template.nil? render engine, template, options, locals end def render(engine, data, options = {}, locals = {}, &block) # merge app-level options engine_options = settings.respond_to?(engine) ? settings.send(engine) : {} - options.merge!(engine_options) { |key, v1, v2| v1 } + options.merge!(engine_options) { |_key, v1, _v2| v1 } # extract generic options locals = options.delete(:locals) || locals || {} - views = options.delete(:views) || settings.views || "./views" + views = options.delete(:views) || settings.views || './views' layout = options[:layout] layout = false if layout.nil? && options.include?(:layout) eat_errors = layout.nil? - layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false) - layout = @default_layout if layout.nil? or layout == true + layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false) + layout = @default_layout if layout.nil? || (layout == true) layout_options = options.delete(:layout_options) || {} content_type = options.delete(:default_content_type) content_type = options.delete(:content_type) || content_type @@ -812,8 +839,9 @@ def render(engine, data, options = {}, locals = {}, &block) # render layout if layout - options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope). - merge!(layout_options) + extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope } + options = options.merge(extra_options).merge!(layout_options) + catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } } end @@ -838,12 +866,13 @@ def compile_template(engine, data, options, views) @preferred_extension = engine.to_s find_template(views, data, template) do |file| path ||= file # keep the initial path rather than the last one - if found = File.exist?(file) + found = File.exist?(file) + if found path = file break end end - throw :layout_missing if eat_errors and not found + throw :layout_missing if eat_errors && !found template.new(path, 1, options) end end @@ -860,7 +889,8 @@ def compile_template(engine, data, options, views) def compile_block_template(template, options, &body) first_location = caller_locations.first - path, line = first_location.path, first_location.lineno + path = first_location.path + line = first_location.lineno path = options[:path] || path line = options[:line] || line template.new(path, line.to_i, options, &body) @@ -878,7 +908,7 @@ class Base attr_accessor :app, :env, :request, :response, :params attr_reader :template_cache - def initialize(app = nil, **kwargs) + def initialize(app = nil, **_kwargs) super() @app = app @template_cache = Tilt::Cache.new @@ -905,7 +935,7 @@ def call!(env) # :nodoc: unless @response['Content-Type'] if Array === body && body[0].respond_to?(:content_type) content_type body[0].content_type - elsif default = settings.default_content_type + elsif (default = settings.default_content_type) content_type default end end @@ -939,7 +969,8 @@ def pass(&block) # Forward the request to the downstream app -- middleware only. def forward - fail "downstream app not set" unless @app.respond_to? :call + raise 'downstream app not set' unless @app.respond_to? :call + status, headers, body = @app.call env @response.status = status @response.body = body @@ -961,18 +992,18 @@ def filter!(type, base = settings, &block) # Run routes defined on the class and all superclasses. def route!(base = settings, pass_block = nil) - if routes = base.routes[@request.request_method] - routes.each do |pattern, conditions, block| - response.delete_header('Content-Type') unless @pinned_response + routes = base.routes[@request.request_method] - returned_pass_block = process_route(pattern, conditions) do |*args| - env['sinatra.route'] = "#{@request.request_method} #{pattern}" - route_eval { block[*args] } - end + routes&.each do |pattern, conditions, block| + response.delete_header('Content-Type') unless @pinned_response - # don't wipe out pass_block in superclass - pass_block = returned_pass_block if returned_pass_block + returned_pass_block = process_route(pattern, conditions) do |*args| + env['sinatra.route'] = "#{@request.request_method} #{pattern}" + route_eval { block[*args] } end + + # don't wipe out pass_block in superclass + pass_block = returned_pass_block if returned_pass_block end # Run routes defined in superclass. @@ -996,15 +1027,17 @@ def route_eval # Returns pass block. def process_route(pattern, conditions, block = nil, values = []) route = @request.path_info - route = '/' if route.empty? and not settings.empty_path_info? + route = '/' if route.empty? && !settings.empty_path_info? route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/') - return unless params = pattern.params(route) - params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes + params = pattern.params(route) + return unless params + + params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes force_encoding(params) - @params = @params.merge(params) { |k, v1, v2| v2 || v1 } if params.any? + @params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any? - regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} ) + regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) }) if regexp_exists captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c } values += captures @@ -1017,7 +1050,7 @@ def process_route(pattern, conditions, block = nil, values = []) conditions.each { |c| throw :pass if c.bind(self).call == false } block ? block[self, values] : yield(self, values) end - rescue + rescue StandardError @env['sinatra.error.params'] = @params raise ensure @@ -1031,35 +1064,35 @@ def process_route(pattern, conditions, block = nil, values = []) # a NotFound exception. Subclasses can override this method to perform # custom route miss logic. def route_missing - if @app - forward - else - raise NotFound - end + raise NotFound unless @app + + forward end # Attempt to serve static files from public directory. Throws :halt when # a matching file is found, returns nil otherwise. def static!(options = {}) return if (public_dir = settings.public_folder).nil? + path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}" return unless valid_path?(path) path = File.expand_path(path) - return unless path.start_with?(File.expand_path(public_dir) + '/') + return unless path.start_with?("#{File.expand_path(public_dir)}/") + return unless File.file?(path) env['sinatra.static_file'] = path cache_control(*settings.static_cache_control) if settings.static_cache_control? - send_file path, options.merge(:disposition => nil) + send_file path, options.merge(disposition: nil) end # Run the block with 'throw :halt' support and apply result to the response. - def invoke - res = catch(:halt) { yield } + def invoke(&block) + res = catch(:halt, &block) - res = [res] if Integer === res or String === res - if Array === res and Integer === res.first + res = [res] if (Integer === res) || (String === res) + if (Array === res) && (Integer === res.first) res = res.dup status(res.shift) body(res.pop) @@ -1075,6 +1108,7 @@ def dispatch! # Avoid passing frozen string in force_encoding @params.merge!(@request.params).each do |key, val| next unless val.respond_to?(:force_encoding) + val = val.dup if val.frozen? @params[key] = force_encoding(val) end @@ -1086,42 +1120,43 @@ def dispatch! end route! end - rescue ::Exception => boom - invoke { handle_exception!(boom) } + rescue ::Exception => e + invoke { handle_exception!(e) } ensure begin filter! :after unless env['sinatra.static_file'] - rescue ::Exception => boom - invoke { handle_exception!(boom) } unless @env['sinatra.error'] + rescue ::Exception => e + invoke { handle_exception!(e) } unless @env['sinatra.error'] end end # Error handling during requests. def handle_exception!(boom) - if error_params = @env['sinatra.error.params'] - @params = @params.merge(error_params) - end + error_params = @env['sinatra.error.params'] + + @params = @params.merge(error_params) if error_params + @env['sinatra.error'] = boom - http_status = if boom.kind_of? Sinatra::Error - if boom.respond_to? :http_status - boom.http_status - elsif settings.use_code? && boom.respond_to?(:code) - boom.code - end - end + http_status = if boom.is_a? Sinatra::Error + if boom.respond_to? :http_status + boom.http_status + elsif settings.use_code? && boom.respond_to?(:code) + boom.code + end + end - http_status = 500 unless http_status && http_status.between?(400, 599) + http_status = 500 unless http_status&.between?(400, 599) status(http_status) if server_error? dump_errors! boom if settings.dump_errors? - raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler + raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler) elsif not_found? headers['X-Cascade'] = 'pass' if settings.x_cascade? end - if res = error_block!(boom.class, boom) || error_block!(status, boom) + if (res = error_block!(boom.class, boom) || error_block!(status, boom)) return res end @@ -1130,12 +1165,14 @@ def handle_exception!(boom) body Rack::Utils.escape_html(boom.message) else content_type 'text/html' - body '

' + (not_found? ? 'Not Found' : 'Bad Request') + '

' + body "

#{not_found? ? 'Not Found' : 'Bad Request'}

" end end return unless server_error? - raise boom if settings.raise_errors? or settings.show_exceptions? + + raise boom if settings.raise_errors? || settings.show_exceptions? + error_block! Exception, boom end @@ -1143,7 +1180,10 @@ def handle_exception!(boom) def error_block!(key, *block_params) base = settings while base.respond_to?(:errors) - next base = base.superclass unless args_array = base.errors[key] + args_array = base.errors[key] + + next base = base.superclass unless args_array + args_array.reverse_each do |args| first = args == args_array.first args += [block_params] @@ -1151,25 +1191,26 @@ def error_block!(key, *block_params) return resp unless resp.nil? && !first end end - return false unless key.respond_to? :superclass and key.superclass < Exception + return false unless key.respond_to?(:superclass) && (key.superclass < Exception) + error_block!(key.superclass, *block_params) end def dump_errors!(boom) - msg = ["#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") + msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") @env['rack.errors'].puts(msg) end class << self CALLERS_TO_IGNORE = [ # :nodoc: - /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code - /lib\/tilt.*\.rb$/, # all tilt code + %r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code + %r{lib/tilt.*\.rb$}, # all tilt code /^\(.*\)$/, # generated code - /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks + %r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks /active_support/, # active_support require hacks - /bundler(\/(?:runtime|inline))?\.rb/, # bundler require hacks + %r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks /= 1.9.2 - ] + ].freeze attr_reader :routes, :filters, :templates, :errors @@ -1178,17 +1219,17 @@ class << self def reset! @conditions = [] @routes = {} - @filters = {:before => [], :after => []} + @filters = { before: [], after: [] } @errors = {} @middleware = [] @prototype = nil @extensions = [] - if superclass.respond_to?(:templates) - @templates = Hash.new { |hash, key| superclass.templates[key] } - else - @templates = {} - end + @templates = if superclass.respond_to?(:templates) + Hash.new { |_hash, key| superclass.templates[key] } + else + {} + end end # Extension modules registered on this class and all superclasses. @@ -1212,16 +1253,21 @@ def middleware # Sets an option to the given value. If the value is a proc, # the proc will be called every time the option is accessed. def set(option, value = (not_set = true), ignore_setter = false, &block) - raise ArgumentError if block and !not_set - value, not_set = block, false if block + raise ArgumentError if block && !not_set + + if block + value = block + not_set = false + end if not_set raise ArgumentError unless option.respond_to?(:each) - option.each { |k,v| set(k, v) } + + option.each { |k, v| set(k, v) } return self end - if respond_to?("#{option}=") and not ignore_setter + if respond_to?("#{option}=") && !ignore_setter return __send__("#{option}=", value) end @@ -1260,7 +1306,7 @@ def disable(*opts) # class, or an HTTP status code to specify which errors should be # handled. def error(*codes, &block) - args = compile! "ERROR", /.*/, block + args = compile! 'ERROR', /.*/, block codes = codes.flat_map(&method(:Array)) codes << Exception if codes.empty? codes << Sinatra::NotFound if codes.include?(404) @@ -1286,7 +1332,7 @@ def layout(name = :layout, &block) # Load embedded templates from the file; uses the caller's __FILE__ # when no file is specified. def inline_templates=(file = nil) - file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file + file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true begin io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file) @@ -1295,23 +1341,24 @@ def inline_templates=(file = nil) app, data = nil end - if data - if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m - encoding = $2 - else - encoding = settings.default_encoding - end - lines = app.count("\n") + 1 - template = nil - force_encoding data, encoding - data.each_line do |line| - lines += 1 - if line =~ /^@@\s*(.*\S)\s*$/ - template = force_encoding(String.new, encoding) - templates[$1.to_sym] = [template, file, lines] - elsif template - template << line - end + return unless data + + encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m + $2 + else + settings.default_encoding + end + + lines = app.count("\n") + 1 + template = nil + force_encoding data, encoding + data.each_line do |line| + lines += 1 + if line =~ /^@@\s*(.*\S)\s*$/ + template = force_encoding(String.new, encoding) + templates[$1.to_sym] = [template, file, lines] + elsif template + template << line end end end @@ -1320,8 +1367,10 @@ def inline_templates=(file = nil) def mime_type(type, value = nil) return type if type.nil? return type.to_s if type.to_s.include?('/') - type = ".#{type}" unless type.to_s[0] == ?. + + type = ".#{type}" unless type.to_s[0] == '.' return Rack::Mime.mime_type(type, nil) unless value + Rack::Mime::MIME_TYPES[type] = value end @@ -1330,7 +1379,7 @@ def mime_type(type, value = nil) # mime_types :js # => ['application/javascript', 'text/javascript'] def mime_types(type) type = mime_type type - type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type] + type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type] end # Define a before filter; runs before all requests within the same @@ -1359,7 +1408,7 @@ def condition(name = "#{caller.first[/`.*'/]} condition", &block) end def public=(value) - warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead" + warn ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead' set(:public_folder, value) end @@ -1381,14 +1430,21 @@ def get(path, opts = {}, &block) route('HEAD', path, opts, &block) end - def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end - def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end - def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end - def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end - def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end - def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end - def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end - def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end + def put(path, opts = {}, &block) route 'PUT', path, opts, &block end + + def post(path, opts = {}, &block) route 'POST', path, opts, &block end + + def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end + + def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end + + def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end + + def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end + + def link(path, opts = {}, &block) route 'LINK', path, opts, &block end + + def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end # Makes the methods defined in the block and in the Modules given # in `extensions` available to the handlers and templates @@ -1428,37 +1484,39 @@ def use(middleware, *args, &block) # Stop the self-hosted server if running. def quit! return unless running? + # Use Thin's hard #stop! if available, otherwise just #stop. running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop - $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless suppress_messages? + warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages? set :running_server, nil set :handler_name, nil end - alias_method :stop!, :quit! + alias stop! quit! # Run the Sinatra app as a self-hosted server using # Puma, Falcon, Mongrel, or WEBrick (in that order). If given a block, will call # with the constructed handler once we have taken the stage. def run!(options = {}, &block) return if running? + set options handler = Rack::Handler.pick(server) handler_name = handler.name.gsub(/.*::/, '') server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {} - server_settings.merge!(:Port => port, :Host => bind) + server_settings.merge!(Port: port, Host: bind) begin start_server(handler, server_settings, handler_name, &block) rescue Errno::EADDRINUSE - $stderr.puts "== Someone is already performing on port #{port}!" + warn "== Someone is already performing on port #{port}!" raise ensure quit! end end - alias_method :start!, :run! + alias start! run! # Check whether the self-hosted server is running or not. def running? @@ -1476,8 +1534,8 @@ def prototype # Create a new instance of the class fronted by its middleware # pipeline. The object is guaranteed to respond to #call but may not be # an instance of the class new was called on. - def new(*args, &bk) - instance = new!(*args, &bk) + def new(*args, &block) + instance = new!(*args, &block) Wrapper.new(build(instance).to_app, instance) end ruby2_keywords :new if respond_to?(:ruby2_keywords, true) @@ -1512,7 +1570,7 @@ def start_server(handler, server_settings, handler_name) # Run the instance we created: handler.run(self, **server_settings) do |server| unless suppress_messages? - $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}" + warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}" end setup_traps @@ -1529,18 +1587,18 @@ def suppress_messages? end def setup_traps - if traps? - at_exit { quit! } + return unless traps? - [:INT, :TERM].each do |signal| - old_handler = trap(signal) do - quit! - old_handler.call if old_handler.respond_to?(:call) - end - end + at_exit { quit! } - set :traps, false + %i[INT TERM].each do |signal| + old_handler = trap(signal) do + quit! + old_handler.call if old_handler.respond_to?(:call) + end end + + set :traps, false end # Dynamically defines a method on settings. @@ -1568,18 +1626,21 @@ def user_agent(pattern) end end end - alias_method :agent, :user_agent + alias agent user_agent # Condition for matching mimetypes. Accepts file extensions. def provides(*types) types.map! { |t| mime_types(t) } types.flatten! condition do - if type = response['Content-Type'] - types.include? type or types.include? type[/^[^;]+/] - elsif type = request.preferred_type(types) - params = (type.respond_to?(:params) ? type.params : {}) - content_type(type, params) + response_content_type = response['Content-Type'] + preferred_type = request.preferred_type(types) + + if response_content_type + types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/]) + elsif preferred_type + params = (preferred_type.respond_to?(:params) ? preferred_type.params : {}) + content_type(preferred_type, params) true else false @@ -1588,7 +1649,7 @@ def provides(*types) end def route(verb, path, options = {}, &block) - enable :empty_path_info if path == "" and empty_path_info.nil? + enable :empty_path_info if path == '' && empty_path_info.nil? signature = compile!(verb, path, block, **options) (@routes[verb] ||= []) << signature invoke_hook(:route_added, verb, path, block) @@ -1617,12 +1678,13 @@ def compile!(verb, path, block, **options) pattern = compile(path, route_mustermann_opts) method_name = "#{verb} #{path}" unbound_method = generate_method(method_name, &block) - conditions, @conditions = @conditions, [] - wrapper = block.arity != 0 ? - proc { |a, p| unbound_method.bind(a).call(*p) } : - proc { |a, p| unbound_method.bind(a).call } + conditions = @conditions + @conditions = [] + wrapper = block.arity.zero? ? + proc { |a, _p| unbound_method.bind(a).call } : + proc { |a, p| unbound_method.bind(a).call(*p) } - [ pattern, conditions, wrapper ] + [pattern, conditions, wrapper] end def compile(path, route_mustermann_opts = {}) @@ -1640,7 +1702,7 @@ def setup_default_middleware(builder) end def setup_middleware(builder) - middleware.each { |c,a,b| builder.use(c, *a, &b) } + middleware.each { |c, a, b| builder.use(c, *a, &b) } end def setup_logging(builder) @@ -1670,9 +1732,10 @@ def setup_custom_logger(builder) def setup_protection(builder) return unless protection? + options = Hash === protection ? protection.dup : {} options = { - img_src: "'self' data:", + img_src: "'self' data:", font_src: "'self'" }.merge options @@ -1686,6 +1749,7 @@ def setup_protection(builder) def setup_sessions(builder) return unless sessions? + options = {} options[:secret] = session_secret if session_secret? options.merge! sessions.to_hash if sessions.respond_to? :to_hash @@ -1714,9 +1778,9 @@ def warn(message) # Like Kernel#caller but excluding certain magic entries def cleaned_caller(keep = 3) - caller(1). - map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. - reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } + caller(1) + .map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] } + .reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } end end @@ -1724,6 +1788,7 @@ def cleaned_caller(keep = 3) # which is UTF-8 by default def self.force_encoding(data, encoding = default_encoding) return if data == settings || data.is_a?(Tempfile) + if data.respond_to? :force_encoding data.force_encoding(encoding).encode! elsif data.respond_to? :each_value @@ -1734,24 +1799,26 @@ def self.force_encoding(data, encoding = default_encoding) data end - def force_encoding(*args) settings.force_encoding(*args) end + def force_encoding(*args) + settings.force_encoding(*args) + end reset! set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym - set :raise_errors, Proc.new { test? } - set :dump_errors, Proc.new { !test? } - set :show_exceptions, Proc.new { development? } + set :raise_errors, proc { test? } + set :dump_errors, proc { !test? } + set :show_exceptions, proc { development? } set :sessions, false set :session_store, Rack::Protection::EncryptedCookie set :logging, false set :protection, true set :method_override, false set :use_code, false - set :default_encoding, "utf-8" + set :default_encoding, 'utf-8' set :x_cascade, true set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" } - settings.add_charset << /^text\// + settings.add_charset << %r{^text/} set :mustermann_opts, {} set :default_content_type, 'text/html' @@ -1761,12 +1828,12 @@ def force_encoding(*args) settings.force_encoding(*args) end set :session_secret, SecureRandom.hex(64) rescue LoadError, NotImplementedError # SecureRandom raises a NotImplementedError if no random device is available - set :session_secret, "%064x" % Kernel.rand(2**256-1) + set :session_secret, format('%064x', Kernel.rand((2**256) - 1)) end class << self - alias_method :methodoverride?, :method_override? - alias_method :methodoverride=, :method_override= + alias methodoverride? method_override? + alias methodoverride= method_override= end set :run, false # start server via at-exit hook? @@ -1774,7 +1841,7 @@ class << self set :handler_name, nil set :traps, true set :server, %w[HTTP webrick] - set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' } + set :bind, proc { development? ? 'localhost' : '0.0.0.0' } set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567) set :quiet, false @@ -1792,14 +1859,14 @@ class << self set :strict_paths, true set :app_file, nil - set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) } - set :views, Proc.new { root && File.join(root, 'views') } - set :reload_templates, Proc.new { development? } + set :root, proc { app_file && File.expand_path(File.dirname(app_file)) } + set :views, proc { root && File.join(root, 'views') } + set :reload_templates, proc { development? } set :lock, false set :threaded, true - set :public_folder, Proc.new { root && File.join(root, 'public') } - set :static, Proc.new { public_folder && File.exist?(public_folder) } + set :public_folder, proc { root && File.join(root, 'public') } + set :static, proc { public_folder && File.exist?(public_folder) } set :static_cache_control, false error ::Exception do @@ -1818,7 +1885,7 @@ class << self error NotFound do content_type 'text/html' - if self.class == Sinatra::Application + if instance_of?(Sinatra::Application) code = <<-RUBY.gsub(/^ {12}/, '') #{request.request_method.downcase} '#{request.path_info}' do "Hello World" @@ -1833,11 +1900,11 @@ class #{self.class} end RUBY - file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '') + file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '') code = "# in #{file}\n#{code}" unless file.empty? end - (<<-HTML).gsub(/^ {10}/, '') + <<-HTML.gsub(/^ {10}/, '') @@ -1849,7 +1916,7 @@ class #{self.class}

Sinatra doesn’t know this ditty.

- +
Try this:
#{Rack::Utils.escape_html(code)}
@@ -1869,12 +1936,12 @@ class #{self.class} # top-level. Subclassing Sinatra::Base is highly recommended for # modular applications. class Application < Base - set :logging, Proc.new { !test? } + set :logging, proc { !test? } set :method_override, true - set :run, Proc.new { !test? } + set :run, proc { !test? } set :app_file, nil - def self.register(*extensions, &block) #:nodoc: + def self.register(*extensions, &block) # :nodoc: added_methods = extensions.flat_map(&:public_instance_methods) Delegator.delegate(*added_methods) super(*extensions, &block) @@ -1884,11 +1951,12 @@ def self.register(*extensions, &block) #:nodoc: # Sinatra delegation mixin. Mixing this module into an object causes all # methods to be delegated to the Sinatra::Application class. Used primarily # at the top-level. - module Delegator #:nodoc: + module Delegator # :nodoc: def self.delegate(*methods) methods.each do |method_name| define_method(method_name) do |*args, &block| return super(*args, &block) if respond_to? method_name + Delegator.target.send(method_name, *args, &block) end # ensure keyword argument passing is compatible with ruby >= 2.7 @@ -1911,7 +1979,8 @@ class << self class Wrapper def initialize(stack, instance) - @stack, @instance = stack, instance + @stack = stack + @instance = instance end def settings diff --git a/lib/sinatra/indifferent_hash.rb b/lib/sinatra/indifferent_hash.rb index 6951527472..a3a96d5c4d 100644 --- a/lib/sinatra/indifferent_hash.rb +++ b/lib/sinatra/indifferent_hash.rb @@ -79,7 +79,7 @@ def []=(key, value) super(convert_key(key), convert_value(value)) end - alias_method :store, :[]= + alias store []= def key(value) super(convert_value(value)) @@ -89,20 +89,21 @@ def key?(key) super(convert_key(key)) end - alias_method :has_key?, :key? - alias_method :include?, :key? - alias_method :member?, :key? + alias has_key? key? + alias include? key? + alias member? key? def value?(value) super(convert_value(value)) end - alias_method :has_value?, :value? + alias has_value? value? def delete(key) super(convert_key(key)) end + # Added in Ruby 2.3 def dig(key, *other_keys) super(convert_key(key), *other_keys) end @@ -141,7 +142,7 @@ def merge!(*other_hashes) self end - alias_method :update, :merge! + alias update merge! def merge(*other_hashes, &block) dup.merge!(*other_hashes, &block) @@ -171,17 +172,19 @@ def transform_keys! def select(*args, &block) return to_enum(:select) unless block_given? + dup.tap { |hash| hash.select!(*args, &block) } end def reject(*args, &block) return to_enum(:reject) unless block_given? + dup.tap { |hash| hash.reject!(*args, &block) } end def compact dup.tap(&:compact!) - end if method_defined?(:compact) # Added in Ruby 2.4 + end private diff --git a/lib/sinatra/main.rb b/lib/sinatra/main.rb index e4231c30f9..bca7fc1a09 100644 --- a/lib/sinatra/main.rb +++ b/lib/sinatra/main.rb @@ -1,47 +1,49 @@ +# frozen_string_literal: true + module Sinatra - ParamsConfig = {} + PARAMS_CONFIG = {} if ARGV.any? require 'optparse' - parser = OptionParser.new { |op| - op.on('-p port', 'set the port (default is 4567)') { |val| ParamsConfig[:port] = Integer(val) } - op.on('-s server', 'specify rack server/handler') { |val| ParamsConfig[:server] = val } - op.on('-q', 'turn on quiet mode (default is off)') { ParamsConfig[:quiet] = true } - op.on('-x', 'turn on the mutex lock (default is off)') { ParamsConfig[:lock] = true } + parser = OptionParser.new do |op| + op.on('-p port', 'set the port (default is 4567)') { |val| PARAMS_CONFIG[:port] = Integer(val) } + op.on('-s server', 'specify rack server/handler') { |val| PARAMS_CONFIG[:server] = val } + op.on('-q', 'turn on quiet mode (default is off)') { PARAMS_CONFIG[:quiet] = true } + op.on('-x', 'turn on the mutex lock (default is off)') { PARAMS_CONFIG[:lock] = true } op.on('-e env', 'set the environment (default is development)') do |val| ENV['RACK_ENV'] = val - ParamsConfig[:environment] = val.to_sym + PARAMS_CONFIG[:environment] = val.to_sym end op.on('-o addr', "set the host (default is (env == 'development' ? 'localhost' : '0.0.0.0'))") do |val| - ParamsConfig[:bind] = val + PARAMS_CONFIG[:bind] = val end - } + end begin parser.parse!(ARGV.dup) - rescue => evar - ParamsConfig[:optparse_error] = evar + rescue StandardError => e + PARAMS_CONFIG[:optparse_error] = e end end require 'sinatra/base' class Application < Base - # we assume that the first file that requires 'sinatra' is the # app_file. all other path related options are calculated based # on this path by default. set :app_file, caller_files.first || $0 - set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) } + set :run, proc { File.expand_path($0) == File.expand_path(app_file) } if run? && ARGV.any? - error = ParamsConfig.delete(:optparse_error) + error = PARAMS_CONFIG.delete(:optparse_error) raise error if error - ParamsConfig.each { |k, v| set k, v } + + PARAMS_CONFIG.each { |k, v| set k, v } end end - remove_const(:ParamsConfig) + remove_const(:PARAMS_CONFIG) at_exit { Application.run! if $!.nil? && Application.run? } end diff --git a/lib/sinatra/show_exceptions.rb b/lib/sinatra/show_exceptions.rb index de468c0a4f..db847ffab7 100644 --- a/lib/sinatra/show_exceptions.rb +++ b/lib/sinatra/show_exceptions.rb @@ -12,6 +12,7 @@ module Sinatra class ShowExceptions < Rack::ShowExceptions @@eats_errors = Object.new def @@eats_errors.flush(*) end + def @@eats_errors.puts(*) end def initialize(app) @@ -21,23 +22,24 @@ def initialize(app) def call(env) @app.call(env) rescue Exception => e - errors, env["rack.errors"] = env["rack.errors"], @@eats_errors + errors = env['rack.errors'] + env['rack.errors'] = @@eats_errors if prefers_plain_text?(env) - content_type = "text/plain" + content_type = 'text/plain' body = dump_exception(e) else - content_type = "text/html" + content_type = 'text/html' body = pretty(env, e) end - env["rack.errors"] = errors + env['rack.errors'] = errors [ 500, { - "Content-Type" => content_type, - "Content-Length" => body.bytesize.to_s + 'Content-Type' => content_type, + 'Content-Length' => body.bytesize.to_s }, [body] ] @@ -49,27 +51,27 @@ def template private - def bad_request?(e) - Sinatra::BadRequest === e + def bad_request?(exception) + Sinatra::BadRequest === exception end def prefers_plain_text?(env) - !(Request.new(env).preferred_type("text/plain","text/html") == "text/html") && - [/curl/].index { |item| item =~ env["HTTP_USER_AGENT"] } + Request.new(env).preferred_type('text/plain', 'text/html') != 'text/html' && + [/curl/].index { |item| item =~ env['HTTP_USER_AGENT'] } end def frame_class(frame) if frame.filename =~ %r{lib/sinatra.*\.rb} - "framework" + 'framework' elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) || frame.filename =~ %r{/bin/(\w+)\z} - "system" + 'system' else - "app" + 'app' end end -TEMPLATE = ERB.new <<-HTML # :nodoc: + TEMPLATE = ERB.new <<-HTML # :nodoc: @@ -357,6 +359,6 @@ def frame_class(frame)
-HTML + HTML end end diff --git a/lib/sinatra/version.rb b/lib/sinatra/version.rb index 2c1a44fad8..7a21c28c89 100644 --- a/lib/sinatra/version.rb +++ b/lib/sinatra/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Sinatra VERSION = '3.0.0' end diff --git a/rack-protection/Gemfile b/rack-protection/Gemfile index f9631bf2fc..293cdb402d 100644 --- a/rack-protection/Gemfile +++ b/rack-protection/Gemfile @@ -1,11 +1,13 @@ -source "https://rubygems.org" +# frozen_string_literal: true + +source 'https://rubygems.org' # encoding: utf-8 gem 'rake' rack_version = ENV['rack'].to_s -rack_version = nil if rack_version.empty? or rack_version == 'stable' -rack_version = {:github => 'rack/rack'} if rack_version == 'master' +rack_version = nil if rack_version.empty? || (rack_version == 'stable') +rack_version = { github: 'rack/rack' } if rack_version == 'master' gem 'rack', rack_version gem 'sinatra', path: '..' diff --git a/rack-protection/Rakefile b/rack-protection/Rakefile index 5737bad66d..4ccffd5ffa 100644 --- a/rack-protection/Rakefile +++ b/rack-protection/Rakefile @@ -1,14 +1,15 @@ -# encoding: utf-8 +# frozen_string_literal: true + $LOAD_PATH.unshift File.expand_path('lib', __dir__) begin require 'bundler' Bundler::GemHelper.install_tasks rescue LoadError => e - $stderr.puts e + warn e end -desc "run specs" +desc 'run specs' task(:spec) { ruby '-S rspec' } namespace :doc do @@ -16,38 +17,39 @@ namespace :doc do Dir.glob 'lib/rack/protection/*.rb' do |file| excluded_files = %w[lib/rack/protection/base.rb lib/rack/protection/version.rb] next if excluded_files.include?(file) + doc = File.read(file)[/^ module Protection(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n") - file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc" - Dir.mkdir "doc" unless File.directory? "doc" + file = "doc/#{file[4..-4].tr('/_', '-')}.rdoc" + Dir.mkdir 'doc' unless File.directory? 'doc' puts "writing #{file}" - File.open(file, "w") { |f| f << doc } + File.open(file, 'w') { |f| f << doc } end end task :index do - doc = File.read("README.md") - file = "doc/rack-protection-readme.md" - Dir.mkdir "doc" unless File.directory? "doc" + doc = File.read('README.md') + file = 'doc/rack-protection-readme.md' + Dir.mkdir 'doc' unless File.directory? 'doc' puts "writing #{file}" - File.open(file, "w") { |f| f << doc } + File.open(file, 'w') { |f| f << doc } end - task :all => [:readmes, :index] + task all: %i[readmes index] end -desc "generate documentation" -task :doc => 'doc:all' +desc 'generate documentation' +task doc: 'doc:all' -desc "generate gemspec" +desc 'generate gemspec' task 'rack-protection.gemspec' do require 'rack/protection/version' content = File.binread 'rack-protection.gemspec' # fetch data fields = { - :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/), - :email => ["mail@zzak.io", "konstantin.haase@gmail.com"], - :files => %w(License README.md Rakefile Gemfile rack-protection.gemspec) + Dir['lib/**/*'] + authors: `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/), + email: ['mail@zzak.io', 'konstantin.haase@gmail.com'], + files: %w[License README.md Rakefile Gemfile rack-protection.gemspec] + Dir['lib/**/*'] } # insert data @@ -67,6 +69,6 @@ task 'rack-protection.gemspec' do File.open('rack-protection.gemspec', 'w') { |f| f << content } end -task :gemspec => 'rack-protection.gemspec' -task :default => :spec -task :test => :spec +task gemspec: 'rack-protection.gemspec' +task default: :spec +task test: :spec diff --git a/rack-protection/lib/rack-protection.rb b/rack-protection/lib/rack-protection.rb deleted file mode 100644 index 1633086e2c..0000000000 --- a/rack-protection/lib/rack-protection.rb +++ /dev/null @@ -1 +0,0 @@ -require "rack/protection" diff --git a/rack-protection/lib/rack/protection.rb b/rack-protection/lib/rack/protection.rb index 1be25e4870..21d2ec9268 100644 --- a/rack-protection/lib/rack/protection.rb +++ b/rack-protection/lib/rack/protection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection/version' require 'rack' @@ -29,7 +31,7 @@ def self.new(app, options = {}) use_these = Array options[:use] if options.fetch(:without_session, false) - except += [:session_hijacking, :remote_token] + except += %i[session_hijacking remote_token] end Rack::Builder.new do diff --git a/rack-protection/lib/rack/protection/authenticity_token.rb b/rack-protection/lib/rack/protection/authenticity_token.rb index c55a40654c..f5c8db62d4 100644 --- a/rack-protection/lib/rack/protection/authenticity_token.rb +++ b/rack-protection/lib/rack/protection/authenticity_token.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' require 'securerandom' require 'openssl' @@ -19,7 +21,7 @@ module Protection # # It is not OOTB-compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem. # For that, the following patch needs to be applied: - # + # # Rack::Protection::AuthenticityToken.default_options(key: "csrf.token", authenticity_param: "_csrf") # # == Options @@ -95,12 +97,12 @@ module Protection class AuthenticityToken < Base TOKEN_LENGTH = 32 - default_options :authenticity_param => 'authenticity_token', - :key => :csrf, - :allow_if => nil + default_options authenticity_param: 'authenticity_token', + key: :csrf, + allow_if: nil def self.token(session, path: nil, method: :post) - self.new(nil).mask_authenticity_token(session, path: path, method: method) + new(nil).mask_authenticity_token(session, path: path, method: method) end def self.random_token @@ -114,8 +116,8 @@ def accepts?(env) safe?(env) || valid_token?(env, env['HTTP_X_CSRF_TOKEN']) || valid_token?(env, Request.new(env).params[options[:authenticity_param]]) || - ( options[:allow_if] && options[:allow_if].call(env) ) - rescue + options[:allow_if]&.call(env) + rescue StandardError false end @@ -123,10 +125,10 @@ def mask_authenticity_token(session, path: nil, method: :post) set_token(session) token = if path && method - per_form_token(session, path, method) - else - global_token(session) - end + per_form_token(session, path, method) + else + global_token(session) + end mask_token(token) end @@ -185,7 +187,7 @@ def unmask_token(masked_token) # value and decrypt it token_length = masked_token.length / 2 one_time_pad = masked_token[0...token_length] - encrypted_token = masked_token[token_length..-1] + encrypted_token = masked_token[token_length..] xor_byte_strings(one_time_pad, encrypted_token) end @@ -207,8 +209,7 @@ def compare_with_global_token(token, session) def compare_with_per_form_token(token, session, request) secure_compare(token, - per_form_token(session, request.path.chomp('/'), request.request_method) - ) + per_form_token(session, request.path.chomp('/'), request.request_method)) end def real_token(session) @@ -233,7 +234,7 @@ def decode_token(token) def token_hmac(session, identifier) OpenSSL::HMAC.digest( - OpenSSL::Digest::SHA256.new, + OpenSSL::Digest.new('SHA256'), real_token(session), identifier ) diff --git a/rack-protection/lib/rack/protection/base.rb b/rack-protection/lib/rack/protection/base.rb index 83e724069b..a1314adb82 100644 --- a/rack-protection/lib/rack/protection/base.rb +++ b/rack-protection/lib/rack/protection/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' require 'rack/utils' require 'digest' @@ -8,12 +10,12 @@ module Rack module Protection class Base DEFAULT_OPTIONS = { - :reaction => :default_reaction, :logging => true, - :message => 'Forbidden', :encryptor => Digest::SHA1, - :session_key => 'rack.session', :status => 403, - :allow_empty_referrer => true, - :report_key => "protection.failed", - :html_types => %w[text/html application/xhtml text/xml application/xml] + reaction: :default_reaction, logging: true, + message: 'Forbidden', encryptor: Digest::SHA1, + session_key: 'rack.session', status: 403, + allow_empty_referrer: true, + report_key: 'protection.failed', + html_types: %w[text/html application/xhtml text/xml application/xml] } attr_reader :app, :options @@ -31,7 +33,8 @@ def default_options end def initialize(app, options = {}) - @app, @options = app, default_options.merge(options) + @app = app + @options = default_options.merge(options) end def safe?(env) @@ -52,24 +55,26 @@ def call(env) def react(env) result = send(options[:reaction], env) - result if Array === result and result.size == 3 + result if (Array === result) && (result.size == 3) end def warn(env, message) return unless options[:logging] + l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors']) l.warn(message) end def instrument(env) - return unless i = options[:instrumenter] + return unless (i = options[:instrumenter]) + env['rack.protection.attack'] = self.class.name.split('::').last.downcase i.instrument('rack.protection', env) end def deny(env) warn env, "attack prevented by #{self.class}" - [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]] + [options[:status], { 'Content-Type' => 'text/plain' }, [options[:message]]] end def report(env) @@ -83,7 +88,8 @@ def session?(env) def session(env) return env[options[:session_key]] if session? env - fail "you need to set up a session middleware *before* #{self.class}" + + raise "you need to set up a session middleware *before* #{self.class}" end def drop_session(env) @@ -92,7 +98,8 @@ def drop_session(env) def referrer(env) ref = env['HTTP_REFERER'].to_s - return if !options[:allow_empty_referrer] and ref.empty? + return if !options[:allow_empty_referrer] && ref.empty? + URI.parse(ref).host || Request.new(env).host rescue URI::InvalidURIError end @@ -102,7 +109,7 @@ def origin(env) end def random_string(secure = defined? SecureRandom) - secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1) + secure ? SecureRandom.hex(16) : '%032x' % rand((2**128) - 1) rescue NotImplementedError random_string false end @@ -118,8 +125,9 @@ def secure_compare(a, b) alias default_reaction deny def html?(headers) - return false unless header = headers.detect { |k,v| k.downcase == 'content-type' } - options[:html_types].include? header.last[/^\w+\/\w+/] + return false unless (header = headers.detect { |k, _v| k.downcase == 'content-type' }) + + options[:html_types].include? header.last[%r{^\w+/\w+}] end end end diff --git a/rack-protection/lib/rack/protection/content_security_policy.rb b/rack-protection/lib/rack/protection/content_security_policy.rb index 91eea925ef..32d8ac70fa 100644 --- a/rack-protection/lib/rack/protection/content_security_policy.rb +++ b/rack-protection/lib/rack/protection/content_security_policy.rb @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -38,16 +39,16 @@ module Protection class ContentSecurityPolicy < Base default_options default_src: "'self'", report_only: false - DIRECTIVES = %i(base_uri child_src connect_src default_src + DIRECTIVES = %i[base_uri child_src connect_src default_src font_src form_action frame_ancestors frame_src img_src manifest_src media_src object_src plugin_types referrer reflected_xss report_to report_uri require_sri_for sandbox script_src style_src worker_src webrtc_src navigate_to - prefetch_src).freeze + prefetch_src].freeze - NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener - upgrade_insecure_requests).freeze + NO_ARG_DIRECTIVES = %i[block_all_mixed_content disown_opener + upgrade_insecure_requests].freeze def csp_policy directives = [] diff --git a/rack-protection/lib/rack/protection/cookie_tossing.rb b/rack-protection/lib/rack/protection/cookie_tossing.rb index 10614d5261..0a7f43ad54 100644 --- a/rack-protection/lib/rack/protection/cookie_tossing.rb +++ b/rack-protection/lib/rack/protection/cookie_tossing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' require 'pathname' @@ -29,9 +31,8 @@ def accepts?(env) cookie_header = env['HTTP_COOKIE'] cookies = Rack::Utils.parse_query(cookie_header, ';,') { |s| s } cookies.each do |k, v| - if k == session_key && Array(v).size > 1 - bad_cookies << k - elsif k != session_key && Rack::Utils.unescape(k) == session_key + if (k == session_key && Array(v).size > 1) || + (k != session_key && Rack::Utils.unescape(k) == session_key) bad_cookies << k end end @@ -40,6 +41,7 @@ def accepts?(env) def remove_bad_cookies(request, response) return if bad_cookies.empty? + paths = cookie_paths(request.path) bad_cookies.each do |name| paths.each { |path| response.set_cookie name, empty_cookie(request.host, path) } @@ -49,7 +51,7 @@ def remove_bad_cookies(request, response) def redirect(env) request = Request.new(env) warn env, "attack prevented by #{self.class}" - [302, {'Content-Type' => 'text/html', 'Location' => request.path}, []] + [302, { 'Content-Type' => 'text/html', 'Location' => request.path }, []] end def bad_cookies @@ -64,7 +66,7 @@ def cookie_paths(path) end def empty_cookie(host, path) - {:value => '', :domain => host, :path => path, :expires => Time.at(0)} + { value: '', domain: host, path: path, expires: Time.at(0) } end def session_key diff --git a/rack-protection/lib/rack/protection/encrypted_cookie.rb b/rack-protection/lib/rack/protection/encrypted_cookie.rb index bb0dbacb38..bf682312a9 100644 --- a/rack-protection/lib/rack/protection/encrypted_cookie.rb +++ b/rack-protection/lib/rack/protection/encrypted_cookie.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'openssl' require 'zlib' require 'json' @@ -67,7 +69,7 @@ def encode(str) end def decode(str) - str.unpack('m').first + str.unpack1('m') end # Encode session cookies as Marshaled Base64 data @@ -78,7 +80,12 @@ def encode(str) def decode(str) return unless str - ::Marshal.load(super(str)) rescue nil + + begin + ::Marshal.load(super(str)) + rescue StandardError + nil + end end end @@ -91,7 +98,12 @@ def encode(obj) def decode(str) return unless str - ::JSON.parse(super(str)) rescue nil + + begin + ::JSON.parse(super(str)) + rescue StandardError + nil + end end end @@ -102,8 +114,9 @@ def encode(obj) def decode(str) return unless str + ::JSON.parse(Zlib::Inflate.inflate(super(str))) - rescue + rescue StandardError nil end end @@ -127,12 +140,12 @@ def decode(str) attr_reader :coder - def initialize(app, options={}) + def initialize(app, options = {}) # Assume keys are hex strings and convert them to raw byte strings for # actual key material - @secrets = options.values_at(:secret, :old_secret).compact.map { |secret| + @secrets = options.values_at(:secret, :old_secret).compact.map do |secret| [secret].pack('H*') - } + end warn <<-MSG unless secure?(options) SECURITY WARNING: No secret option provided to Rack::Protection::EncryptedCookie. @@ -154,7 +167,7 @@ def initialize(app, options={}) Called from: #{caller[0]}. MSG - if options.has_key?(:legacy_hmac_secret) + if options.key?(:legacy_hmac_secret) @legacy_hmac = options.fetch(:legacy_hmac, OpenSSL::Digest::SHA1) # Multiply the :digest_length: by 2 because this value is the length of @@ -173,19 +186,19 @@ def initialize(app, options={}) # If no encryption is used, rely on the previous default (Base64::Marshal) @coder = (options[:coder] ||= (@secrets.any? ? Marshal.new : Base64::Marshal.new)) - super(app, options.merge!(:cookie_only => true)) + super(app, options.merge!(cookie_only: true)) end private - def find_session(req, sid) + def find_session(req, _sid) data = unpacked_cookie_data(req) data = persistent_session_id!(data) - [data["session_id"], data] + [data['session_id'], data] end def extract_session_id(request) - unpacked_cookie_data(request)["session_id"] + unpacked_cookie_data(request)['session_id'] end def unpacked_cookie_data(request) @@ -214,14 +227,14 @@ def unpacked_cookie_data(request) end end - def persistent_session_id!(data, sid=nil) + def persistent_session_id!(data, sid = nil) data ||= {} - data["session_id"] ||= sid || generate_sid + data['session_id'] ||= sid || generate_sid data end - def write_session(req, session_id, session, options) - session = session.merge("session_id" => session_id) + def write_session(req, session_id, session, _options) + session = session.merge('session_id' => session_id) session_data = coder.encode(session) unless @secrets.empty? @@ -229,14 +242,14 @@ def write_session(req, session_id, session, options) end if session_data.size > (4096 - @key.size) - req.get_header(RACK_ERRORS).puts("Warning! Rack::Protection::EncryptedCookie data size exceeds 4K.") + req.get_header(RACK_ERRORS).puts('Warning! Rack::Protection::EncryptedCookie data size exceeds 4K.') nil else session_data end end - def delete_session(req, session_id, options) + def delete_session(_req, _session_id, options) # Nothing to do here, data is in the client generate_sid unless options[:drop] end @@ -253,7 +266,7 @@ def generate_hmac(data) def secure?(options) @secrets.size >= 1 || - (options[:coder] && options[:let_coder_handle_secure_encoding]) + (options[:coder] && options[:let_coder_handle_secure_encoding]) end end end diff --git a/rack-protection/lib/rack/protection/encryptor.rb b/rack-protection/lib/rack/protection/encryptor.rb index e55c6aa96b..ded10c7e8e 100644 --- a/rack-protection/lib/rack/protection/encryptor.rb +++ b/rack-protection/lib/rack/protection/encryptor.rb @@ -1,21 +1,23 @@ +# frozen_string_literal: true + require 'openssl' module Rack module Protection module Encryptor - CIPHER = 'aes-256-gcm'.freeze - DELIMITER = '--'.freeze + CIPHER = 'aes-256-gcm' + DELIMITER = '--' def self.base64_encode(str) [str].pack('m0') end def self.base64_decode(str) - str.unpack('m0').first + str.unpack1('m0') end def self.encrypt_message(data, secret, auth_data = '') - raise ArgumentError, "data cannot be nil" if data.nil? + raise ArgumentError, 'data cannot be nil' if data.nil? cipher = OpenSSL::Cipher.new(CIPHER) cipher.encrypt @@ -52,7 +54,6 @@ def self.decrypt_message(data, secret) decrypted_data = cipher.update(cipher_text) decrypted_data << cipher.final decrypted_data - rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError nil end diff --git a/rack-protection/lib/rack/protection/escaped_params.rb b/rack-protection/lib/rack/protection/escaped_params.rb index 6706d1030e..17b35d5292 100644 --- a/rack-protection/lib/rack/protection/escaped_params.rb +++ b/rack-protection/lib/rack/protection/escaped_params.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' require 'rack/utils' require 'tempfile' @@ -29,8 +31,8 @@ class << self public :escape_html end - default_options :escape => :html, - :escaper => defined?(EscapeUtils) ? EscapeUtils : self + default_options escape: :html, + escaper: defined?(EscapeUtils) ? EscapeUtils : self def initialize(*) super @@ -41,15 +43,19 @@ def initialize(*) @javascript = modes.include? :javascript @url = modes.include? :url - if @javascript and not @escaper.respond_to? :escape_javascript - fail("Use EscapeUtils for JavaScript escaping.") - end + return unless @javascript && (!@escaper.respond_to? :escape_javascript) + + raise('Use EscapeUtils for JavaScript escaping.') end def call(env) request = Request.new(env) get_was = handle(request.GET) - post_was = handle(request.POST) rescue nil + post_was = begin + handle(request.POST) + rescue StandardError + nil + end app.call env ensure request.GET.replace get_was if get_was @@ -68,13 +74,12 @@ def escape(object) when Array then object.map { |o| escape(o) } when String then escape_string(object) when Tempfile then object - else nil end end def escape_hash(hash) hash = hash.dup - hash.each { |k,v| hash[k] = escape(v) } + hash.each { |k, v| hash[k] = escape(v) } hash end diff --git a/rack-protection/lib/rack/protection/form_token.rb b/rack-protection/lib/rack/protection/form_token.rb index 994740f83f..7e01976171 100644 --- a/rack-protection/lib/rack/protection/form_token.rb +++ b/rack-protection/lib/rack/protection/form_token.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -16,7 +18,7 @@ module Protection # Compatible with rack-csrf. class FormToken < AuthenticityToken def accepts?(env) - env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super + env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' or super end end end diff --git a/rack-protection/lib/rack/protection/frame_options.rb b/rack-protection/lib/rack/protection/frame_options.rb index bce75c47e9..c159d4a985 100644 --- a/rack-protection/lib/rack/protection/frame_options.rb +++ b/rack-protection/lib/rack/protection/frame_options.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -17,7 +19,7 @@ module Protection # frame. Use :deny to forbid any embedding, :sameorigin # to allow embedding from the same origin (default). class FrameOptions < Base - default_options :frame_options => :sameorigin + default_options frame_options: :sameorigin def frame_options @frame_options ||= begin diff --git a/rack-protection/lib/rack/protection/http_origin.rb b/rack-protection/lib/rack/protection/http_origin.rb index 834a185525..8eb25bbbd9 100644 --- a/rack-protection/lib/rack/protection/http_origin.rb +++ b/rack-protection/lib/rack/protection/http_origin.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -19,7 +21,7 @@ module Protection class HttpOrigin < Base DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } default_reaction :deny - default_options :allow_if => nil + default_options allow_if: nil def base_url(env) request = Rack::Request.new(env) @@ -29,14 +31,13 @@ def base_url(env) def accepts?(env) return true if safe? env - return true unless origin = env['HTTP_ORIGIN'] + return true unless (origin = env['HTTP_ORIGIN']) return true if base_url(env) == origin - return true if options[:allow_if] && options[:allow_if].call(env) + return true if options[:allow_if]&.call(env) permitted_origins = options[:permitted_origins] Array(permitted_origins).include? origin end - end end end diff --git a/rack-protection/lib/rack/protection/ip_spoofing.rb b/rack-protection/lib/rack/protection/ip_spoofing.rb index 3e404ad7c0..a8799324b6 100644 --- a/rack-protection/lib/rack/protection/ip_spoofing.rb +++ b/rack-protection/lib/rack/protection/ip_spoofing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -13,9 +15,11 @@ class IPSpoofing < Base def accepts?(env) return true unless env.include? 'HTTP_X_FORWARDED_FOR' + ips = env['HTTP_X_FORWARDED_FOR'].split(/\s*,\s*/) - return false if env.include? 'HTTP_CLIENT_IP' and not ips.include? env['HTTP_CLIENT_IP'] - return false if env.include? 'HTTP_X_REAL_IP' and not ips.include? env['HTTP_X_REAL_IP'] + return false if env.include?('HTTP_CLIENT_IP') && (!ips.include? env['HTTP_CLIENT_IP']) + return false if env.include?('HTTP_X_REAL_IP') && (!ips.include? env['HTTP_X_REAL_IP']) + true end end diff --git a/rack-protection/lib/rack/protection/json_csrf.rb b/rack-protection/lib/rack/protection/json_csrf.rb index 5ec826b719..9f6817ac18 100644 --- a/rack-protection/lib/rack/protection/json_csrf.rb +++ b/rack-protection/lib/rack/protection/json_csrf.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -17,7 +19,7 @@ module Protection # # The `:allow_if` option can be set to a proc to use custom allow/deny logic. class JsonCsrf < Base - default_options :allow_if => nil + default_options allow_if: nil alias react deny @@ -36,8 +38,9 @@ def call(env) def has_vector?(request, headers) return false if request.xhr? - return false if options[:allow_if] && options[:allow_if].call(request.env) - return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/ + return false if options[:allow_if]&.call(request.env) + return false unless headers['Content-Type'].to_s.split(';', 2).first =~ %r{^\s*application/json\s*$} + origin(request.env).nil? and referrer(request.env) != request.host end diff --git a/rack-protection/lib/rack/protection/path_traversal.rb b/rack-protection/lib/rack/protection/path_traversal.rb index 4e8c94ca95..34e2e36eb5 100644 --- a/rack-protection/lib/rack/protection/path_traversal.rb +++ b/rack-protection/lib/rack/protection/path_traversal.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -11,11 +13,11 @@ module Protection # Thus GET /foo/%2e%2e%2fbar becomes GET /bar. class PathTraversal < Base def call(env) - path_was = env["PATH_INFO"] - env["PATH_INFO"] = cleanup path_was if path_was && !path_was.empty? + path_was = env['PATH_INFO'] + env['PATH_INFO'] = cleanup path_was if path_was && !path_was.empty? app.call env ensure - env["PATH_INFO"] = path_was + env['PATH_INFO'] = path_was end def cleanup(path) @@ -29,12 +31,13 @@ def cleanup(path) unescaped = unescaped.gsub(backslash, slash) unescaped.split(slash).each do |part| - next if part.empty? or part == dot + next if part.empty? || (part == dot) + part == '..' ? parts.pop : parts << part end cleaned = slash + parts.join(slash) - cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$} + cleaned << slash if parts.any? && unescaped =~ (%r{/\.{0,2}$}) cleaned end end diff --git a/rack-protection/lib/rack/protection/referrer_policy.rb b/rack-protection/lib/rack/protection/referrer_policy.rb index f977d72e4b..eaff7020f9 100644 --- a/rack-protection/lib/rack/protection/referrer_policy.rb +++ b/rack-protection/lib/rack/protection/referrer_policy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -13,7 +15,7 @@ module Protection # Options: # referrer_policy:: The policy to use (default: 'strict-origin-when-cross-origin') class ReferrerPolicy < Base - default_options :referrer_policy => 'strict-origin-when-cross-origin' + default_options referrer_policy: 'strict-origin-when-cross-origin' def call(env) status, headers, body = @app.call(env) diff --git a/rack-protection/lib/rack/protection/remote_referrer.rb b/rack-protection/lib/rack/protection/remote_referrer.rb index 5375ebc3d2..f882567fa1 100644 --- a/rack-protection/lib/rack/protection/remote_referrer.rb +++ b/rack-protection/lib/rack/protection/remote_referrer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack diff --git a/rack-protection/lib/rack/protection/remote_token.rb b/rack-protection/lib/rack/protection/remote_token.rb index f3922c8739..ef4f6e5744 100644 --- a/rack-protection/lib/rack/protection/remote_token.rb +++ b/rack-protection/lib/rack/protection/remote_token.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack diff --git a/rack-protection/lib/rack/protection/session_hijacking.rb b/rack-protection/lib/rack/protection/session_hijacking.rb index 55f4aad871..555121d8b9 100644 --- a/rack-protection/lib/rack/protection/session_hijacking.rb +++ b/rack-protection/lib/rack/protection/session_hijacking.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -13,14 +15,14 @@ module Protection # spoofed, too, this will not prevent determined hijacking attempts. class SessionHijacking < Base default_reaction :drop_session - default_options :tracking_key => :tracking, - :track => %w[HTTP_USER_AGENT] + default_options tracking_key: :tracking, + track: %w[HTTP_USER_AGENT] def accepts?(env) session = session env key = options[:tracking_key] if session.include? key - session[key].all? { |k,v| v == encode(env[k]) } + session[key].all? { |k, v| v == encode(env[k]) } else session[key] = {} options[:track].each { |k| session[key][k] = encode(env[k]) } diff --git a/rack-protection/lib/rack/protection/strict_transport.rb b/rack-protection/lib/rack/protection/strict_transport.rb index b4283bab36..05fe4ae6a8 100644 --- a/rack-protection/lib/rack/protection/strict_transport.rb +++ b/rack-protection/lib/rack/protection/strict_transport.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -18,11 +20,11 @@ module Protection # preload:: Allow this domain to be included in browsers HSTS preload list. See https://hstspreload.appspot.com/ class StrictTransport < Base - default_options :max_age => 31_536_000, :include_subdomains => false, :preload => false + default_options max_age: 31_536_000, include_subdomains: false, preload: false def strict_transport @strict_transport ||= begin - strict_transport = 'max-age=' + options[:max_age].to_s + strict_transport = "max-age=#{options[:max_age]}" strict_transport += '; includeSubDomains' if options[:include_subdomains] strict_transport += '; preload' if options[:preload] strict_transport.to_str diff --git a/rack-protection/lib/rack/protection/version.rb b/rack-protection/lib/rack/protection/version.rb index 5c581ee170..ac5d076b97 100644 --- a/rack-protection/lib/rack/protection/version.rb +++ b/rack-protection/lib/rack/protection/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack module Protection VERSION = '3.0.0' diff --git a/rack-protection/lib/rack/protection/xss_header.rb b/rack-protection/lib/rack/protection/xss_header.rb index eb6f92fe36..14c679d9fb 100644 --- a/rack-protection/lib/rack/protection/xss_header.rb +++ b/rack-protection/lib/rack/protection/xss_header.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -12,7 +14,7 @@ module Protection # Options: # xss_mode:: How the browser should prevent the attack (default: :block) class XSSHeader < Base - default_options :xss_mode => :block, :nosniff => true + default_options xss_mode: :block, nosniff: true def call(env) status, headers, body = @app.call(env) diff --git a/rack-protection/lib/rack_protection.rb b/rack-protection/lib/rack_protection.rb new file mode 100644 index 0000000000..fa36f00450 --- /dev/null +++ b/rack-protection/lib/rack_protection.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require 'rack/protection' diff --git a/rack-protection/rack-protection.gemspec b/rack-protection/rack-protection.gemspec index c8e0f2d1b8..354a66b227 100644 --- a/rack-protection/rack-protection.gemspec +++ b/rack-protection/rack-protection.gemspec @@ -1,40 +1,45 @@ -version = File.read(File.expand_path("../VERSION", __dir__)).strip +# frozen_string_literal: true + +version = File.read(File.expand_path('../VERSION', __dir__)).strip Gem::Specification.new do |s| # general infos - s.name = "rack-protection" + s.name = 'rack-protection' s.version = version - s.description = "Protect against typical web attacks, works with all Rack apps, including Rails." - s.homepage = "http://sinatrarb.com/protection/" + s.description = 'Protect against typical web attacks, works with all Rack apps, including Rails.' + s.homepage = 'http://sinatrarb.com/protection/' s.summary = s.description s.license = 'MIT' - s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"] - s.email = "sinatrarb@googlegroups.com" - s.files = Dir["lib/**/*.rb"] + [ - "License", - "README.md", - "Rakefile", - "Gemfile", - "rack-protection.gemspec" + s.authors = ['https://github.com/sinatra/sinatra/graphs/contributors'] + s.email = 'sinatrarb@googlegroups.com' + s.files = Dir['lib/**/*.rb'] + [ + 'License', + 'README.md', + 'Rakefile', + 'Gemfile', + 'rack-protection.gemspec' ] - if s.respond_to?(:metadata) - s.metadata = { - 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/rack-protection', - 'homepage_uri' => 'http://sinatrarb.com/protection/', - 'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection' - } - else - raise <<-EOF + unless s.respond_to?(:metadata) + raise <<-WARN RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: gem install rubygems-update update_rubygems: gem update --system -EOF + WARN end + s.metadata = { + 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/rack-protection', + 'homepage_uri' => 'http://sinatrarb.com/protection/', + 'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection', + 'rubygems_mfa_required' => 'true' + } + + s.required_ruby_version = '>= 2.6.0' + # dependencies - s.add_dependency "rack" - s.add_development_dependency "rack-test", "~> 2" - s.add_development_dependency "rspec", "~> 3" + s.add_dependency 'rack' + s.add_development_dependency 'rack-test', '~> 2' + s.add_development_dependency 'rspec', '~> 3' end diff --git a/rack-protection/spec/lib/rack/protection/authenticity_token_spec.rb b/rack-protection/spec/lib/rack/protection/authenticity_token_spec.rb index 433e6ea58f..e589726be5 100644 --- a/rack-protection/spec/lib/rack/protection/authenticity_token_spec.rb +++ b/rack-protection/spec/lib/rack/protection/authenticity_token_spec.rb @@ -1,68 +1,70 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::AuthenticityToken do let(:token) { described_class.random_token } let(:masked_token) { described_class.token(session) } - let(:bad_token) { Base64.strict_encode64("badtoken") } - let(:session) { {:csrf => token} } + let(:bad_token) { Base64.strict_encode64('badtoken') } + let(:session) { { csrf: token } } - it_behaves_like "any rack application" + it_behaves_like 'any rack application' - it "denies post requests without any token" do + it 'denies post requests without any token' do expect(post('/')).not_to be_ok end - it "accepts post requests with correct X-CSRF-Token header" do + it 'accepts post requests with correct X-CSRF-Token header' do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => token) expect(last_response).to be_ok end - it "accepts post requests with masked X-CSRF-Token header" do + it 'accepts post requests with masked X-CSRF-Token header' do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => masked_token) expect(last_response).to be_ok end - it "denies post requests with wrong X-CSRF-Token header" do + it 'denies post requests with wrong X-CSRF-Token header' do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => bad_token) expect(last_response).not_to be_ok end - it "accepts post form requests with correct authenticity_token field" do - post('/', {"authenticity_token" => token}, 'rack.session' => session) + it 'accepts post form requests with correct authenticity_token field' do + post('/', { 'authenticity_token' => token }, 'rack.session' => session) expect(last_response).to be_ok end - it "accepts post form requests with masked authenticity_token field" do - post('/', {"authenticity_token" => masked_token}, 'rack.session' => session) + it 'accepts post form requests with masked authenticity_token field' do + post('/', { 'authenticity_token' => masked_token }, 'rack.session' => session) expect(last_response).to be_ok end - it "denies post form requests with wrong authenticity_token field" do - post('/', {"authenticity_token" => bad_token}, 'rack.session' => session) + it 'denies post form requests with wrong authenticity_token field' do + post('/', { 'authenticity_token' => bad_token }, 'rack.session' => session) expect(last_response).not_to be_ok end - it "accepts post form requests with a valid per form token" do + it 'accepts post form requests with a valid per form token' do token = Rack::Protection::AuthenticityToken.token(session, path: '/foo') - post('/foo', {"authenticity_token" => token}, 'rack.session' => session) + post('/foo', { 'authenticity_token' => token }, 'rack.session' => session) expect(last_response).to be_ok end - it "denies post form requests with an invalid per form token" do + it 'denies post form requests with an invalid per form token' do token = Rack::Protection::AuthenticityToken.token(session, path: '/foo') - post('/bar', {"authenticity_token" => token}, 'rack.session' => session) + post('/bar', { 'authenticity_token' => token }, 'rack.session' => session) expect(last_response).not_to be_ok end - it "prevents ajax requests without a valid token" do - expect(post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")).not_to be_ok + it 'prevents ajax requests without a valid token' do + expect(post('/', {}, 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest')).not_to be_ok end - it "allows for a custom authenticity token param" do + it 'allows for a custom authenticity token param' do mock_app do - use Rack::Protection::AuthenticityToken, :authenticity_param => 'csrf_param' - run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] } + use Rack::Protection::AuthenticityToken, authenticity_param: 'csrf_param' + run proc { |_e| [200, { 'Content-Type' => 'text/plain' }, ['hi']] } end - post('/', {"csrf_param" => token}, 'rack.session' => {:csrf => token}) + post('/', { 'csrf_param' => token }, 'rack.session' => { csrf: token }) expect(last_response).to be_ok end @@ -71,10 +73,10 @@ expect(env['rack.session'][:csrf]).not_to be_nil end - it "allows for a custom token session key" do + it 'allows for a custom token session key' do mock_app do - use Rack::Session::Cookie, :key => 'rack.session' - use Rack::Protection::AuthenticityToken, :key => :_csrf + use Rack::Session::Cookie, key: 'rack.session' + use Rack::Protection::AuthenticityToken, key: :_csrf run DummyApp end @@ -82,12 +84,12 @@ expect(env['rack.session'][:_csrf]).not_to be_nil end - describe ".token" do - it "returns a unique masked version of the authenticity token" do + describe '.token' do + it 'returns a unique masked version of the authenticity token' do expect(Rack::Protection::AuthenticityToken.token(session)).not_to eq(masked_token) end - it "sets a session authenticity token if one does not exist" do + it 'sets a session authenticity token if one does not exist' do session = {} allow(Rack::Protection::AuthenticityToken).to receive(:random_token).and_return(token) allow_any_instance_of(Rack::Protection::AuthenticityToken).to receive(:mask_token).and_return(masked_token) @@ -96,8 +98,8 @@ end end - describe ".random_token" do - it "generates a base64 encoded 32 character string" do + describe '.random_token' do + it 'generates a base64 encoded 32 character string' do expect(Base64.urlsafe_decode64(token).length).to eq(32) end end diff --git a/rack-protection/spec/lib/rack/protection/base_spec.rb b/rack-protection/spec/lib/rack/protection/base_spec.rb index 46a4d901d4..c4b8f8c86e 100644 --- a/rack-protection/spec/lib/rack/protection/base_spec.rb +++ b/rack-protection/spec/lib/rack/protection/base_spec.rb @@ -1,37 +1,38 @@ -RSpec.describe Rack::Protection::Base do +# frozen_string_literal: true - subject { described_class.new(lambda {}) } +RSpec.describe Rack::Protection::Base do + subject { described_class.new(-> {}) } - describe "#random_string" do - it "outputs a string of 32 characters" do + describe '#random_string' do + it 'outputs a string of 32 characters' do expect(subject.random_string.length).to eq(32) end end - describe "#referrer" do - it "Reads referrer from Referer header" do - env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/valid"} - expect(subject.referrer(env)).to eq("bar.com") + describe '#referrer' do + it 'Reads referrer from Referer header' do + env = { 'HTTP_HOST' => 'foo.com', 'HTTP_REFERER' => 'http://bar.com/valid' } + expect(subject.referrer(env)).to eq('bar.com') end - it "Reads referrer from Host header when Referer header is relative" do - env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "/valid"} - expect(subject.referrer(env)).to eq("foo.com") + it 'Reads referrer from Host header when Referer header is relative' do + env = { 'HTTP_HOST' => 'foo.com', 'HTTP_REFERER' => '/valid' } + expect(subject.referrer(env)).to eq('foo.com') end - it "Reads referrer from Host header when Referer header is missing" do - env = {"HTTP_HOST" => "foo.com"} - expect(subject.referrer(env)).to eq("foo.com") + it 'Reads referrer from Host header when Referer header is missing' do + env = { 'HTTP_HOST' => 'foo.com' } + expect(subject.referrer(env)).to eq('foo.com') end - it "Returns nil when Referer header is missing and allow_empty_referrer is false" do - env = {"HTTP_HOST" => "foo.com"} + it 'Returns nil when Referer header is missing and allow_empty_referrer is false' do + env = { 'HTTP_HOST' => 'foo.com' } subject.options[:allow_empty_referrer] = false expect(subject.referrer(env)).to be_nil end - it "Returns nil when Referer header is invalid" do - env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/bad|uri"} + it 'Returns nil when Referer header is invalid' do + env = { 'HTTP_HOST' => 'foo.com', 'HTTP_REFERER' => 'http://bar.com/bad|uri' } expect(subject.referrer(env)).to be_nil end end diff --git a/rack-protection/spec/lib/rack/protection/content_security_policy_spec.rb b/rack-protection/spec/lib/rack/protection/content_security_policy_spec.rb index 0df46ba83b..35a26ad837 100644 --- a/rack-protection/spec/lib/rack/protection/content_security_policy_spec.rb +++ b/rack-protection/spec/lib/rack/protection/content_security_policy_spec.rb @@ -1,66 +1,68 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::ContentSecurityPolicy do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' it 'should set the Content Security Policy' do expect( - get('/', {}, 'wants' => 'text/html').headers["Content-Security-Policy"] + get('/', {}, 'wants' => 'text/html').headers['Content-Security-Policy'] ).to eq("default-src 'self'") end it 'should not set the Content Security Policy for other content types' do headers = get('/', {}, 'wants' => 'text/foo').headers - expect(headers["Content-Security-Policy"]).to be_nil - expect(headers["Content-Security-Policy-Report-Only"]).to be_nil + expect(headers['Content-Security-Policy']).to be_nil + expect(headers['Content-Security-Policy-Report-Only']).to be_nil end it 'should allow changing the protection settings' do mock_app do - use Rack::Protection::ContentSecurityPolicy, :default_src => 'none', :script_src => 'https://cdn.mybank.net', :style_src => 'https://cdn.mybank.net', :img_src => 'https://cdn.mybank.net', :connect_src => 'https://api.mybank.com', :frame_src => 'self', :font_src => 'https://cdn.mybank.net', :object_src => 'https://cdn.mybank.net', :media_src => 'https://cdn.mybank.net', :report_uri => '/my_amazing_csp_report_parser', :sandbox => 'allow-scripts' + use Rack::Protection::ContentSecurityPolicy, default_src: 'none', script_src: 'https://cdn.mybank.net', style_src: 'https://cdn.mybank.net', img_src: 'https://cdn.mybank.net', connect_src: 'https://api.mybank.com', frame_src: 'self', font_src: 'https://cdn.mybank.net', object_src: 'https://cdn.mybank.net', media_src: 'https://cdn.mybank.net', report_uri: '/my_amazing_csp_report_parser', sandbox: 'allow-scripts' run DummyApp end headers = get('/', {}, 'wants' => 'text/html').headers - expect(headers["Content-Security-Policy"]).to eq("connect-src https://api.mybank.com; default-src none; font-src https://cdn.mybank.net; frame-src self; img-src https://cdn.mybank.net; media-src https://cdn.mybank.net; object-src https://cdn.mybank.net; report-uri /my_amazing_csp_report_parser; sandbox allow-scripts; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net") - expect(headers["Content-Security-Policy-Report-Only"]).to be_nil + expect(headers['Content-Security-Policy']).to eq('connect-src https://api.mybank.com; default-src none; font-src https://cdn.mybank.net; frame-src self; img-src https://cdn.mybank.net; media-src https://cdn.mybank.net; object-src https://cdn.mybank.net; report-uri /my_amazing_csp_report_parser; sandbox allow-scripts; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net') + expect(headers['Content-Security-Policy-Report-Only']).to be_nil end it 'should allow setting CSP3 no arg directives' do mock_app do - use Rack::Protection::ContentSecurityPolicy, :block_all_mixed_content => true, :disown_opener => true, :upgrade_insecure_requests => true + use Rack::Protection::ContentSecurityPolicy, block_all_mixed_content: true, disown_opener: true, upgrade_insecure_requests: true run DummyApp end headers = get('/', {}, 'wants' => 'text/html').headers - expect(headers["Content-Security-Policy"]).to eq("block-all-mixed-content; default-src 'self'; disown-opener; upgrade-insecure-requests") + expect(headers['Content-Security-Policy']).to eq("block-all-mixed-content; default-src 'self'; disown-opener; upgrade-insecure-requests") end it 'should ignore CSP3 no arg directives unless they are set to true' do mock_app do - use Rack::Protection::ContentSecurityPolicy, :block_all_mixed_content => false, :disown_opener => 'false', :upgrade_insecure_requests => 'foo' + use Rack::Protection::ContentSecurityPolicy, block_all_mixed_content: false, disown_opener: 'false', upgrade_insecure_requests: 'foo' run DummyApp end headers = get('/', {}, 'wants' => 'text/html').headers - expect(headers["Content-Security-Policy"]).to eq("default-src 'self'") + expect(headers['Content-Security-Policy']).to eq("default-src 'self'") end it 'should allow changing report only' do # I have no clue what other modes are available mock_app do - use Rack::Protection::ContentSecurityPolicy, :report_uri => '/my_amazing_csp_report_parser', :report_only => true + use Rack::Protection::ContentSecurityPolicy, report_uri: '/my_amazing_csp_report_parser', report_only: true run DummyApp end headers = get('/', {}, 'wants' => 'text/html').headers - expect(headers["Content-Security-Policy"]).to be_nil - expect(headers["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; report-uri /my_amazing_csp_report_parser") + expect(headers['Content-Security-Policy']).to be_nil + expect(headers['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; report-uri /my_amazing_csp_report_parser") end it 'should not override the header if already set' do - mock_app with_headers("Content-Security-Policy" => "default-src: none") - expect(get('/', {}, 'wants' => 'text/html').headers["Content-Security-Policy"]).to eq("default-src: none") + mock_app with_headers('Content-Security-Policy' => 'default-src: none') + expect(get('/', {}, 'wants' => 'text/html').headers['Content-Security-Policy']).to eq('default-src: none') end end diff --git a/rack-protection/spec/lib/rack/protection/cookie_tossing_spec.rb b/rack-protection/spec/lib/rack/protection/cookie_tossing_spec.rb index af6d888296..f24b9920b6 100644 --- a/rack-protection/spec/lib/rack/protection/cookie_tossing_spec.rb +++ b/rack-protection/spec/lib/rack/protection/cookie_tossing_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::CookieTossing do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' context 'with default reaction' do before(:each) do @@ -34,7 +36,7 @@ rack.session=; domain=example.org; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT rack.session=; domain=example.org; path=/some; expires=Thu, 01 Jan 1970 00:00:00 GMT rack.session=; domain=example.org; path=/some/path; expires=Thu, 01 Jan 1970 00:00:00 GMT -END + END expect(last_response.headers['Set-Cookie']).to eq(expected_header) end end @@ -42,7 +44,7 @@ context 'with redirect reaction' do before(:each) do mock_app do - use Rack::Protection::CookieTossing, :reaction => :redirect + use Rack::Protection::CookieTossing, reaction: :redirect run DummyApp end end @@ -63,7 +65,7 @@ context 'with custom session key' do it 'denies requests with duplicate session cookies' do mock_app do - use Rack::Protection::CookieTossing, :session_key => '_session' + use Rack::Protection::CookieTossing, session_key: '_session' run DummyApp end diff --git a/rack-protection/spec/lib/rack/protection/encrypted_cookie_spec.rb b/rack-protection/spec/lib/rack/protection/encrypted_cookie_spec.rb index 726687c2ac..de2a659bc1 100644 --- a/rack-protection/spec/lib/rack/protection/encrypted_cookie_spec.rb +++ b/rack-protection/spec/lib/rack/protection/encrypted_cookie_spec.rb @@ -1,77 +1,79 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::EncryptedCookie do let(:incrementor) do lambda do |env| - env["rack.session"]["counter"] ||= 0 - env["rack.session"]["counter"] += 1 - hash = env["rack.session"].dup - hash.delete("session_id") + env['rack.session']['counter'] ||= 0 + env['rack.session']['counter'] += 1 + hash = env['rack.session'].dup + hash.delete('session_id') Rack::Response.new(hash.inspect).to_a end end let(:session_id) do lambda do |env| - Rack::Response.new(env["rack.session"].to_hash.inspect).to_a + Rack::Response.new(env['rack.session'].to_hash.inspect).to_a end end let(:session_option) do lambda do |opt| lambda do |env| - Rack::Response.new(env["rack.session.options"][opt].inspect).to_a + Rack::Response.new(env['rack.session.options'][opt].inspect).to_a end end end let(:nothing) do - lambda do |env| - Rack::Response.new("Nothing").to_a + lambda do |_env| + Rack::Response.new('Nothing').to_a end end let(:renewer) do lambda do |env| - env["rack.session.options"][:renew] = true - Rack::Response.new("Nothing").to_a + env['rack.session.options'][:renew] = true + Rack::Response.new('Nothing').to_a end end let(:only_session_id) do lambda do |env| - Rack::Response.new(env["rack.session"]["session_id"].to_s).to_a + Rack::Response.new(env['rack.session']['session_id'].to_s).to_a end end let(:bigcookie) do lambda do |env| - env["rack.session"]["cookie"] = "big" * 3000 - Rack::Response.new(env["rack.session"].inspect).to_a + env['rack.session']['cookie'] = 'big' * 3000 + Rack::Response.new(env['rack.session'].inspect).to_a end end let(:destroy_session) do lambda do |env| - env["rack.session"].destroy - Rack::Response.new("Nothing").to_a + env['rack.session'].destroy + Rack::Response.new('Nothing').to_a end end - def response_for(options={}) + def response_for(options = {}) request_options = options.fetch(:request, {}) cookie = if options[:cookie].is_a?(Rack::Response) - options[:cookie]["Set-Cookie"] - else - options[:cookie] - end - request_options["HTTP_COOKIE"] = cookie || "" + options[:cookie]['Set-Cookie'] + else + options[:cookie] + end + request_options['HTTP_COOKIE'] = cookie || '' app_with_cookie = Rack::Protection::EncryptedCookie.new(*options[:app]) app_with_cookie = Rack::Lint.new(app_with_cookie) - Rack::MockRequest.new(app_with_cookie).get("/", request_options) + Rack::MockRequest.new(app_with_cookie).get('/', request_options) end def random_cipher_secret - OpenSSL::Cipher.new('aes-256-gcm').random_key.unpack('H*').first + OpenSSL::Cipher.new('aes-256-gcm').random_key.unpack1('H*') end let(:secret) { random_cipher_secret } @@ -99,7 +101,7 @@ def random_cipher_secret it 'uses base64 to decode' do coder = Rack::Protection::EncryptedCookie::Base64.new str = ['fuuuuu'].pack('m0') - expect(coder.decode(str)).to eq(str.unpack('m0').first) + expect(coder.decode(str)).to eq(str.unpack1('m0')) end it 'handles non-strict base64 encoding' do @@ -118,7 +120,7 @@ def random_cipher_secret it 'marshals and base64 decodes' do coder = Rack::Protection::EncryptedCookie::Base64::Marshal.new str = [::Marshal.dump('fuuuuu')].pack('m0') - expect(coder.decode(str)).to eq(::Marshal.load(str.unpack('m0').first)) + expect(coder.decode(str)).to eq(::Marshal.load(str.unpack1('m0'))) end it 'rescues failures on decode' do @@ -137,7 +139,7 @@ def random_cipher_secret it 'JSON and base64 decodes' do coder = Rack::Protection::EncryptedCookie::Base64::JSON.new str = [::JSON.dump(%w[fuuuuu])].pack('m0') - expect(coder.decode(str)).to eq(::JSON.parse(str.unpack('m0').first)) + expect(coder.decode(str)).to eq(::JSON.parse(str.unpack1('m0'))) end it 'rescues failures on decode' do @@ -169,7 +171,7 @@ def random_cipher_secret end end - it "warns if no secret is given" do + it 'warns if no secret is given' do Rack::Protection::EncryptedCookie.new(incrementor) expect(warnings.first).to match(/no secret/i) warnings.clear @@ -178,7 +180,7 @@ def random_cipher_secret end it 'warns if secret is to short' do - Rack::Protection::EncryptedCookie.new(incrementor, secret: secret[0,16]) + Rack::Protection::EncryptedCookie.new(incrementor, secret: secret[0, 16]) expect(warnings.first).to match(/secret is not long enough/i) warnings.clear Rack::Protection::EncryptedCookie.new(incrementor, secret: secret) @@ -192,38 +194,46 @@ def random_cipher_secret expect(warnings).to be_empty end - it "still warns if coder is not set" do + it 'still warns if coder is not set' do Rack::Protection::EncryptedCookie.new( incrementor, - let_coder_handle_secure_encoding: true) + let_coder_handle_secure_encoding: true + ) expect(warnings.first).to match(/no secret/i) end it 'uses a coder' do - identity = Class.new { + identity = Class.new do attr_reader :calls def initialize @calls = [] end - def encode(str); @calls << :encode; str; end - def decode(str); @calls << :decode; str; end - }.new + def encode(str) + @calls << :encode + str + end + + def decode(str) + @calls << :decode + str + end + end.new response = response_for(app: [incrementor, { coder: identity }]) - expect(response["Set-Cookie"]).to include("rack.session=") + expect(response['Set-Cookie']).to include('rack.session=') expect(response.body).to eq('{"counter"=>1}') - expect(identity.calls).to eq([:decode, :encode]) + expect(identity.calls).to eq(%i[decode encode]) end - it "creates a new cookie" do + it 'creates a new cookie' do response = response_for(app: incrementor) - expect(response["Set-Cookie"]).to include("rack.session=") + expect(response['Set-Cookie']).to include('rack.session=') expect(response.body).to eq('{"counter"=>1}') end - it "loads from a cookie" do + it 'loads from a cookie' do response = response_for(app: incrementor) response = response_for(app: incrementor, cookie: response) @@ -233,58 +243,58 @@ def decode(str); @calls << :decode; str; end expect(response.body).to eq('{"counter"=>3}') end - it "renew session id" do + it 'renew session id' do response = response_for(app: incrementor) cookie = response['Set-Cookie'] response = response_for(app: only_session_id, cookie: cookie) cookie = response['Set-Cookie'] if response['Set-Cookie'] - expect(response.body).to_not eq("") + expect(response.body).to_not eq('') old_session_id = response.body response = response_for(app: renewer, cookie: cookie) cookie = response['Set-Cookie'] if response['Set-Cookie'] response = response_for(app: only_session_id, cookie: cookie) - expect(response.body).to_not eq("") + expect(response.body).to_not eq('') expect(response.body).to_not eq(old_session_id) end - it "destroys session" do + it 'destroys session' do response = response_for(app: incrementor) response = response_for(app: only_session_id, cookie: response) - expect(response.body).to_not eq("") + expect(response.body).to_not eq('') old_session_id = response.body response = response_for(app: destroy_session, cookie: response) response = response_for(app: only_session_id, cookie: response) - expect(response.body).to_not eq("") + expect(response.body).to_not eq('') expect(response.body).to_not eq(old_session_id) end - it "survives broken cookies" do + it 'survives broken cookies' do response = response_for( app: incrementor, - cookie: "rack.session=blarghfasel" + cookie: 'rack.session=blarghfasel' ) expect(response.body).to eq('{"counter"=>1}') response = response_for( app: [incrementor, { secret: secret }], - cookie: "rack.session=" + cookie: 'rack.session=' ) expect(response.body).to eq('{"counter"=>1}') end - it "barks on too big cookies" do - expect { + it 'barks on too big cookies' do + expect do response_for(app: bigcookie, request: { fatal: true }) - }.to raise_error Rack::MockRequest::FatalWarning + end.to raise_error Rack::MockRequest::FatalWarning end - it "loads from a cookie with integrity hash" do + it 'loads from a cookie with integrity hash' do app = [incrementor, { secret: secret }] response = response_for(app: app) @@ -300,7 +310,7 @@ def decode(str); @calls << :decode; str; end expect(response.body).to eq('{"counter"=>1}') end - it "loads from a cookie with accept-only integrity hash for graceful key rotation" do + it 'loads from a cookie with accept-only integrity hash for graceful key rotation' do response = response_for(app: [incrementor, { secret: secret }]) new_secret = random_cipher_secret @@ -319,7 +329,7 @@ def decode(str); @calls << :decode; str; end it 'loads from a legacy hmac cookie' do legacy_session = Rack::Protection::EncryptedCookie::Base64::Marshal.new.encode({ 'counter' => 1, 'session_id' => 'abcdef' }) legacy_secret = 'test legacy secret' - legacy_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, legacy_secret, legacy_session) + legacy_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA1'), legacy_secret, legacy_session) legacy_cookie = "rack.session=#{legacy_session}--#{legacy_digest}; path=/; HttpOnly" @@ -328,7 +338,7 @@ def decode(str); @calls << :decode; str; end expect(response.body).to eq('{"counter"=>2}') end - it "ignores tampered with session cookies" do + it 'ignores tampered with session cookies' do app = [incrementor, { secret: secret }] response = response_for(app: app) expect(response.body).to eq('{"counter"=>1}') @@ -336,7 +346,7 @@ def decode(str); @calls << :decode; str; end response = response_for(app: app, cookie: response) expect(response.body).to eq('{"counter"=>2}') - ctxt, iv, auth_tag = response["Set-Cookie"].split("--", 3) + ctxt, iv, auth_tag = response['Set-Cookie'].split('--', 3) tampered_with_cookie = [ctxt, iv, auth_tag.reverse].join('--') response = response_for(app: app, cookie: tampered_with_cookie) @@ -346,7 +356,7 @@ def decode(str); @calls << :decode; str; end it 'ignores tampered with legacy hmac cookie' do legacy_session = Rack::Protection::EncryptedCookie::Base64::Marshal.new.encode({ 'counter' => 1, 'session_id' => 'abcdef' }) legacy_secret = 'test legacy secret' - legacy_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, legacy_secret, legacy_session).reverse + legacy_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA1'), legacy_secret, legacy_session).reverse legacy_cookie = "rack.session=#{legacy_session}--#{legacy_digest}; path=/; HttpOnly" @@ -355,7 +365,7 @@ def decode(str); @calls << :decode; str; end expect(response.body).to eq('{"counter"=>1}') end - it "supports either of secret or old_secret" do + it 'supports either of secret or old_secret' do app = [incrementor, { secret: secret }] response = response_for(app: app) expect(response.body).to eq('{"counter"=>1}') @@ -371,7 +381,7 @@ def decode(str); @calls << :decode; str; end expect(response.body).to eq('{"counter"=>2}') end - it "supports custom digest class for legacy hmac cookie" do + it 'supports custom digest class for legacy hmac cookie' do legacy_hmac = OpenSSL::Digest::SHA256 legacy_session = Rack::Protection::EncryptedCookie::Base64::Marshal.new.encode({ 'counter' => 1, 'session_id' => 'abcdef' }) legacy_secret = 'test legacy secret' @@ -379,7 +389,8 @@ def decode(str); @calls << :decode; str; end legacy_cookie = "rack.session=#{Rack::Utils.escape legacy_session}--#{legacy_digest}; path=/; HttpOnly" app = [incrementor, { - secret: secret, legacy_hmac_secret: legacy_secret, legacy_hmac: legacy_hmac }] + secret: secret, legacy_hmac_secret: legacy_secret, legacy_hmac: legacy_hmac + }] response = response_for(app: app, cookie: legacy_cookie) expect(response.body).to eq('{"counter"=>2}') @@ -388,7 +399,7 @@ def decode(str); @calls << :decode; str; end expect(response.body).to eq('{"counter"=>3}') end - it "can handle Rack::Lint middleware" do + it 'can handle Rack::Lint middleware' do response = response_for(app: incrementor) lint = Rack::Lint.new(session_id) @@ -396,11 +407,12 @@ def decode(str); @calls << :decode; str; end expect(response.body).to_not be_nil end - it "can handle middleware that inspects the env" do + it 'can handle middleware that inspects the env' do class TestEnvInspector def initialize(app) @app = app end + def call(env) env.inspect @app.call(env) @@ -414,7 +426,7 @@ def call(env) expect(response.body).to_not be_nil end - it "returns the session id in the session hash" do + it 'returns the session id in the session hash' do response = response_for(app: incrementor) expect(response.body).to eq('{"counter"=>1}') @@ -423,38 +435,38 @@ def call(env) expect(response.body).to match(/"counter"=>1/) end - it "does not return a cookie if set to secure but not using ssl" do + it 'does not return a cookie if set to secure but not using ssl' do app = [incrementor, { secure: true }] response = response_for(app: app) - expect(response["Set-Cookie"]).to be_nil + expect(response['Set-Cookie']).to be_nil - response = response_for(app: app, request: { "HTTPS" => "on" }) - expect(response["Set-Cookie"]).to_not be_nil - expect(response["Set-Cookie"]).to match(/secure/) + response = response_for(app: app, request: { 'HTTPS' => 'on' }) + expect(response['Set-Cookie']).to_not be_nil + expect(response['Set-Cookie']).to match(/secure/) end - it "does not return a cookie if cookie was not read/written" do + it 'does not return a cookie if cookie was not read/written' do response = response_for(app: nothing) - expect(response["Set-Cookie"]).to be_nil + expect(response['Set-Cookie']).to be_nil end - it "does not return a cookie if cookie was not written (only read)" do + it 'does not return a cookie if cookie was not written (only read)' do response = response_for(app: session_id) - expect(response["Set-Cookie"]).to be_nil + expect(response['Set-Cookie']).to be_nil end - it "returns even if not read/written if :expire_after is set" do + it 'returns even if not read/written if :expire_after is set' do app = [nothing, { expire_after: 3600 }] - request = { "rack.session" => { "not" => "empty" }} + request = { 'rack.session' => { 'not' => 'empty' } } response = response_for(app: app, request: request) - expect(response["Set-Cookie"]).to_not be_nil + expect(response['Set-Cookie']).to_not be_nil end - it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do + it 'returns no cookie if no data was written and no session was created previously, even if :expire_after is set' do app = [nothing, { expire_after: 3600 }] response = response_for(app: app) - expect(response["Set-Cookie"]).to be_nil + expect(response['Set-Cookie']).to be_nil end it "exposes :secret in env['rack.session.option']" do @@ -472,14 +484,14 @@ def call(env) expect(response.body).to match(/Marshal/) end - it "allows passing in a hash with session data from middleware in front" do - request = { 'rack.session' => { foo: 'bar' }} + it 'allows passing in a hash with session data from middleware in front' do + request = { 'rack.session' => { foo: 'bar' } } response = response_for(app: session_id, request: request) expect(response.body).to match(/foo/) end - it "allows modifying session data with session data from middleware in front" do - request = { 'rack.session' => { foo: 'bar' }} + it 'allows modifying session data with session data from middleware in front' do + request = { 'rack.session' => { foo: 'bar' } } response = response_for(app: incrementor, request: request) expect(response.body).to match(/counter/) expect(response.body).to match(/foo/) @@ -488,41 +500,42 @@ def call(env) it "allows more than one '--' in the cookie when calculating legacy digests" do @counter = 0 app = lambda do |env| - env["rack.session"]["message"] ||= "" - env["rack.session"]["message"] << "#{(@counter += 1).to_s}--" - hash = env["rack.session"].dup - hash.delete("session_id") - Rack::Response.new(hash["message"]).to_a + env['rack.session']['message'] ||= '' + env['rack.session']['message'] << "#{@counter += 1}--" + hash = env['rack.session'].dup + hash.delete('session_id') + Rack::Response.new(hash['message']).to_a end # another example of an unsafe coder is Base64.urlsafe_encode64 - unsafe_coder = Class.new { + unsafe_coder = Class.new do def encode(hash); hash.inspect end def decode(str); eval(str) if str; end - }.new + end.new legacy_session = unsafe_coder.encode('message' => "#{@counter += 1}--#{@counter += 1}--", 'session_id' => 'abcdef') legacy_secret = 'test legacy secret' - legacy_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, legacy_secret, legacy_session) + legacy_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA1'), legacy_secret, legacy_session) legacy_cookie = "rack.session=#{Rack::Utils.escape legacy_session}--#{legacy_digest}; path=/; HttpOnly" - _app = [ app, { + _app = [app, { secret: secret, legacy_hmac_secret: legacy_secret, - legacy_hmac_coder: unsafe_coder } ] + legacy_hmac_coder: unsafe_coder + }] response = response_for(app: _app, cookie: legacy_cookie) - expect(response.body).to eq("1--2--3--") + expect(response.body).to eq('1--2--3--') end it 'allows for non-strict encoded cookie' do long_session_app = lambda do |env| env['rack.session']['value'] = 'A' * 256 env['rack.session']['counter'] = 1 - hash = env["rack.session"].dup - hash.delete("session_id") + hash = env['rack.session'].dup + hash.delete('session_id') Rack::Response.new(hash.inspect).to_a end - non_strict_coder = Class.new { + non_strict_coder = Class.new do def encode(str) [Marshal.dump(str)].pack('m') end @@ -530,19 +543,19 @@ def encode(str) def decode(str) return unless str - Marshal.load(str.unpack('m').first) + Marshal.load(str.unpack1('m')) end - }.new + end.new non_strict_response = response_for(app: [ - long_session_app, { coder: non_strict_coder } - ]) + long_session_app, { coder: non_strict_coder } + ]) response = response_for(app: [ - incrementor - ], cookie: non_strict_response) + incrementor + ], cookie: non_strict_response) - expect(response.body).to match(%Q["value"=>"#{'A' * 256}"]) + expect(response.body).to match(%("value"=>"#{'A' * 256}")) expect(response.body).to match('"counter"=>2') expect(response.body).to match(/\A{[^}]+}\z/) end diff --git a/rack-protection/spec/lib/rack/protection/encryptor_spec.rb b/rack-protection/spec/lib/rack/protection/encryptor_spec.rb index ce11d75e74..cb7a2a401f 100644 --- a/rack-protection/spec/lib/rack/protection/encryptor_spec.rb +++ b/rack-protection/spec/lib/rack/protection/encryptor_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::Encryptor do - let(:secret) { + let(:secret) do OpenSSL::Cipher.new(Rack::Protection::Encryptor::CIPHER).random_key - } + end it 'encrypted message contains ciphertext iv and auth_tag' do msg = Rack::Protection::Encryptor.encrypt_message('hello world', secret) diff --git a/rack-protection/spec/lib/rack/protection/escaped_params_spec.rb b/rack-protection/spec/lib/rack/protection/escaped_params_spec.rb index 01c4f3a672..d6b1727b3e 100644 --- a/rack-protection/spec/lib/rack/protection/escaped_params_spec.rb +++ b/rack-protection/spec/lib/rack/protection/escaped_params_spec.rb @@ -1,37 +1,39 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::EscapedParams do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' context 'escaping' do it 'escapes html entities' do mock_app do |env| request = Rack::Request.new(env) - [200, {'Content-Type' => 'text/plain'}, [request.params['foo']]] + [200, { 'Content-Type' => 'text/plain' }, [request.params['foo']]] end - get '/', :foo => "" + get '/', foo: '' expect(body).to eq('<bar>') end it 'leaves normal params untouched' do mock_app do |env| request = Rack::Request.new(env) - [200, {'Content-Type' => 'text/plain'}, [request.params['foo']]] + [200, { 'Content-Type' => 'text/plain' }, [request.params['foo']]] end - get '/', :foo => "bar" + get '/', foo: 'bar' expect(body).to eq('bar') end it 'copes with nested arrays' do mock_app do |env| request = Rack::Request.new(env) - [200, {'Content-Type' => 'text/plain'}, [request.params['foo']['bar']]] + [200, { 'Content-Type' => 'text/plain' }, [request.params['foo']['bar']]] end - get '/', :foo => {:bar => ""} + get '/', foo: { bar: '' } expect(body).to eq('<bar>') end it 'leaves cache-breaker params untouched' do - mock_app do |env| - [200, {'Content-Type' => 'text/plain'}, ['hi']] + mock_app do |_env| + [200, { 'Content-Type' => 'text/plain' }, ['hi']] end get '/?95df8d9bf5237ad08df3115ee74dcb10' @@ -41,9 +43,7 @@ it 'leaves TempFiles untouched' do mock_app do |env| request = Rack::Request.new(env) - [200, {'Content-Type' => 'text/plain'}, [request.params['file'][:filename] + "\n" + \ - request.params['file'][:tempfile].read + "\n" + \ - request.params['other']]] + [200, { 'Content-Type' => 'text/plain' }, ["#{request.params['file'][:filename]}\n#{request.params['file'][:tempfile].read}\n#{request.params['other']}"]] end temp_file = File.open('_escaped_params_tmp_file', 'w') @@ -51,7 +51,7 @@ temp_file.write('hello world') temp_file.close - post '/', :file => Rack::Test::UploadedFile.new(temp_file.path), :other => '' + post '/', file: Rack::Test::UploadedFile.new(temp_file.path), other: '' expect(body).to eq("_escaped_params_tmp_file\nhello world\n<bar>") ensure File.unlink(temp_file.path) diff --git a/rack-protection/spec/lib/rack/protection/form_token_spec.rb b/rack-protection/spec/lib/rack/protection/form_token_spec.rb index b9a340da8a..b85923a99f 100644 --- a/rack-protection/spec/lib/rack/protection/form_token_spec.rb +++ b/rack-protection/spec/lib/rack/protection/form_token_spec.rb @@ -1,46 +1,48 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::FormToken do let(:token) { described_class.random_token } let(:masked_token) { described_class.token(session) } - let(:bad_token) { Base64.strict_encode64("badtoken") } - let(:session) { {:csrf => token} } + let(:bad_token) { Base64.strict_encode64('badtoken') } + let(:session) { { csrf: token } } - it_behaves_like "any rack application" + it_behaves_like 'any rack application' - it "denies post requests without any token" do + it 'denies post requests without any token' do expect(post('/')).not_to be_ok end - it "accepts post requests with correct X-CSRF-Token header" do + it 'accepts post requests with correct X-CSRF-Token header' do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => token) expect(last_response).to be_ok end - it "accepts post requests with masked X-CSRF-Token header" do + it 'accepts post requests with masked X-CSRF-Token header' do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => masked_token) expect(last_response).to be_ok end - it "denies post requests with wrong X-CSRF-Token header" do + it 'denies post requests with wrong X-CSRF-Token header' do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => bad_token) expect(last_response).not_to be_ok end - it "accepts post form requests with correct authenticity_token field" do - post('/', {"authenticity_token" => token}, 'rack.session' => session) + it 'accepts post form requests with correct authenticity_token field' do + post('/', { 'authenticity_token' => token }, 'rack.session' => session) expect(last_response).to be_ok end - it "accepts post form requests with masked authenticity_token field" do - post('/', {"authenticity_token" => masked_token}, 'rack.session' => session) + it 'accepts post form requests with masked authenticity_token field' do + post('/', { 'authenticity_token' => masked_token }, 'rack.session' => session) expect(last_response).to be_ok end - it "denies post form requests with wrong authenticity_token field" do - post('/', {"authenticity_token" => bad_token}, 'rack.session' => session) + it 'denies post form requests with wrong authenticity_token field' do + post('/', { 'authenticity_token' => bad_token }, 'rack.session' => session) expect(last_response).not_to be_ok end - it "accepts ajax requests without a valid token" do - expect(post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")).to be_ok + it 'accepts ajax requests without a valid token' do + expect(post('/', {}, 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest')).to be_ok end end diff --git a/rack-protection/spec/lib/rack/protection/frame_options_spec.rb b/rack-protection/spec/lib/rack/protection/frame_options_spec.rb index 367bf36414..23ba8f11ce 100644 --- a/rack-protection/spec/lib/rack/protection/frame_options_spec.rb +++ b/rack-protection/spec/lib/rack/protection/frame_options_spec.rb @@ -1,37 +1,38 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::FrameOptions do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' it 'should set the X-Frame-Options' do - expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("SAMEORIGIN") + expect(get('/', {}, 'wants' => 'text/html').headers['X-Frame-Options']).to eq('SAMEORIGIN') end it 'should not set the X-Frame-Options for other content types' do - expect(get('/', {}, 'wants' => 'text/foo').headers["X-Frame-Options"]).to be_nil + expect(get('/', {}, 'wants' => 'text/foo').headers['X-Frame-Options']).to be_nil end it 'should allow changing the protection mode' do # I have no clue what other modes are available mock_app do - use Rack::Protection::FrameOptions, :frame_options => :deny + use Rack::Protection::FrameOptions, frame_options: :deny run DummyApp end - expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("DENY") + expect(get('/', {}, 'wants' => 'text/html').headers['X-Frame-Options']).to eq('DENY') end - it 'should allow changing the protection mode to a string' do # I have no clue what other modes are available mock_app do - use Rack::Protection::FrameOptions, :frame_options => "ALLOW-FROM foo" + use Rack::Protection::FrameOptions, frame_options: 'ALLOW-FROM foo' run DummyApp end - expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("ALLOW-FROM foo") + expect(get('/', {}, 'wants' => 'text/html').headers['X-Frame-Options']).to eq('ALLOW-FROM foo') end it 'should not override the header if already set' do - mock_app with_headers("X-Frame-Options" => "allow") - expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("allow") + mock_app with_headers('X-Frame-Options' => 'allow') + expect(get('/', {}, 'wants' => 'text/html').headers['X-Frame-Options']).to eq('allow') end end diff --git a/rack-protection/spec/lib/rack/protection/http_origin_spec.rb b/rack-protection/spec/lib/rack/protection/http_origin_spec.rb index bc8a77239b..0471dd85aa 100644 --- a/rack-protection/spec/lib/rack/protection/http_origin_spec.rb +++ b/rack-protection/spec/lib/rack/protection/http_origin_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::HttpOrigin do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' before(:each) do mock_app do @@ -8,29 +10,29 @@ end end - %w(GET HEAD POST PUT DELETE).each do |method| + %w[GET HEAD POST PUT DELETE].each do |method| it "accepts #{method} requests with no Origin" do expect(send(method.downcase, '/')).to be_ok end end - %w(GET HEAD).each do |method| + %w[GET HEAD].each do |method| it "accepts #{method} requests with non-permitted Origin" do expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com')).to be_ok end end - %w(GET HEAD POST PUT DELETE).each do |method| + %w[GET HEAD POST PUT DELETE].each do |method| it "accepts #{method} requests when allow_if is true" do mock_app do - use Rack::Protection::HttpOrigin, :allow_if => lambda{|env| env.has_key?('HTTP_ORIGIN') } + use Rack::Protection::HttpOrigin, allow_if: ->(env) { env.key?('HTTP_ORIGIN') } run DummyApp end expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://any.domain.com')).to be_ok end end - %w(POST PUT DELETE).each do |method| + %w[POST PUT DELETE].each do |method| it "denies #{method} requests with non-permitted Origin" do expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com')).not_to be_ok end diff --git a/rack-protection/spec/lib/rack/protection/ip_spoofing_spec.rb b/rack-protection/spec/lib/rack/protection/ip_spoofing_spec.rb index b3a9109438..2cb201f6e7 100644 --- a/rack-protection/spec/lib/rack/protection/ip_spoofing_spec.rb +++ b/rack-protection/spec/lib/rack/protection/ip_spoofing_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::IPSpoofing do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' it 'accepts requests without X-Forward-For header' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_REAL_IP' => '4.3.2.1') @@ -8,7 +10,7 @@ it 'accepts requests with proper X-Forward-For header' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', - 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') + 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') expect(last_response).to be_ok end @@ -19,15 +21,15 @@ it 'denies requests where the client spoofs the IP but not X-Forward-For' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.5', - 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') + 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') expect(last_response).not_to be_ok end it 'denies requests where IP and X-Forward-For are spoofed but not X-Real-IP' do get('/', {}, - 'HTTP_CLIENT_IP' => '1.2.3.5', - 'HTTP_X_FORWARDED_FOR' => '1.2.3.5', - 'HTTP_X_REAL_IP' => '1.2.3.4') + 'HTTP_CLIENT_IP' => '1.2.3.5', + 'HTTP_X_FORWARDED_FOR' => '1.2.3.5', + 'HTTP_X_REAL_IP' => '1.2.3.4') expect(last_response).not_to be_ok end end diff --git a/rack-protection/spec/lib/rack/protection/json_csrf_spec.rb b/rack-protection/spec/lib/rack/protection/json_csrf_spec.rb index fb45b8405d..06312665a3 100644 --- a/rack-protection/spec/lib/rack/protection/json_csrf_spec.rb +++ b/rack-protection/spec/lib/rack/protection/json_csrf_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::JsonCsrf do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' module DummyAppWithBody module Closeable @@ -22,22 +24,22 @@ def self.body def self.call(env) Thread.current[:last_env] = env - [200, {'Content-Type' => 'application/json'}, body] + [200, { 'Content-Type' => 'application/json' }, body] end end describe 'json response' do before do - mock_app { |e| [200, {'Content-Type' => 'application/json'}, []]} + mock_app { |_e| [200, { 'Content-Type' => 'application/json' }, []] } end - it "denies get requests with json responses with a remote referrer" do + it 'denies get requests with json responses with a remote referrer' do expect(get('/', {}, 'HTTP_REFERER' => 'http://evil.com')).not_to be_ok end - it "closes the body returned by the app if it denies the get request" do - mock_app DummyAppWithBody do |e| - [200, {'Content-Type' => 'application/json'}, []] + it 'closes the body returned by the app if it denies the get request' do + mock_app DummyAppWithBody do |_e| + [200, { 'Content-Type' => 'application/json' }, []] end get('/', {}, 'HTTP_REFERER' => 'http://evil.com') @@ -45,10 +47,10 @@ def self.call(env) expect(DummyAppWithBody.body).to be_closed end - it "accepts requests with json responses with a remote referrer when allow_if is true" do + it 'accepts requests with json responses with a remote referrer when allow_if is true' do mock_app do - use Rack::Protection::JsonCsrf, :allow_if => lambda{|env| env['HTTP_REFERER'] == 'http://good.com'} - run proc { |e| [200, {'Content-Type' => 'application/json'}, []]} + use Rack::Protection::JsonCsrf, allow_if: ->(env) { env['HTTP_REFERER'] == 'http://good.com' } + run proc { |_e| [200, { 'Content-Type' => 'application/json' }, []] } end expect(get('/', {}, 'HTTP_REFERER' => 'http://good.com')).to be_ok @@ -62,37 +64,34 @@ def self.call(env) expect(get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_X_ORIGIN' => 'http://good.com')).to be_ok end - it "accepts get requests with json responses with a local referrer" do + it 'accepts get requests with json responses with a local referrer' do expect(get('/', {}, 'HTTP_REFERER' => '/')).to be_ok end - it "accepts get requests with json responses with no referrer" do + it 'accepts get requests with json responses with no referrer' do expect(get('/', {})).to be_ok end - it "accepts XHR requests" do + it 'accepts XHR requests' do expect(get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest')).to be_ok end - end describe 'not json response' do - - it "accepts get requests with 304 headers" do - mock_app { |e| [304, {}, []]} + it 'accepts get requests with 304 headers' do + mock_app { |_e| [304, {}, []] } expect(get('/', {}).status).to eq(304) end - end describe 'with drop_session as default reaction' do it 'still denies' do mock_app do - use Rack::Protection, :reaction => :drop_session - run proc { |e| [200, {'Content-Type' => 'application/json'}, []]} + use Rack::Protection, reaction: :drop_session + run proc { |_e| [200, { 'Content-Type' => 'application/json' }, []] } end - session = {:foo => :bar} + session = { foo: :bar } get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'rack.session' => session) expect(last_response).not_to be_ok end diff --git a/rack-protection/spec/lib/rack/protection/path_traversal_spec.rb b/rack-protection/spec/lib/rack/protection/path_traversal_spec.rb index fba876d9ce..1eca34bdc8 100644 --- a/rack-protection/spec/lib/rack/protection/path_traversal_spec.rb +++ b/rack-protection/spec/lib/rack/protection/path_traversal_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::PathTraversal do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' context 'escaping' do before do - mock_app { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO']]] } + mock_app { |e| [200, { 'Content-Type' => 'text/plain' }, [e['PATH_INFO']]] } end %w[/foo/bar /foo/bar/ / /.f /a.x].each do |path| @@ -26,7 +28,7 @@ context "PATH_INFO's encoding" do before do - @app = Rack::Protection::PathTraversal.new(proc { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO'].encoding.to_s]] }) + @app = Rack::Protection::PathTraversal.new(proc { |e| [200, { 'Content-Type' => 'text/plain' }, [e['PATH_INFO'].encoding.to_s]] }) end it 'should remain unchanged as ASCII-8BIT' do diff --git a/rack-protection/spec/lib/rack/protection/protection_spec.rb b/rack-protection/spec/lib/rack/protection/protection_spec.rb index 9bdb34e063..e8dc55df5d 100644 --- a/rack-protection/spec/lib/rack/protection/protection_spec.rb +++ b/rack-protection/spec/lib/rack/protection/protection_spec.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' it 'passes on options' do mock_app do - use Rack::Protection, :track => ['HTTP_FOO'] - run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] } + use Rack::Protection, track: ['HTTP_FOO'] + run proc { |_e| [200, { 'Content-Type' => 'text/plain' }, ['hi']] } end - session = {:foo => :bar} + session = { foo: :bar } get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b' expect(session[:foo]).to eq(:bar) @@ -18,21 +20,21 @@ it 'passes errors through if :reaction => :report is used' do mock_app do - use Rack::Protection, :reaction => :report - run proc { |e| [200, {'Content-Type' => 'text/plain'}, [e["protection.failed"].to_s]] } + use Rack::Protection, reaction: :report + run proc { |e| [200, { 'Content-Type' => 'text/plain' }, [e['protection.failed'].to_s]] } end - session = {:foo => :bar} + session = { foo: :bar } post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com') expect(last_response).to be_ok - expect(body).to eq("true") + expect(body).to eq('true') end - describe "#react" do + describe '#react' do it 'prevents attacks and warns about it' do io = StringIO.new mock_app do - use Rack::Protection, :logger => Logger.new(io) + use Rack::Protection, logger: Logger.new(io) run DummyApp end post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com') @@ -42,7 +44,7 @@ it 'reports attacks if reaction is to report' do io = StringIO.new mock_app do - use Rack::Protection, :reaction => :report, :logger => Logger.new(io) + use Rack::Protection, reaction: :report, logger: Logger.new(io) run DummyApp end post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com') @@ -54,7 +56,7 @@ io = StringIO.new Rack::Protection::Base.send(:define_method, :special) { |*args| io << args.inspect } mock_app do - use Rack::Protection, :reaction => :special, :logger => Logger.new(io) + use Rack::Protection, reaction: :special, logger: Logger.new(io) run DummyApp end post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com') @@ -63,34 +65,34 @@ end end - describe "#html?" do - context "given an appropriate content-type header" do - subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/html" } + describe '#html?' do + context 'given an appropriate content-type header' do + subject { Rack::Protection::Base.new(nil).html? 'content-type' => 'text/html' } it { is_expected.to be_truthy } end - context "given an appropriate content-type header of text/xml" do - subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/xml" } + context 'given an appropriate content-type header of text/xml' do + subject { Rack::Protection::Base.new(nil).html? 'content-type' => 'text/xml' } it { is_expected.to be_truthy } end - context "given an appropriate content-type header of application/xml" do - subject { Rack::Protection::Base.new(nil).html? 'content-type' => "application/xml" } + context 'given an appropriate content-type header of application/xml' do + subject { Rack::Protection::Base.new(nil).html? 'content-type' => 'application/xml' } it { is_expected.to be_truthy } end - context "given an inappropriate content-type header" do - subject { Rack::Protection::Base.new(nil).html? 'content-type' => "image/gif" } + context 'given an inappropriate content-type header' do + subject { Rack::Protection::Base.new(nil).html? 'content-type' => 'image/gif' } it { is_expected.to be_falsey } end - context "given no content-type header" do + context 'given no content-type header' do subject { Rack::Protection::Base.new(nil).html?({}) } it { is_expected.to be_falsey } end end - describe "#instrument" do + describe '#instrument' do let(:env) { { 'rack.protection.attack' => 'base' } } let(:instrumenter) { double('Instrumenter') } @@ -99,7 +101,7 @@ end context 'with an instrumenter specified' do - let(:app) { Rack::Protection::Base.new(nil, :instrumenter => instrumenter) } + let(:app) { Rack::Protection::Base.new(nil, instrumenter: instrumenter) } it { expect(instrumenter).to receive(:instrument).with('rack.protection', env) } end @@ -111,14 +113,14 @@ end end - describe "new" do + describe 'new' do it 'should allow disable session protection' do mock_app do - use Rack::Protection, :without_session => true + use Rack::Protection, without_session: true run DummyApp end - session = {:foo => :bar} + session = { foo: :bar } get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'b' expect(session[:foo]).to eq :bar @@ -126,7 +128,7 @@ it 'should allow disable CSRF protection' do mock_app do - use Rack::Protection, :without_session => true + use Rack::Protection, without_session: true run DummyApp end diff --git a/rack-protection/spec/lib/rack/protection/remote_referrer_spec.rb b/rack-protection/spec/lib/rack/protection/remote_referrer_spec.rb index 35512b9df9..92165ecaf4 100644 --- a/rack-protection/spec/lib/rack/protection/remote_referrer_spec.rb +++ b/rack-protection/spec/lib/rack/protection/remote_referrer_spec.rb @@ -1,28 +1,30 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::RemoteReferrer do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' - it "accepts post requests with no referrer" do + it 'accepts post requests with no referrer' do expect(post('/')).to be_ok end - it "does not accept post requests with no referrer if allow_empty_referrer is false" do + it 'does not accept post requests with no referrer if allow_empty_referrer is false' do mock_app do - use Rack::Protection::RemoteReferrer, :allow_empty_referrer => false + use Rack::Protection::RemoteReferrer, allow_empty_referrer: false run DummyApp end expect(post('/')).not_to be_ok end - it "should allow post request with a relative referrer" do + it 'should allow post request with a relative referrer' do expect(post('/', {}, 'HTTP_REFERER' => '/')).to be_ok end - it "accepts post requests with the same host in the referrer" do + it 'accepts post requests with the same host in the referrer' do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.com') expect(last_response).to be_ok end - it "denies post requests with a remote referrer" do + it 'denies post requests with a remote referrer' do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org') expect(last_response).not_to be_ok end diff --git a/rack-protection/spec/lib/rack/protection/remote_token_spec.rb b/rack-protection/spec/lib/rack/protection/remote_token_spec.rb index bc04f1ade9..8693ba1c68 100644 --- a/rack-protection/spec/lib/rack/protection/remote_token_spec.rb +++ b/rack-protection/spec/lib/rack/protection/remote_token_spec.rb @@ -1,57 +1,59 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::RemoteToken do let(:token) { described_class.random_token } let(:masked_token) { described_class.token(session) } - let(:bad_token) { Base64.strict_encode64("badtoken") } - let(:session) { {:csrf => token} } + let(:bad_token) { Base64.strict_encode64('badtoken') } + let(:session) { { csrf: token } } - it_behaves_like "any rack application" + it_behaves_like 'any rack application' - it "accepts post requests with no referrer" do + it 'accepts post requests with no referrer' do expect(post('/')).to be_ok end - it "accepts post requests with a local referrer" do + it 'accepts post requests with a local referrer' do expect(post('/', {}, 'HTTP_REFERER' => '/')).to be_ok end - it "denies post requests with a remote referrer and no token" do + it 'denies post requests with a remote referrer and no token' do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org') expect(last_response).not_to be_ok end - it "accepts post requests with a remote referrer and correct X-CSRF-Token header" do + it 'accepts post requests with a remote referrer and correct X-CSRF-Token header' do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', - 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => token) + 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => token) expect(last_response).to be_ok end - it "accepts post requests with a remote referrer and masked X-CSRF-Token header" do + it 'accepts post requests with a remote referrer and masked X-CSRF-Token header' do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', - 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => masked_token) + 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => masked_token) expect(last_response).to be_ok end - it "denies post requests with a remote referrer and wrong X-CSRF-Token header" do + it 'denies post requests with a remote referrer and wrong X-CSRF-Token header' do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', - 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => bad_token) + 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => bad_token) expect(last_response).not_to be_ok end - it "accepts post form requests with a remote referrer and correct authenticity_token field" do - post('/', {"authenticity_token" => token}, 'HTTP_REFERER' => 'http://example.com/foo', - 'HTTP_HOST' => 'example.org', 'rack.session' => session) + it 'accepts post form requests with a remote referrer and correct authenticity_token field' do + post('/', { 'authenticity_token' => token }, 'HTTP_REFERER' => 'http://example.com/foo', + 'HTTP_HOST' => 'example.org', 'rack.session' => session) expect(last_response).to be_ok end - it "accepts post form requests with a remote referrer and masked authenticity_token field" do - post('/', {"authenticity_token" => masked_token}, 'HTTP_REFERER' => 'http://example.com/foo', - 'HTTP_HOST' => 'example.org', 'rack.session' => session) + it 'accepts post form requests with a remote referrer and masked authenticity_token field' do + post('/', { 'authenticity_token' => masked_token }, 'HTTP_REFERER' => 'http://example.com/foo', + 'HTTP_HOST' => 'example.org', 'rack.session' => session) expect(last_response).to be_ok end - it "denies post form requests with a remote referrer and wrong authenticity_token field" do - post('/', {"authenticity_token" => bad_token}, 'HTTP_REFERER' => 'http://example.com/foo', - 'HTTP_HOST' => 'example.org', 'rack.session' => session) + it 'denies post form requests with a remote referrer and wrong authenticity_token field' do + post('/', { 'authenticity_token' => bad_token }, 'HTTP_REFERER' => 'http://example.com/foo', + 'HTTP_HOST' => 'example.org', 'rack.session' => session) expect(last_response).not_to be_ok end end diff --git a/rack-protection/spec/lib/rack/protection/session_hijacking_spec.rb b/rack-protection/spec/lib/rack/protection/session_hijacking_spec.rb index 0a96c23075..e39497b1a9 100644 --- a/rack-protection/spec/lib/rack/protection/session_hijacking_spec.rb +++ b/rack-protection/spec/lib/rack/protection/session_hijacking_spec.rb @@ -1,30 +1,32 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::SessionHijacking do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' - it "accepts a session without changes to tracked parameters" do - session = {:foo => :bar} + it 'accepts a session without changes to tracked parameters' do + session = { foo: :bar } get '/', {}, 'rack.session' => session get '/', {}, 'rack.session' => session expect(session[:foo]).to eq(:bar) end - it "denies requests with a changing User-Agent header" do - session = {:foo => :bar} + it 'denies requests with a changing User-Agent header' do + session = { foo: :bar } get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'b' expect(session).to be_empty end - it "accepts requests with a changing Accept-Encoding header" do + it 'accepts requests with a changing Accept-Encoding header' do # this is tested because previously it led to clearing the session - session = {:foo => :bar} + session = { foo: :bar } get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b' expect(session).not_to be_empty end - it "accepts requests with a changing Version header"do - session = {:foo => :bar} + it 'accepts requests with a changing Version header' do + session = { foo: :bar } get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.0' get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.1' expect(session[:foo]).to eq(:bar) diff --git a/rack-protection/spec/lib/rack/protection/strict_transport_spec.rb b/rack-protection/spec/lib/rack/protection/strict_transport_spec.rb index 21cba97aa5..62602fa03a 100644 --- a/rack-protection/spec/lib/rack/protection/strict_transport_spec.rb +++ b/rack-protection/spec/lib/rack/protection/strict_transport_spec.rb @@ -1,43 +1,45 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::StrictTransport do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' it 'should set the Strict-Transport-Security header' do - expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000") + expect(get('/', {}, 'wants' => 'text/html').headers['Strict-Transport-Security']).to eq('max-age=31536000') end it 'should allow changing the max-age option' do mock_app do - use Rack::Protection::StrictTransport, :max_age => 16_070_400 + use Rack::Protection::StrictTransport, max_age: 16_070_400 run DummyApp end - expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=16070400") + expect(get('/', {}, 'wants' => 'text/html').headers['Strict-Transport-Security']).to eq('max-age=16070400') end it 'should allow switching on the include_subdomains option' do mock_app do - use Rack::Protection::StrictTransport, :include_subdomains => true + use Rack::Protection::StrictTransport, include_subdomains: true run DummyApp end - expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000; includeSubDomains") + expect(get('/', {}, 'wants' => 'text/html').headers['Strict-Transport-Security']).to eq('max-age=31536000; includeSubDomains') end it 'should allow switching on the preload option' do mock_app do - use Rack::Protection::StrictTransport, :preload => true + use Rack::Protection::StrictTransport, preload: true run DummyApp end - expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000; preload") + expect(get('/', {}, 'wants' => 'text/html').headers['Strict-Transport-Security']).to eq('max-age=31536000; preload') end it 'should allow switching on all the options' do mock_app do - use Rack::Protection::StrictTransport, :preload => true, :include_subdomains => true + use Rack::Protection::StrictTransport, preload: true, include_subdomains: true run DummyApp end - expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000; includeSubDomains; preload") + expect(get('/', {}, 'wants' => 'text/html').headers['Strict-Transport-Security']).to eq('max-age=31536000; includeSubDomains; preload') end end diff --git a/rack-protection/spec/lib/rack/protection/xss_header_spec.rb b/rack-protection/spec/lib/rack/protection/xss_header_spec.rb index 1b5d3c03bf..2fef0edbf7 100644 --- a/rack-protection/spec/lib/rack/protection/xss_header_spec.rb +++ b/rack-protection/spec/lib/rack/protection/xss_header_spec.rb @@ -1,54 +1,54 @@ +# frozen_string_literal: true + RSpec.describe Rack::Protection::XSSHeader do - it_behaves_like "any rack application" + it_behaves_like 'any rack application' it 'should set the X-XSS-Protection' do - expect(get('/', {}, 'wants' => 'text/html;charset=utf-8').headers["X-XSS-Protection"]).to eq("1; mode=block") + expect(get('/', {}, 'wants' => 'text/html;charset=utf-8').headers['X-XSS-Protection']).to eq('1; mode=block') end it 'should set the X-XSS-Protection for XHTML' do - expect(get('/', {}, 'wants' => 'application/xhtml+xml').headers["X-XSS-Protection"]).to eq("1; mode=block") + expect(get('/', {}, 'wants' => 'application/xhtml+xml').headers['X-XSS-Protection']).to eq('1; mode=block') end it 'should not set the X-XSS-Protection for other content types' do - expect(get('/', {}, 'wants' => 'application/foo').headers["X-XSS-Protection"]).to be_nil + expect(get('/', {}, 'wants' => 'application/foo').headers['X-XSS-Protection']).to be_nil end it 'should allow changing the protection mode' do # I have no clue what other modes are available mock_app do - use Rack::Protection::XSSHeader, :xss_mode => :foo + use Rack::Protection::XSSHeader, xss_mode: :foo run DummyApp end - expect(get('/', {}, 'wants' => 'application/xhtml').headers["X-XSS-Protection"]).to eq("1; mode=foo") + expect(get('/', {}, 'wants' => 'application/xhtml').headers['X-XSS-Protection']).to eq('1; mode=foo') end it 'should not override the header if already set' do - mock_app with_headers("X-XSS-Protection" => "0") - expect(get('/', {}, 'wants' => 'text/html').headers["X-XSS-Protection"]).to eq("0") + mock_app with_headers('X-XSS-Protection' => '0') + expect(get('/', {}, 'wants' => 'text/html').headers['X-XSS-Protection']).to eq('0') end it 'should set the X-Content-Type-Options' do - expect(get('/', {}, 'wants' => 'text/html').header["X-Content-Type-Options"]).to eq("nosniff") + expect(get('/', {}, 'wants' => 'text/html').header['X-Content-Type-Options']).to eq('nosniff') end - it 'should set the X-Content-Type-Options for other content types' do - expect(get('/', {}, 'wants' => 'application/foo').header["X-Content-Type-Options"]).to eq("nosniff") + expect(get('/', {}, 'wants' => 'application/foo').header['X-Content-Type-Options']).to eq('nosniff') end - it 'should allow changing the nosniff-mode off' do mock_app do - use Rack::Protection::XSSHeader, :nosniff => false + use Rack::Protection::XSSHeader, nosniff: false run DummyApp end - expect(get('/').headers["X-Content-Type-Options"]).to be_nil + expect(get('/').headers['X-Content-Type-Options']).to be_nil end it 'should not override the header if already set X-Content-Type-Options' do - mock_app with_headers("X-Content-Type-Options" => "sniff") - expect(get('/', {}, 'wants' => 'text/html').headers["X-Content-Type-Options"]).to eq("sniff") + mock_app with_headers('X-Content-Type-Options' => 'sniff') + expect(get('/', {}, 'wants' => 'text/html').headers['X-Content-Type-Options']).to eq('sniff') end end diff --git a/rack-protection/spec/spec_helper.rb b/rack-protection/spec/spec_helper.rb index 745c2f9f87..f35ec34855 100644 --- a/rack-protection/spec/spec_helper.rb +++ b/rack-protection/spec/spec_helper.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require 'rack/protection' require 'rack/test' require 'rack' -Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require f } +Dir[File.expand_path('support/**/*.rb', __dir__)].sort.each { |f| require f } # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. diff --git a/rack-protection/spec/support/dummy_app.rb b/rack-protection/spec/support/dummy_app.rb index 980e183b54..fb83d36bb3 100644 --- a/rack-protection/spec/support/dummy_app.rb +++ b/rack-protection/spec/support/dummy_app.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + module DummyApp def self.call(env) Thread.current[:last_env] = env body = (env['REQUEST_METHOD'] == 'HEAD' ? '' : 'ok') - [200, {'Content-Type' => env['wants'] || 'text/plain'}, [body]] + [200, { 'Content-Type' => env['wants'] || 'text/plain' }, [body]] end -end \ No newline at end of file +end diff --git a/rack-protection/spec/support/not_implemented_as_pending.rb b/rack-protection/spec/support/not_implemented_as_pending.rb index 2598ca41ea..2ef371b377 100644 --- a/rack-protection/spec/support/not_implemented_as_pending.rb +++ b/rack-protection/spec/support/not_implemented_as_pending.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # see http://blog.101ideas.cz/posts/pending-examples-via-not-implemented-error-in-rspec.html module NotImplementedAsPending def self.included(base) @@ -20,4 +22,4 @@ def finish(reporter) end RSpec::Core::Example.send :include, self -end \ No newline at end of file +end diff --git a/rack-protection/spec/support/rack_monkey_patches.rb b/rack-protection/spec/support/rack_monkey_patches.rb index aa4a4565c9..574d367e08 100644 --- a/rack-protection/spec/support/rack_monkey_patches.rb +++ b/rack-protection/spec/support/rack_monkey_patches.rb @@ -1,12 +1,15 @@ -if defined? Gem.loaded_specs and Gem.loaded_specs.include? 'rack' - version = Gem.loaded_specs['rack'].version.to_s -else - version = Rack.release + '.0' -end +# frozen_string_literal: true + +version = if defined? Gem.loaded_specs&.include?('rack') + Gem.loaded_specs['rack'].version.to_s + else + "#{Rack.release}.0" + end -if version == "1.3" +if version == '1.3' Rack::Session::Abstract::ID.class_eval do private + def prepare_session(env) session_was = env[ENV_SESSION_KEY] env[ENV_SESSION_KEY] = SessionHash.new(self, env) diff --git a/rack-protection/spec/support/shared_examples.rb b/rack-protection/spec/support/shared_examples.rb index 6e56cdb8ba..c8eeaad69b 100644 --- a/rack-protection/spec/support/shared_examples.rb +++ b/rack-protection/spec/support/shared_examples.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + RSpec.shared_examples_for 'any rack application' do - it "should not interfere with normal get requests" do + it 'should not interfere with normal get requests' do expect(get('/')).to be_ok expect(body).to eq('ok') end - it "should not interfere with normal head requests" do + it 'should not interfere with normal head requests' do expect(head('/')).to be_ok end @@ -14,9 +16,10 @@ def call(env) was = env.dup res = app.call(env) - was.each do |k,v| + was.each do |k, v| next if env[k] == v - fail "env[#{k.inspect}] changed from #{v.inspect} to #{env[k].inspect}" + + raise "env[#{k.inspect}] changed from #{v.inspect} to #{env[k].inspect}" end res end @@ -24,13 +27,13 @@ def call(env) mock_app do use Rack::Head - use(Rack::Config) { |e| e['rack.session'] ||= {}} + use(Rack::Config) { |e| e['rack.session'] ||= {} } use detector use klass run DummyApp end - expect(get('/..', :foo => '')).to be_ok + expect(get('/..', foo: '')).to be_ok end it 'allows passing on values in env' do @@ -53,7 +56,7 @@ def call(env) mock_app do use Rack::Head - use(Rack::Config) { |e| e['rack.session'] ||= {}} + use(Rack::Config) { |e| e['rack.session'] ||= {} } use changer use klass use detector diff --git a/rack-protection/spec/support/spec_helpers.rb b/rack-protection/spec/support/spec_helpers.rb index e862f172b9..d9f40a2777 100644 --- a/rack-protection/spec/support/spec_helpers.rb +++ b/rack-protection/spec/support/spec_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'forwardable' module SpecHelpers @@ -12,12 +14,12 @@ def app end def mock_app(app = nil, &block) - app = block if app.nil? and block.arity == 1 + app = block if app.nil? && (block.arity == 1) if app klass = described_class mock_app do use Rack::Head - use(Rack::Config) { |e| e['rack.session'] ||= {}} + use(Rack::Config) { |e| e['rack.session'] ||= {} } use klass run app end @@ -27,10 +29,10 @@ def mock_app(app = nil, &block) end def with_headers(headers) - proc { [200, {'Content-Type' => 'text/plain'}.merge(headers), ['ok']] } + proc { [200, { 'Content-Type' => 'text/plain' }.merge(headers), ['ok']] } end def env Thread.current[:last_env] end -end \ No newline at end of file +end diff --git a/sinatra-contrib/Gemfile b/sinatra-contrib/Gemfile index 0a42b5a234..af41c9e5c9 100644 --- a/sinatra-contrib/Gemfile +++ b/sinatra-contrib/Gemfile @@ -1,8 +1,10 @@ -source "https://rubygems.org" +# frozen_string_literal: true + +source 'https://rubygems.org' gemspec -gem 'sinatra', path: '..' gem 'rack-protection', path: '../rack-protection' +gem 'sinatra', path: '..' gem 'rack-test', github: 'rack/rack-test' @@ -36,6 +38,6 @@ repos = { 'tilt' => 'rtomayko/tilt', 'rack' => 'rack/rack' } %w[tilt rack].each do |lib| dep = (ENV[lib] || 'stable').sub "#{lib}-", '' dep = nil if dep == 'stable' - dep = {:github => repos[lib], :branch => dep} if dep and dep !~ /(\d+\.)+\d+/ + dep = { github: repos[lib], branch: dep } if dep && dep !~ (/(\d+\.)+\d+/) gem lib, dep if dep end diff --git a/sinatra-contrib/Rakefile b/sinatra-contrib/Rakefile index 94d2afdc71..c78189421b 100644 --- a/sinatra-contrib/Rakefile +++ b/sinatra-contrib/Rakefile @@ -1,12 +1,14 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift File.expand_path('lib', __dir__) require 'open-uri' require 'yaml' require 'sinatra/contrib/version' -desc "run specs" +desc 'run specs' task(:spec) { ruby '-S rspec' } -task(:test => :spec) -task(:default => :spec) +task(test: :spec) +task(default: :spec) namespace :doc do task :readmes do @@ -14,36 +16,37 @@ namespace :doc do puts "Trying file... #{file}" excluded_files = %w[lib/sinatra/contrib.rb lib/sinatra/decompile.rb] next if excluded_files.include?(file) + doc = File.read(file)[/^module Sinatra(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n") - file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc" - Dir.mkdir "doc" unless File.directory? "doc" + file = "doc/#{file[4..-4].tr('/_', '-')}.rdoc" + Dir.mkdir 'doc' unless File.directory? 'doc' puts "writing #{file}" - File.open(file, "w") { |f| f << doc } + File.open(file, 'w') { |f| f << doc } end end task :index do - doc = File.read("README.md") - file = "doc/sinatra-contrib-readme.md" - Dir.mkdir "doc" unless File.directory? "doc" + doc = File.read('README.md') + file = 'doc/sinatra-contrib-readme.md' + Dir.mkdir 'doc' unless File.directory? 'doc' puts "writing #{file}" - File.open(file, "w") { |f| f << doc } + File.open(file, 'w') { |f| f << doc } end - task :all => [:readmes, :index] + task all: %i[readmes index] end -desc "generate documentation" -task :doc => 'doc:all' +desc 'generate documentation' +task doc: 'doc:all' -desc "generate gemspec" +desc 'generate gemspec' task 'sinatra-contrib.gemspec' do content = File.read 'sinatra-contrib.gemspec' fields = { - :authors => `git shortlog -sn`.scan(/[^\d\s].*/), - :email => `git shortlog -sne`.scan(/[^<]+@[^>]+/), - :files => `git ls-files`.split("\n").reject { |f| f =~ /^(\.|Gemfile)/ } + authors: `git shortlog -sn`.scan(/[^\d\s].*/), + email: `git shortlog -sne`.scan(/[^<]+@[^>]+/), + files: `git ls-files`.split("\n").grep_v(/^(\.|Gemfile)/) } fields.each do |field, values| @@ -56,9 +59,9 @@ task 'sinatra-contrib.gemspec' do File.open('sinatra-contrib.gemspec', 'w') { |f| f << content } end -task :gemspec => 'sinatra-contrib.gemspec' +task gemspec: 'sinatra-contrib.gemspec' -task :release => :gemspec do +task release: :gemspec do sh <<-SH rm -Rf sinatra-contrib*.gem && gem build sinatra-contrib.gemspec && @@ -70,4 +73,3 @@ task :release => :gemspec do git push --tags && (git push origin --tags || true) SH end - diff --git a/sinatra-contrib/lib/sinatra/capture.rb b/sinatra-contrib/lib/sinatra/capture.rb index 3a74688a61..f6d18e2d28 100644 --- a/sinatra-contrib/lib/sinatra/capture.rb +++ b/sinatra-contrib/lib/sinatra/capture.rb @@ -86,17 +86,19 @@ module Capture def capture(*args, &block) return block[*args] if ruby? + if haml? && Tilt[:haml] == Tilt::HamlTemplate buffer = Haml::Buffer.new(nil, Haml::Options.new.for_buffer) with_haml_buffer(buffer) { capture_haml(*args, &block) } else - @_out_buf, _buf_was = '', @_out_buf + buf_was = @_out_buf + @_out_buf = '' begin raw = block[*args] captured = block.binding.eval('@_out_buf') captured.empty? ? raw : captured ensure - @_out_buf = _buf_was + @_out_buf = buf_was end end end diff --git a/sinatra-contrib/lib/sinatra/config_file.rb b/sinatra-contrib/lib/sinatra/config_file.rb index cbaa06deee..c3c0a1aa47 100644 --- a/sinatra-contrib/lib/sinatra/config_file.rb +++ b/sinatra-contrib/lib/sinatra/config_file.rb @@ -3,7 +3,6 @@ require 'erb' module Sinatra - # = Sinatra::ConfigFile # # Sinatra::ConfigFile is an extension that allows you to load the @@ -107,7 +106,6 @@ module Sinatra # bar: 'baz' # override the default value # module ConfigFile - # When the extension is registered sets the +environments+ setting to the # traditional environments: development, test and production. def self.registered(base) @@ -122,8 +120,9 @@ def config_file(*paths) paths.each do |pattern| Dir.glob(pattern) do |file| raise UnsupportedConfigType unless ['.yml', '.yaml', '.erb'].include?(File.extname(file)) + logger.info "loading config file '#{file}'" if logging? && respond_to?(:logger) - document = ERB.new(IO.read(file)).result + document = ERB.new(File.read(file)).result yaml = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(document) : YAML.load(document) config = config_for_env(yaml) config.each_pair { |key, value| set(key, value) } @@ -132,7 +131,7 @@ def config_file(*paths) end end - class UnsupportedConfigType < Exception + class UnsupportedConfigType < StandardError def message 'Invalid config file type, use .yml, .yaml or .erb' end diff --git a/sinatra-contrib/lib/sinatra/content_for.rb b/sinatra-contrib/lib/sinatra/content_for.rb index 6102f2c8fd..75d1ee562a 100644 --- a/sinatra-contrib/lib/sinatra/content_for.rb +++ b/sinatra-contrib/lib/sinatra/content_for.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + require 'sinatra/base' require 'sinatra/capture' module Sinatra - # = Sinatra::ContentFor # # Sinatra::ContentFor is a set of helpers that allows you to capture @@ -174,7 +175,7 @@ def clear_content_for(key) # for :head. def yield_content(key, *args, &block) if block_given? && !content_for?(key) - (haml? && Tilt[:haml] == Tilt::HamlTemplate) ? capture_haml(*args, &block) : yield(*args) + haml? && Tilt[:haml] == Tilt::HamlTemplate ? capture_haml(*args, &block) : yield(*args) else content = content_blocks[key.to_sym].map { |b| capture(*args, &b) } content.join.tap do |c| diff --git a/sinatra-contrib/lib/sinatra/contrib.rb b/sinatra-contrib/lib/sinatra/contrib.rb index e27888b368..9bee090215 100644 --- a/sinatra-contrib/lib/sinatra/contrib.rb +++ b/sinatra-contrib/lib/sinatra/contrib.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sinatra/contrib/setup' module Sinatra diff --git a/sinatra-contrib/lib/sinatra/contrib/all.rb b/sinatra-contrib/lib/sinatra/contrib/all.rb index 427be2686d..c217a112a0 100644 --- a/sinatra-contrib/lib/sinatra/contrib/all.rb +++ b/sinatra-contrib/lib/sinatra/contrib/all.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + require 'sinatra/contrib' Sinatra.register Sinatra::Contrib::All diff --git a/sinatra-contrib/lib/sinatra/contrib/setup.rb b/sinatra-contrib/lib/sinatra/contrib/setup.rb index 61e4f87279..b2ae15e944 100644 --- a/sinatra-contrib/lib/sinatra/contrib/setup.rb +++ b/sinatra-contrib/lib/sinatra/contrib/setup.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sinatra/base' require 'sinatra/contrib/version' @@ -5,7 +7,7 @@ module Sinatra module Contrib module Loader def extensions - @extensions ||= {:helpers => [], :register => []} + @extensions ||= { helpers: [], register: [] } end def register(name, path) diff --git a/sinatra-contrib/lib/sinatra/contrib/version.rb b/sinatra-contrib/lib/sinatra/contrib/version.rb index 2c9d689f93..d81b671fdb 100644 --- a/sinatra-contrib/lib/sinatra/contrib/version.rb +++ b/sinatra-contrib/lib/sinatra/contrib/version.rb @@ -1,6 +1,7 @@ +# frozen_string_literal: true + module Sinatra module Contrib VERSION = '3.0.0' end end - diff --git a/sinatra-contrib/lib/sinatra/cookies.rb b/sinatra-contrib/lib/sinatra/cookies.rb index 14aee33a71..742f45d7d0 100644 --- a/sinatra-contrib/lib/sinatra/cookies.rb +++ b/sinatra-contrib/lib/sinatra/cookies.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sinatra/base' module Sinatra @@ -65,15 +67,15 @@ def initialize(app) @deleted = [] @options = { - :path => @request.script_name.to_s.empty? ? '/' : @request.script_name, - :domain => @request.host == 'localhost' ? nil : @request.host, - :secure => @request.secure?, - :httponly => true + path: @request.script_name.to_s.empty? ? '/' : @request.script_name, + domain: @request.host == 'localhost' ? nil : @request.host, + secure: @request.secure?, + httponly: true } - if app.settings.respond_to? :cookie_options - @options.merge! app.settings.cookie_options - end + return unless app.settings.respond_to? :cookie_options + + @options.merge! app.settings.cookie_options end def ==(other) @@ -88,9 +90,11 @@ def []=(key, value) set(key, value: value) end - def assoc(key) - to_hash.assoc(key.to_s) - end if Hash.method_defined? :assoc + if Hash.method_defined? :assoc + def assoc(key) + to_hash.assoc(key.to_s) + end + end def clear each_key { |k| delete(k) } @@ -114,17 +118,20 @@ def delete(key) def delete_if return enum_for(__method__) unless block_given? + each { |k, v| delete(k) if yield(k, v) } self end def each(&block) return enum_for(__method__) unless block_given? + to_hash.each(&block) end def each_key(&block) return enum_for(__method__) unless block_given? + to_hash.each_key(&block) end @@ -132,6 +139,7 @@ def each_key(&block) def each_value(&block) return enum_for(__method__) unless block_given? + to_hash.each_value(&block) end @@ -145,16 +153,18 @@ def fetch(key, &block) end end - def flatten - to_hash.flatten - end if Hash.method_defined? :flatten + if Hash.method_defined? :flatten + def flatten + to_hash.flatten + end + end def has_key?(key) - response_cookies.has_key? key.to_s or request_cookies.has_key? key.to_s + response_cookies.key? key.to_s or request_cookies.key? key.to_s end def has_value?(value) - response_cookies.has_value? value or request_cookies.has_value? value + response_cookies.value? value or request_cookies.value? value end def hash @@ -168,13 +178,16 @@ def inspect "<##{self.class}: #{to_hash.inspect[1..-2]}>" end - def invert - to_hash.invert - end if Hash.method_defined? :invert + if Hash.method_defined? :invert + def invert + to_hash.invert + end + end def keep_if return enum_for(__method__) unless block_given? - delete_if { |*a| not yield(*a) } + + delete_if { |*a| !yield(*a) } end def key(value) @@ -197,11 +210,11 @@ def merge(other, &block) def merge!(other) other.each_pair do |key, value| - if block_given? and include? key - self[key] = yield(key.to_s, self[key], value) - else - self[key] = value - end + self[key] = if block_given? && include?(key) + yield(key.to_s, self[key], value) + else + value + end end end @@ -217,18 +230,20 @@ def rehash def reject(&block) return enum_for(__method__) unless block_given? + to_hash.reject(&block) end alias reject! delete_if def replace(other) - select! { |k, v| other.include?(k) or other.include?(k.to_s) } + select! { |k, _v| other.include?(k) or other.include?(k.to_s) } merge! other end def select(&block) return enum_for(__method__) unless block_given? + to_hash.select(&block) end @@ -246,9 +261,11 @@ def shift alias size length - def sort(&block) - to_hash.sort(&block) - end if Hash.method_defined? :sort + if Hash.method_defined? :sort + def sort(&block) + to_hash.sort(&block) + end + end alias store []= @@ -300,6 +317,7 @@ def parse_response string.each_line do |line| key, value = line.split(';', 2).first.to_s.split('=', 2) next if key.nil? + key = Rack::Utils.unescape(key) if line =~ /expires=Thu, 01[-\s]Jan[-\s]1970/ @deleted << key @@ -314,7 +332,7 @@ def parse_response end def request_cookies - @request.cookies.reject { |key, value| deleted.include? key } + @request.cookies.reject { |key, _value| deleted.include? key } end end diff --git a/sinatra-contrib/lib/sinatra/custom_logger.rb b/sinatra-contrib/lib/sinatra/custom_logger.rb index 42c0d639cd..eb3295601a 100644 --- a/sinatra-contrib/lib/sinatra/custom_logger.rb +++ b/sinatra-contrib/lib/sinatra/custom_logger.rb @@ -1,5 +1,6 @@ -module Sinatra +# frozen_string_literal: true +module Sinatra # = Sinatra::CustomLogger # # CustomLogger extension allows you to define your own logger instance diff --git a/sinatra-contrib/lib/sinatra/engine_tracking.rb b/sinatra-contrib/lib/sinatra/engine_tracking.rb index 6709d880ad..52ebf858fb 100644 --- a/sinatra-contrib/lib/sinatra/engine_tracking.rb +++ b/sinatra-contrib/lib/sinatra/engine_tracking.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sinatra/base' module Sinatra @@ -17,7 +19,7 @@ def erb? # @return [Boolean] Returns true if current engine is `:erubi`. def erubi? @current_engine == :erubi or - erb? && Tilt[:erb] == Tilt::ErubiTemplate + (erb? && Tilt[:erb] == Tilt::ErubiTemplate) end # @return [Boolean] Returns true if current engine is `:haml`. @@ -72,7 +74,8 @@ def initialize(*) # @param engine [Symbol, String] Name of Engine to shift to. def with_engine(engine) - @current_engine, engine_was = engine.to_sym, @current_engine + engine_was = @current_engine + @current_engine = engine.to_sym yield ensure @current_engine = engine_was diff --git a/sinatra-contrib/lib/sinatra/extension.rb b/sinatra-contrib/lib/sinatra/extension.rb index 2a5cda78d4..7e6b609180 100644 --- a/sinatra-contrib/lib/sinatra/extension.rb +++ b/sinatra-contrib/lib/sinatra/extension.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'sinatra/base' module Sinatra - # = Sinatra::Extension # # Sinatra::Extension is a mixin that provides some syntactic sugar @@ -81,13 +82,14 @@ def recorded_methods def method_missing(method, *args, &block) return super unless Sinatra::Base.respond_to? method + record(method, *args, &block) DontCall.new(method) end class DontCall < BasicObject def initialize(method) @method = method end - def method_missing(*) fail "not supposed to use result of #@method!" end + def method_missing(*) raise "not supposed to use result of #{@method}!" end def inspect; "#<#{self.class}: #{@method}>" end end end diff --git a/sinatra-contrib/lib/sinatra/json.rb b/sinatra-contrib/lib/sinatra/json.rb index 712419bbd1..31c4199ad9 100644 --- a/sinatra-contrib/lib/sinatra/json.rb +++ b/sinatra-contrib/lib/sinatra/json.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'sinatra/base' require 'multi_json' module Sinatra - # = Sinatra::JSON # # Sinatra::JSON adds a helper method, called +json+, for (obviously) @@ -95,7 +96,7 @@ def encode(object) def json(object, options = {}) content_type resolve_content_type(options) - resolve_encoder_action object, resolve_encoder(options) + resolve_encoder_action object, resolve_encoder(options) end private @@ -109,16 +110,14 @@ def resolve_encoder(options = {}) end def resolve_encoder_action(object, encoder) - [:encode, :generate].each do |method| + %i[encode generate].each do |method| return encoder.send(method, object) if encoder.respond_to? method end - if encoder.is_a? Symbol - object.__send__(encoder) - else - fail "#{encoder} does not respond to #generate nor #encode" - end #if - end #resolve_encoder_action - end #JSON + raise "#{encoder} does not respond to #generate nor #encode" unless encoder.is_a? Symbol + + object.__send__(encoder) + end + end Base.set :json_encoder do ::MultiJson diff --git a/sinatra-contrib/lib/sinatra/link_header.rb b/sinatra-contrib/lib/sinatra/link_header.rb index 6d9bac4d3c..fcf1dcb41e 100644 --- a/sinatra-contrib/lib/sinatra/link_header.rb +++ b/sinatra-contrib/lib/sinatra/link_header.rb @@ -1,7 +1,6 @@ require 'sinatra/base' module Sinatra - # = Sinatra::LinkHeader # # Sinatra::LinkHeader adds a set of helper methods to generate link @@ -86,8 +85,8 @@ def link(*urls) opts[:rel] = urls.shift unless urls.first.respond_to? :to_str options = opts.map { |k, v| " #{k}=#{v.to_s.inspect}" } html_pattern = "" - http_pattern = ["<%s>", *options].join ";" - link = (response["Link"] ||= "") + http_pattern = ['<%s>', *options].join ';' + link = (response['Link'] ||= '') urls.map do |url| link << ",\n" unless link.empty? @@ -116,14 +115,15 @@ def link(*urls) # %body= yield def link_headers yield if block_given? - return "" unless response.include? "Link" - response["Link"].split(",\n").map do |line| + return '' unless response.include? 'Link' + + response['Link'].split(",\n").map do |line| url, *opts = line.split(';').map(&:strip) - "" + "" end.join "\n" end - def self.registered(base) + def self.registered(_base) puts "WARNING: #{self} is a helpers module, not an extension." end end diff --git a/sinatra-contrib/lib/sinatra/multi_route.rb b/sinatra-contrib/lib/sinatra/multi_route.rb index af48ac008b..6de5acb2f0 100644 --- a/sinatra-contrib/lib/sinatra/multi_route.rb +++ b/sinatra-contrib/lib/sinatra/multi_route.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sinatra/base' module Sinatra diff --git a/sinatra-contrib/lib/sinatra/namespace.rb b/sinatra-contrib/lib/sinatra/namespace.rb index 157a965b13..6a00d3753f 100644 --- a/sinatra-contrib/lib/sinatra/namespace.rb +++ b/sinatra-contrib/lib/sinatra/namespace.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + require 'sinatra/base' require 'mustermann' module Sinatra - # = Sinatra::Namespace # # Sinatra::Namespace is an extension that adds namespaces to an @@ -187,13 +188,16 @@ module Sinatra module Namespace def self.new(base, pattern, conditions = {}, &block) Module.new do - #quelch uninitialized variable warnings, since these get used by compile method. - @pattern, @conditions = nil, nil + # quelch uninitialized variable warnings, since these get used by compile method. + @pattern = nil + @conditions = nil extend NamespacedMethods include InstanceMethods - @base, @extensions, @errors = base, [], {} + @base = base + @extensions = [] + @errors = {} @pattern, @conditions = compile(pattern, conditions) - @templates = Hash.new { |h,k| @base.templates[k] } + @templates = Hash.new { |_h, k| @base.templates[k] } namespace = self before { extend(@namespace = namespace) } class_eval(&block) @@ -224,14 +228,14 @@ module NamespacedMethods include SharedMethods attr_reader :base, :templates - ALLOWED_ENGINES = [ - :erb, :erubi, :haml, :hamlit, :builder, :nokogiri, - :liquid, :markdown, :rdoc, :asciidoc, :markaby, - :rabl, :slim, :yajl + ALLOWED_ENGINES = %i[ + erb erubi haml hamlit builder nokogiri + liquid markdown rdoc asciidoc markaby + rabl slim yajl ] def self.prefixed(*names) - names.each { |n| define_method(n) { |*a, &b| prefixed(n, *a, &b) }} + names.each { |n| define_method(n) { |*a, &b| prefixed(n, *a, &b) } } end prefixed :before, :after, :delete, :get, :head, :options, :patch, :post, :put @@ -267,7 +271,7 @@ def namespace_errors end def error(*codes, &block) - args = Sinatra::Base.send(:compile!, "ERROR", /.*/, block) + args = Sinatra::Base.send(:compile!, 'ERROR', /.*/, block) codes = codes.map { |c| Array(c) }.flatten codes << Exception if codes.empty? codes << Sinatra::NotFound if codes.include?(404) @@ -280,12 +284,14 @@ def error(*codes, &block) def respond_to(*args) return @conditions[:provides] || base.respond_to if args.empty? + @conditions[:provides] = args end def set(key, value = self, &block) - return key.each { |k,v| set(k, v) } if key.respond_to?(:each) and block.nil? and value == self + return key.each { |k, v| set(k, v) } if key.respond_to?(:each) && block.nil? && (value == self) raise ArgumentError, "may not set #{key}" unless ([:views] + ALLOWED_ENGINES).include?(key) + block ||= proc { value } singleton_class.send(:define_method, key, &block) end @@ -300,11 +306,12 @@ def disable(*opts) def template(name, &block) first_location = caller_locations.first - filename, line = first_location.path, first_location.lineno + filename = first_location.path + line = first_location.lineno templates[name] = [block, filename, line] end - def layout(name=:layout, &block) + def layout(name = :layout, &block) template name, &block end @@ -323,21 +330,22 @@ def compile(pattern, conditions, default_pattern = nil) conditions = conditions.merge pattern.to_hash pattern = nil end - base_pattern, base_conditions = @pattern, @conditions + base_pattern = @pattern + base_conditions = @conditions pattern ||= default_pattern - [ prefixed_path(base_pattern, pattern), - (base_conditions || {}).merge(conditions) ] + [prefixed_path(base_pattern, pattern), + (base_conditions || {}).merge(conditions)] end def prefixed_path(a, b) - return a || b || /.*/ unless a and b + return a || b || /.*/ unless a && b return Mustermann.new(b) if a == /.*/ Mustermann.new(a) + Mustermann.new(b) end def prefixed(method, pattern = nil, conditions = {}, &block) - default = %r{(?:/.*)?} if method == :before or method == :after + default = %r{(?:/.*)?} if (method == :before) || (method == :after) pattern, conditions = compile pattern, conditions, default result = base.send(method, pattern, **conditions, &block) invoke_hook :route_added, method.to_s.upcase, pattern, block diff --git a/sinatra-contrib/lib/sinatra/quiet_logger.rb b/sinatra-contrib/lib/sinatra/quiet_logger.rb index 7c00b403f6..ee0ff8dafd 100644 --- a/sinatra-contrib/lib/sinatra/quiet_logger.rb +++ b/sinatra-contrib/lib/sinatra/quiet_logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Sinatra # = Sinatra::QuietLogger # @@ -32,10 +34,14 @@ module Sinatra # end # module QuietLogger - def self.registered(app) - quiet_logger_prefixes = app.settings.quiet_logger_prefixes.join('|') rescue '' + quiet_logger_prefixes = begin + app.settings.quiet_logger_prefixes.join('|') + rescue StandardError + '' + end return warn('You need to specify the paths you wish to exclude from logging via `set :quiet_logger_prefixes, %w(images css fonts)`') if quiet_logger_prefixes.empty? + const_set('QUIET_LOGGER_REGEX', %r(\A/{0,2}(?:#{quiet_logger_prefixes}))) ::Rack::CommonLogger.prepend( ::Module.new do @@ -45,6 +51,5 @@ def log(env, *) end ) end - end end diff --git a/sinatra-contrib/lib/sinatra/reloader.rb b/sinatra-contrib/lib/sinatra/reloader.rb index 7cb1612373..6c309770a7 100644 --- a/sinatra-contrib/lib/sinatra/reloader.rb +++ b/sinatra-contrib/lib/sinatra/reloader.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'sinatra/base' module Sinatra - # = Sinatra::Reloader # # Extension to reload modified files. Useful during development, @@ -94,11 +95,9 @@ module Sinatra # end # module Reloader - # Watches a file so it can tell when it has been updated, and what # elements does it contain. class Watcher - # Represents an element of a Sinatra application that may need to # be reloaded. An element could be: # * a route @@ -172,7 +171,8 @@ def updated # Creates a new +Watcher+ instance for the file located at +path+. def initialize(path) @ignore = nil - @path, @elements = path, [] + @path = path + @elements = [] update end @@ -254,12 +254,13 @@ def self.perform(klass) reloaded_paths << watcher.path end return if reloaded_paths.empty? + @@after_reload.each do |block| - block.arity != 0 ? block.call(reloaded_paths) : block.call + block.arity.zero? ? block.call : block.call(reloaded_paths) end # Prevents after_reload from increasing each time it's reloaded. @@after_reload.delete_if do |blk| - path, _ = blk.source_location + path, = blk.source_location path && reloaded_paths.include?(path) end end @@ -286,7 +287,7 @@ def compile!(verb, path, block, **options) block.source_location.first : caller_files[1] signature = super watch_element( - source_location, :route, { :verb => verb, :signature => signature } + source_location, :route, { verb: verb, signature: signature } ) signature end @@ -295,9 +296,8 @@ def compile!(verb, path, block, **options) # tells the +Watcher::List+ for the Sinatra application to watch the # inline templates in +file+ or the file who made the call to this # method. - def inline_templates=(file=nil) - file = (file.nil? || file == true) ? - (caller_files[1] || File.expand_path($0)) : file + def inline_templates=(file = nil) + file = (caller_files[1] || File.expand_path($0)) if file.nil? || file == true watch_element(file, :inline_templates) super end @@ -329,7 +329,7 @@ def error(*codes, &block) path = caller_files[1] || File.expand_path($0) result = super codes.each do |c| - watch_element(path, :error, :code => c, :handler => @errors[c]) + watch_element(path, :error, code: c, handler: @errors[c]) end result end @@ -358,17 +358,17 @@ module ExtensionMethods # Removes the +element+ from the Sinatra application. def deactivate(element) case element.type - when :route then + when :route verb = element.representation[:verb] signature = element.representation[:signature] (routes[verb] ||= []).delete(signature) - when :middleware then + when :middleware @middleware.delete(element.representation) - when :before_filter then + when :before_filter filters[:before].delete(element.representation) - when :after_filter then + when :after_filter filters[:after].delete(element.representation) - when :error then + when :error code = element.representation[:code] handler = element.representation[:handler] @errors.delete(code) if @errors[code] == handler @@ -387,7 +387,7 @@ def dont_reload(*glob) Dir[*glob].each { |path| Watcher::List.for(self).ignore(path) } end - private + private # attr_reader :register_path warn on -w (private attribute) def register_path; @register_path ||= nil; end @@ -415,7 +415,7 @@ def registering_extension? # watch it in the file where the extension has been registered. # This prevents the duplication of the elements added by the # extension in its +registered+ method with every reload. - def watch_element(path, type, representation=nil) + def watch_element(path, type, representation = nil) list = Watcher::List.for(self) element = Watcher::Element.new(type, representation) list.watch(path, element) diff --git a/sinatra-contrib/lib/sinatra/required_params.rb b/sinatra-contrib/lib/sinatra/required_params.rb index c14525e447..dc286f2862 100644 --- a/sinatra-contrib/lib/sinatra/required_params.rb +++ b/sinatra-contrib/lib/sinatra/required_params.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sinatra/base' module Sinatra @@ -60,7 +62,7 @@ def _required_params(p, *keys) elsif key.is_a?(Array) _required_params(p, *key) else - halt 400 unless p && p.respond_to?(:has_key?) && p.has_key?(key.to_s) + halt 400 unless p.respond_to?(:key?) && p&.key?(key.to_s) end end true diff --git a/sinatra-contrib/lib/sinatra/respond_with.rb b/sinatra-contrib/lib/sinatra/respond_with.rb index 91c3391cb2..498decfa57 100644 --- a/sinatra-contrib/lib/sinatra/respond_with.rb +++ b/sinatra-contrib/lib/sinatra/respond_with.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sinatra/json' require 'sinatra/base' @@ -88,14 +90,17 @@ module Sinatra module RespondWith class Format def initialize(app) - @app, @map, @generic, @default = app, {}, {}, nil + @app = app + @map = {} + @generic = {} + @default = nil end def on(type, &block) @app.settings.mime_types(type).each do |mime| case mime when '*/*' then @default = block - when /^([^\/]+)\/\*$/ then @generic[$1] = block + when %r{^([^/]+)/\*$} then @generic[$1] = block else @map[mime] = block end end @@ -103,23 +108,24 @@ def on(type, &block) def finish yield self if block_given? - mime_type = @app.content_type || - @app.request.preferred_type(@map.keys) || - @app.request.preferred_type || - 'text/html' + mime_type = @app.content_type || + @app.request.preferred_type(@map.keys) || + @app.request.preferred_type || + 'text/html' type = mime_type.split(/\s*;\s*/, 2).first - handlers = [@map[type], @generic[type[/^[^\/]+/]], @default].compact + handlers = [@map[type], @generic[type[%r{^[^/]+}]], @default].compact handlers.each do |block| - if result = block.call(type) + if (result = block.call(type)) @app.content_type mime_type @app.halt result end end - @app.halt 500, "Unknown template engine" + @app.halt 500, 'Unknown template engine' end def method_missing(method, *args, &block) - return super if args.any? or block.nil? or not @app.mime_type(method) + return super if args.any? || block.nil? || !@app.mime_type(method) + on(method, &block) end end @@ -128,19 +134,22 @@ module Helpers include Sinatra::JSON def respond_with(template, object = nil, &block) - object, template = template, nil unless Symbol === template + unless Symbol === template + object = template + template = nil + end format = Format.new(self) - format.on "*/*" do |type| + format.on '*/*' do |type| exts = settings.ext_map[type] exts << :xml if type.end_with? '+xml' if template args = template_cache.fetch(type, template) { template_for(template, exts) } if args.any? - locals = { :object => object } + locals = { object: object } locals.merge! object.to_hash if object.respond_to? :to_hash renderer = args.first - options = args[1..-1] + [{:locals => locals}] + options = args[1..] + [{ locals: locals }] halt send(renderer, *options) end @@ -149,6 +158,7 @@ def respond_with(template, object = nil, &block) exts.each do |ext| halt json(object) if ext == :json next unless object.respond_to? method = "to_#{ext}" + halt(*object.send(method)) end end @@ -176,10 +186,11 @@ def template_for(name, exts) possible.each do |engine, template| klass = Tilt.default_mapping.template_map[engine.to_s] || - Tilt.lazy_map[engine.to_s].fetch(0, [])[0] + Tilt.lazy_map[engine.to_s].fetch(0, [])[0] find_template(settings.views, template, klass) do |file| next unless File.exist? file + return settings.rendering_method(engine) << template.to_sym end end @@ -189,7 +200,7 @@ def template_for(name, exts) def remap_extensions ext_map.clear - Rack::Mime::MIME_TYPES.each { |e,t| ext_map[t] << e[1..-1].to_sym } + Rack::Mime::MIME_TYPES.each { |e, t| ext_map[t] << e[1..].to_sym } ext_map['text/javascript'] << 'js' ext_map['text/xml'] << 'xml' end @@ -206,7 +217,7 @@ def respond_to(*formats) if formats.any? @respond_to ||= [] @respond_to.concat formats - elsif @respond_to.nil? and superclass.respond_to? :respond_to + elsif @respond_to.nil? && superclass.respond_to?(:respond_to) superclass.respond_to else @respond_to @@ -216,7 +227,8 @@ def respond_to(*formats) def rendering_method(engine) return [engine] if Sinatra::Templates.method_defined? engine return [:mab] if engine.to_sym == :markaby - [:render, :engine] + + %i[render engine] end private @@ -228,8 +240,8 @@ def compile!(verb, path, block, **options) def self.jrubyify(engs) not_supported = [:markdown] - engs.keys.each do |key| - engs[key].collect! { |eng| (eng == :yajl) ? :json_pure : eng } + engs.each_key do |key| + engs[key].collect! { |eng| eng == :yajl ? :json_pure : eng } engs[key].delete_if { |eng| not_supported.include?(eng) } end engs @@ -237,19 +249,19 @@ def self.jrubyify(engs) def self.engines engines = { - :xml => [:builder, :nokogiri], - :html => [:erb, :erubi, :haml, :hamlit, :slim, :liquid, - :mab, :markdown, :rdoc], - :all => (Sinatra::Templates.instance_methods.map(&:to_sym) + - [:mab] - [:find_template, :markaby]), - :json => [:yajl], + xml: %i[builder nokogiri], + html: %i[erb erubi haml hamlit slim liquid + mab markdown rdoc], + all: (Sinatra::Templates.instance_methods.map(&:to_sym) + + [:mab] - %i[find_template markaby]), + json: [:yajl] } engines.default = [] - (defined? JRUBY_VERSION) ? jrubyify(engines) : engines + defined?(JRUBY_VERSION) ? jrubyify(engines) : engines end def self.registered(base) - base.set :ext_map, Hash.new { |h,k| h[k] = [] } + base.set :ext_map, Hash.new { |h, k| h[k] = [] } base.set :template_engines, engines base.remap_extensions base.helpers Helpers diff --git a/sinatra-contrib/lib/sinatra/runner.rb b/sinatra-contrib/lib/sinatra/runner.rb index 4e967aaefe..b2124618c9 100644 --- a/sinatra-contrib/lib/sinatra/runner.rb +++ b/sinatra-contrib/lib/sinatra/runner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'open-uri' require 'net/http' require 'timeout' @@ -49,7 +51,7 @@ module Sinatra # For an example, check https://github.com/apotonick/roar/blob/master/test/integration/runner.rb class Runner def app_file - File.expand_path("server.rb", __dir__) + File.expand_path('server.rb', __dir__) end def run @@ -60,7 +62,8 @@ def run def kill return unless pipe - Process.kill("KILL", pipe.pid) + + Process.kill('KILL', pipe.pid) rescue NotImplementedError system "kill -9 #{pipe.pid}" rescue Errno::ESRCH @@ -70,7 +73,7 @@ def get(url) Timeout.timeout(1) { get_url("#{protocol}://127.0.0.1:#{port}#{url}") } end - def get_stream(url = "/stream", &block) + def get_stream(url = '/stream', &block) Net::HTTP.start '127.0.0.1', port do |http| request = Net::HTTP::Get.new url http.request request do |response| @@ -89,29 +92,32 @@ def get_response(url) end def log - @log ||= "" - loop { @log << pipe.read_nonblock(1) } + @log ||= '' + loop { @log << pipe.read_nonblock(1) } rescue Exception @log end - private + private + attr_accessor :pipe def start IO.popen(command) end - def command # to be overwritten + # to be overwritten + def command "bundle exec ruby #{app_file} -p #{port} -e production" end - def ping(timeout=30) + def ping(timeout = 30) loop do return if alive? + if Time.now - @started > timeout - $stderr.puts command, log - fail "timeout" + warn command, log + raise 'timeout' else sleep 0.1 end @@ -121,26 +127,29 @@ def ping(timeout=30) def alive? 3.times { get(ping_path) } true - rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, SystemCallError, OpenURI::HTTPError, Timeout::Error + rescue EOFError, SystemCallError, OpenURI::HTTPError, Timeout::Error false end - def ping_path # to be overwritten + # to be overwritten + def ping_path '/ping' end - def port # to be overwritten + # to be overwritten + def port 4567 end def protocol - "http" + 'http' end def get_url(url) uri = URI.parse(url) - return uri.read unless protocol == "https" + return uri.read unless protocol == 'https' + get_https_url(uri) end diff --git a/sinatra-contrib/lib/sinatra/streaming.rb b/sinatra-contrib/lib/sinatra/streaming.rb index aa9e717f82..e54eb27d7a 100644 --- a/sinatra-contrib/lib/sinatra/streaming.rb +++ b/sinatra-contrib/lib/sinatra/streaming.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'sinatra/base' module Sinatra - # = Sinatra::Streaming # # Sinatra 1.3 introduced the +stream+ helper. This addon improves the @@ -84,13 +85,14 @@ def stream(*) end module Stream - attr_accessor :app, :lineno, :pos, :transformer, :closed alias tell pos alias closed? closed def self.extended(obj) - obj.closed, obj.lineno, obj.pos = false, 0, 0 + obj.closed = false + obj.lineno = 0 + obj.pos = 0 obj.callback { obj.closed = true } obj.errback { obj.closed = true } end @@ -108,6 +110,7 @@ def <<(data) def each # that way body.each.map { ... } works return self unless block_given? + super end @@ -120,7 +123,8 @@ def map!(&block) @transformer ||= nil if @transformer - inner, outer = @transformer, block + inner = @transformer + outer = block block = proc { |value| outer[inner[value]] } end @transformer = block @@ -132,7 +136,7 @@ def write(data) data.to_s.bytesize end - alias syswrite write + alias syswrite write alias write_nonblock write def print(*args) @@ -154,7 +158,7 @@ def puts(*args) end def close_read - raise IOError, "closing non-duplex IO for reading" + raise IOError, 'closing non-duplex IO for reading' end def closed_read? @@ -171,10 +175,6 @@ def external_encoding settings.default_encoding end - def closed? - @closed - end - def settings app.settings end @@ -184,7 +184,7 @@ def rewind end def not_open_for_reading(*) - raise IOError, "not opened for reading" + raise IOError, 'not opened for reading' end alias bytes not_open_for_reading diff --git a/sinatra-contrib/lib/sinatra/test_helpers.rb b/sinatra-contrib/lib/sinatra/test_helpers.rb index 9c053b0337..15599bf255 100644 --- a/sinatra-contrib/lib/sinatra/test_helpers.rb +++ b/sinatra-contrib/lib/sinatra/test_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sinatra/base' require 'rack' begin @@ -158,7 +160,7 @@ def app # @param params [Hash] # @param env [Hash] def options(uri, params = {}, env = {}, &block) - env = env_for(uri, env.merge(:method => "OPTIONS", :params => params)) + env = env_for(uri, env.merge(method: 'OPTIONS', params: params)) current_session.send(:process_request, uri, env, &block) end end @@ -170,7 +172,7 @@ def options(uri, params = {}, env = {}, &block) # @param params [Hash] # @param env [Hash] def patch(uri, params = {}, env = {}, &block) - env = env_for(uri, env.merge(:method => "PATCH", :params => params)) + env = env_for(uri, env.merge(method: 'PATCH', params: params)) current_session.send(:process_request, uri, env, &block) end end @@ -187,7 +189,8 @@ def last_request? # @return [Hash] Session of last request, or the empty Hash def session return {} unless last_request? - raise Rack::Test::Error, "session not enabled for app" unless last_env["rack.session"] or app.session? + raise Rack::Test::Error, 'session not enabled for app' unless last_env['rack.session'] || app.session? + last_request.session end diff --git a/sinatra-contrib/lib/sinatra/webdav.rb b/sinatra-contrib/lib/sinatra/webdav.rb index 6529c8ba14..70ef7f632c 100644 --- a/sinatra-contrib/lib/sinatra/webdav.rb +++ b/sinatra-contrib/lib/sinatra/webdav.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'sinatra/base' module Sinatra - # = Sinatra::WebDAV # # This extensions provides WebDAV verbs, as defined by RFC 4918 @@ -37,8 +38,8 @@ def self.registered(_) module Request def self.included(base) base.class_eval do - alias _safe? safe? - alias _idempotent? idempotent? + alias_method :_safe?, :safe? + alias_method :_idempotent?, :idempotent? def safe? _safe? or propfind? @@ -70,9 +71,9 @@ def move? request_method == 'MOVE' end - #def lock? + # def lock? # request_method == 'LOCK' - #end + # end def unlock? request_method == 'UNLOCK' @@ -84,7 +85,7 @@ def proppatch(path, opts = {}, &bk) route 'PROPPATCH', path, opts, &bk end def mkcol(path, opts = {}, &bk) route 'MKCOL', path, opts, &bk end def copy(path, opts = {}, &bk) route 'COPY', path, opts, &bk end def move(path, opts = {}, &bk) route 'MOVE', path, opts, &bk end - #def lock(path, opts = {}, &bk) route 'LOCK', path, opts, &bk end + # def lock(path, opts = {}, &bk) route 'LOCK', path, opts, &bk end def unlock(path, opts = {}, &bk) route 'UNLOCK', path, opts, &bk end end diff --git a/sinatra-contrib/sinatra-contrib.gemspec b/sinatra-contrib/sinatra-contrib.gemspec index e9043676ae..f5248b69b2 100644 --- a/sinatra-contrib/sinatra-contrib.gemspec +++ b/sinatra-contrib/sinatra-contrib.gemspec @@ -1,57 +1,58 @@ -# -*- encoding: utf-8 -*- +# frozen_string_literal: true -version = File.read(File.expand_path("../VERSION", __dir__)).strip +version = File.read(File.expand_path('../VERSION', __dir__)).strip Gem::Specification.new do |s| - s.name = "sinatra-contrib" + s.name = 'sinatra-contrib' s.version = version - s.description = "Collection of useful Sinatra extensions" - s.homepage = "http://sinatrarb.com/contrib/" - s.license = "MIT" + s.description = 'Collection of useful Sinatra extensions' + s.homepage = 'http://sinatrarb.com/contrib/' + s.license = 'MIT' s.summary = s.description - s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"] - s.email = "sinatrarb@googlegroups.com" - s.files = Dir["lib/**/*.rb"] + [ - "LICENSE", - "README.md", - "Rakefile", - "ideas.md", - "sinatra-contrib.gemspec" + s.authors = ['https://github.com/sinatra/sinatra/graphs/contributors'] + s.email = 'sinatrarb@googlegroups.com' + s.files = Dir['lib/**/*.rb'] + [ + 'LICENSE', + 'README.md', + 'Rakefile', + 'ideas.md', + 'sinatra-contrib.gemspec' ] - if s.respond_to?(:metadata) - s.metadata = { - 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/sinatra-contrib', - 'homepage_uri' => 'http://sinatrarb.com/contrib/', - 'documentation_uri' => 'https://www.rubydoc.info/gems/sinatra-contrib' - } - else - raise <<-EOF + unless s.respond_to?(:metadata) + raise <<-WARN RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: gem install rubygems-update update_rubygems: gem update --system -EOF + WARN end + s.metadata = { + 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/sinatra-contrib', + 'homepage_uri' => 'http://sinatrarb.com/contrib/', + 'documentation_uri' => 'https://www.rubydoc.info/gems/sinatra-contrib', + 'rubygems_mfa_required' => 'true' + } + s.required_ruby_version = '>= 2.6.0' - s.add_dependency "sinatra", version - s.add_dependency "mustermann", "~> 3.0" - s.add_dependency "tilt", "~> 2.0" - s.add_dependency "rack-protection", version - s.add_dependency "multi_json" + s.add_dependency 'multi_json' + s.add_dependency 'mustermann', '~> 3.0' + s.add_dependency 'rack-protection', version + s.add_dependency 'sinatra', version + s.add_dependency 'tilt', '~> 2.0' - s.add_development_dependency "rspec", "~> 3" - s.add_development_dependency "haml" - s.add_development_dependency "erubi" - s.add_development_dependency "slim" - s.add_development_dependency "builder" - s.add_development_dependency "liquid" - s.add_development_dependency "redcarpet" - s.add_development_dependency "asciidoctor" - s.add_development_dependency "nokogiri" - s.add_development_dependency "markaby" - s.add_development_dependency "rake", "< 11" - s.add_development_dependency "rack-test", "~> 2" + s.add_development_dependency 'asciidoctor' + s.add_development_dependency 'builder' + s.add_development_dependency 'erubi' + s.add_development_dependency 'haml' + s.add_development_dependency 'liquid' + s.add_development_dependency 'markaby' + s.add_development_dependency 'nokogiri' + s.add_development_dependency 'rack-test', '~> 2' + s.add_development_dependency 'rake', '< 11' + s.add_development_dependency 'redcarpet' + s.add_development_dependency 'rspec', '~> 3' + s.add_development_dependency 'slim' end diff --git a/sinatra-contrib/spec/cookies_spec.rb b/sinatra-contrib/spec/cookies_spec.rb index 4de61f16fe..852f642574 100644 --- a/sinatra-contrib/spec/cookies_spec.rb +++ b/sinatra-contrib/spec/cookies_spec.rb @@ -1,14 +1,14 @@ require 'spec_helper' RSpec.describe Sinatra::Cookies do - def cookie_route(*cookies, &block) + def cookie_route(*cookies, headers: {}, &block) result = nil set_cookie(cookies) @cookie_app.get('/') do result = instance_eval(&block) "ok" end - get '/', {}, @headers || {} + get '/', {}, headers || {} expect(last_response).to be_ok expect(body).to eq("ok") result @@ -97,8 +97,8 @@ def cookies(*set_cookies) end it 'sets domain to nil if localhost' do - @headers = {'HTTP_HOST' => 'localhost'} - expect(cookie_route do + headers = {'HTTP_HOST' => 'localhost'} + expect(cookie_route(headers: headers) do cookies['foo'] = 'bar' response['Set-Cookie'] end).not_to include("domain") diff --git a/sinatra-contrib/spec/namespace_spec.rb b/sinatra-contrib/spec/namespace_spec.rb index c747001745..0382dda53c 100644 --- a/sinatra-contrib/spec/namespace_spec.rb +++ b/sinatra-contrib/spec/namespace_spec.rb @@ -263,6 +263,7 @@ def foo specify 'are accepted in the before-filter' do namespace '/foo' do + before { @yes = nil } before(:host_name => 'example.com') { @yes = 'yes' } send(verb) { @yes || 'no' } end diff --git a/sinatra.gemspec b/sinatra.gemspec index a0f01b9731..8f9cf599b3 100644 --- a/sinatra.gemspec +++ b/sinatra.gemspec @@ -1,51 +1,54 @@ -version = File.read(File.expand_path("VERSION", __dir__)).strip +# frozen_string_literal: true + +version = File.read(File.expand_path('VERSION', __dir__)).strip Gem::Specification.new 'sinatra', version do |s| - s.description = "Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort." - s.summary = "Classy web-development dressed in a DSL" - s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"] - s.email = "sinatrarb@googlegroups.com" - s.homepage = "http://sinatrarb.com/" + s.description = 'Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort.' + s.summary = 'Classy web-development dressed in a DSL' + s.authors = ['Blake Mizerany', 'Ryan Tomayko', 'Simon Rozet', 'Konstantin Haase'] + s.email = 'sinatrarb@googlegroups.com' + s.homepage = 'http://sinatrarb.com/' s.license = 'MIT' s.files = Dir['README*.md', 'lib/**/*', 'examples/*'] + [ - ".yardopts", - "AUTHORS.md", - "CHANGELOG.md", - "CONTRIBUTING.md", - "Gemfile", - "LICENSE", - "MAINTENANCE.md", - "Rakefile", - "SECURITY.md", - "sinatra.gemspec", - "VERSION"] + '.yardopts', + 'AUTHORS.md', + 'CHANGELOG.md', + 'CONTRIBUTING.md', + 'Gemfile', + 'LICENSE', + 'MAINTENANCE.md', + 'Rakefile', + 'SECURITY.md', + 'sinatra.gemspec', + 'VERSION' + ] s.extra_rdoc_files = %w[README.md LICENSE] s.rdoc_options = %w[--line-numbers --title Sinatra --main README.rdoc --encoding=UTF-8] - if s.respond_to?(:metadata) - s.metadata = { - 'source_code_uri' => 'https://github.com/sinatra/sinatra', - 'changelog_uri' => 'https://github.com/sinatra/sinatra/blob/master/CHANGELOG.md', - 'homepage_uri' => 'http://sinatrarb.com/', - 'bug_tracker_uri' => 'https://github.com/sinatra/sinatra/issues', - 'mailing_list_uri' => 'http://groups.google.com/group/sinatrarb', - 'documentation_uri' => 'https://www.rubydoc.info/gems/sinatra' - } - else - raise <<-EOF + unless s.respond_to?(:metadata) + raise <<-WARN RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: gem install rubygems-update update_rubygems: gem update --system -EOF + WARN end + s.metadata = { + 'source_code_uri' => 'https://github.com/sinatra/sinatra', + 'changelog_uri' => 'https://github.com/sinatra/sinatra/blob/master/CHANGELOG.md', + 'homepage_uri' => 'http://sinatrarb.com/', + 'bug_tracker_uri' => 'https://github.com/sinatra/sinatra/issues', + 'mailing_list_uri' => 'http://groups.google.com/group/sinatrarb', + 'documentation_uri' => 'https://www.rubydoc.info/gems/sinatra' + } + s.required_ruby_version = '>= 2.6.0' + s.add_dependency 'mustermann', '~> 3.0' s.add_dependency 'rack', '~> 2.2', '>= 2.2.4' - s.add_dependency 'tilt', '~> 2.0' s.add_dependency 'rack-protection', version - s.add_dependency 'mustermann', '~> 3.0' + s.add_dependency 'tilt', '~> 2.0' s.add_development_dependency 'rack-test', '~> 2' end