mirror of
				https://github.com/sinatra/sinatra
				synced 2023-03-27 23:18:01 -04:00 
			
		
		
		
	Setup Rubocop (#1537)
* Initialize rubocop * Style/StringLiterals: prefer single quotes * Style/AndOr: use `&&` and `||`, instead of `and` and `or` * Style/HashSyntax: use new hash syntax * Layout/EmptyLineAfterGuardClause: add empty lines after guard clause * Style/SingleLineMethods: temporary disable It breaks layout of the code, it is better to fix it manually * Style/Proc: prefer `proc` vs `Proc.new` * Disable Lint/AmbiguousBlockAssociation It affects proc definitions for sinatra DSL * Disable Style/CaseEquality * Lint/UnusedBlockArgument: put underscore in front of it * Style/Alias: prefer alias vs alias_method in a class body * Layout/EmptyLineBetweenDefs: add empty lines between defs * Style/ParallelAssignment: don't use parallel assigment * Style/RegexpLiteral: prefer %r for regular expressions * Naming/UncommunicativeMethodParamName: fix abbrevs * Style/PerlBackrefs: disable cop * Layout/SpaceAfterComma: add missing spaces * Style/Documentation: disable cop * Style/FrozenStringLiteralComment: add frozen_string_literal * Layout/AlignHash: align hash * Layout/ExtraSpacing: allow for alignment * Layout/SpaceAroundOperators: add missing spaces * Style/Not: prefer `!` instead of `not` * Style/GuardClause: add guard conditions * Style/MutableConstant: freeze contants * Lint/IneffectiveAccessModifier: disable cop * Lint/RescueException: disable cop * Style/SpecialGlobalVars: disable cop * Layout/DotPosition: fix position of dot for multiline method chains * Layout/SpaceInsideArrayLiteralBrackets: don't use spaces inside arrays * Layout/SpaceInsideBlockBraces: add space for blocks * Layout/SpaceInsideHashLiteralBraces: add spaces for hashes * Style/FormatString: use format string syntax * Style/StderrPuts: `warn` is preferable to `$stderr.puts` * Bundler/DuplicatedGem: disable cop * Layout/AlignArray: fix warning * Lint/AssignmentInCondition: remove assignments from conditions * Layout/IndentHeredoc: disable cop * Layout/SpaceInsideParens: remove extra spaces * Lint/UnusedMethodArgument: put underscore in front of unused arg * Naming/RescuedExceptionsVariableName: use `e` for exceptions * Style/CommentedKeyword: put comments before the method * Style/FormatStringToken: disable cop * Style/MultilineIfModifier: move condition before the method * Style/SignalException: prefer `raise` to `fail` * Style/SymbolArray: prefer %i for array of symbols * Gemspec/OrderedDependencies: Use alphabetical order for dependencies * Lint/UselessAccessModifier: disable cop * Naming/HeredocDelimiterNaming: change delimiter's name * Style/ClassCheck: prefer `is_a?` to `kind_of?` * Style/ClassVars: disable cop * Style/Encoding: remove coding comment * Style/RedundantParentheses: remove extra parentheses * Style/StringLiteralsInInterpolation: prefer singl quotes * Layout/AlignArguments: fix alignment * Layout/ClosingHeredocIndentation: align heredoc * Layout/EmptyLineAfterMagicComment: add empty line * Set RubyVersion for rubocop * Lint/UselessAssignment: disable cop * Style/EmptyLiteral: disable cop Causes test failures * Minor code-style fixes with --safe-auto-correct option * Disable the rest of the cops that cause warnings It would be easier to re-enable them in separate PRs * Add rubocop check to the default Rake task * Update to rubocop 1.32.0 * Rubocop updates for rack-protection and sinatra-contrib * Disable Style/SlicingWithRange cop * Make suggested updates Co-authored-by: Jordan Owens <jkowens@gmail.com>
This commit is contained in:
		
							parent
							
								
									9a85bbfcb2
								
							
						
					
					
						commit
						8ae87a87f3
					
				
					 93 changed files with 1600 additions and 1142 deletions
				
			
		
							
								
								
									
										151
									
								
								.rubocop.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								.rubocop.yml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								Gemfile
									
										
									
									
									
								
							
							
						
						
									
										48
									
								
								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]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										123
									
								
								Rakefile
									
										
									
									
									
								
							
							
						
						
									
										123
									
								
								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</tt>[^\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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rainbows'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -8,7 +10,7 @@ module Rack
 | 
			
		|||
          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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
#!/usr/bin/env ruby -I ../lib -I lib
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra'
 | 
			
		||||
get('/') { 'this is a simple app' }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# this example does *not* work properly with WEBrick
 | 
			
		||||
#
 | 
			
		||||
# run *one* of these:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/main'
 | 
			
		||||
 | 
			
		||||
enable :inline_templates
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
				
			
			@ -79,7 +79,7 @@ module Sinatra
 | 
			
		|||
      super(convert_key(key), convert_value(value))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    alias_method :store, :[]=
 | 
			
		||||
    alias store []=
 | 
			
		||||
 | 
			
		||||
    def key(value)
 | 
			
		||||
      super(convert_value(value))
 | 
			
		||||
| 
						 | 
				
			
			@ -89,20 +89,21 @@ module Sinatra
 | 
			
		|||
      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 @@ module Sinatra
 | 
			
		|||
      self
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    alias_method :update, :merge!
 | 
			
		||||
    alias update merge!
 | 
			
		||||
 | 
			
		||||
    def merge(*other_hashes, &block)
 | 
			
		||||
      dup.merge!(*other_hashes, &block)
 | 
			
		||||
| 
						 | 
				
			
			@ -171,17 +172,19 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ module Sinatra
 | 
			
		|||
    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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    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:
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
| 
						 | 
				
			
			@ -357,6 +359,6 @@ enabled the <code>show_exceptions</code> setting.</p>
 | 
			
		|||
  </div> <!-- /WRAP -->
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
HTML
 | 
			
		||||
    HTML
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
  VERSION = '3.0.0'
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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: '..'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
require "rack/protection"
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection/version'
 | 
			
		||||
require 'rack'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +31,7 @@ module Rack
 | 
			
		|||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
require 'securerandom'
 | 
			
		||||
require 'openssl'
 | 
			
		||||
| 
						 | 
				
			
			@ -95,12 +97,12 @@ module Rack
 | 
			
		|||
    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 @@ module Rack
 | 
			
		|||
        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 @@ module Rack
 | 
			
		|||
        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 @@ module Rack
 | 
			
		|||
        # 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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      def token_hmac(session, identifier)
 | 
			
		||||
        OpenSSL::HMAC.digest(
 | 
			
		||||
          OpenSSL::Digest::SHA256.new,
 | 
			
		||||
          OpenSSL::Digest.new('SHA256'),
 | 
			
		||||
          real_token(session),
 | 
			
		||||
          identifier
 | 
			
		||||
        )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ module Rack
 | 
			
		|||
      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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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 @@ module Rack
 | 
			
		|||
      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 @@ module Rack
 | 
			
		|||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -38,16 +39,16 @@ module Rack
 | 
			
		|||
    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 = []
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
require 'pathname'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,9 +31,8 @@ module Rack
 | 
			
		|||
        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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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 @@ module Rack
 | 
			
		|||
      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 @@ module Rack
 | 
			
		|||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'openssl'
 | 
			
		||||
require 'zlib'
 | 
			
		||||
require 'json'
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +69,7 @@ module Rack
 | 
			
		|||
        end
 | 
			
		||||
 | 
			
		||||
        def decode(str)
 | 
			
		||||
          str.unpack('m').first
 | 
			
		||||
          str.unpack1('m')
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # Encode session cookies as Marshaled Base64 data
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +80,12 @@ module Rack
 | 
			
		|||
 | 
			
		||||
          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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
          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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
          def decode(str)
 | 
			
		||||
            return unless str
 | 
			
		||||
 | 
			
		||||
            ::JSON.parse(Zlib::Inflate.inflate(super(str)))
 | 
			
		||||
          rescue
 | 
			
		||||
          rescue StandardError
 | 
			
		||||
            nil
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
| 
						 | 
				
			
			@ -127,12 +140,12 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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 @@ module Rack
 | 
			
		|||
        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 @@ module Rack
 | 
			
		|||
        # 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 @@ module Rack
 | 
			
		|||
        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 @@ module Rack
 | 
			
		|||
        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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ module Rack
 | 
			
		|||
        decrypted_data = cipher.update(cipher_text)
 | 
			
		||||
        decrypted_data << cipher.final
 | 
			
		||||
        decrypted_data
 | 
			
		||||
 | 
			
		||||
      rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
 | 
			
		||||
        nil
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
require 'rack/utils'
 | 
			
		||||
require 'tempfile'
 | 
			
		||||
| 
						 | 
				
			
			@ -29,8 +31,8 @@ module Rack
 | 
			
		|||
        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 @@ module Rack
 | 
			
		|||
        @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 @@ module Rack
 | 
			
		|||
        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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -16,7 +18,7 @@ module Rack
 | 
			
		|||
    # 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +19,7 @@ module Rack
 | 
			
		|||
    #                 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +21,7 @@ module Rack
 | 
			
		|||
    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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -13,9 +15,11 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +19,7 @@ module Rack
 | 
			
		|||
    #
 | 
			
		||||
    # 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 @@ module Rack
 | 
			
		|||
 | 
			
		||||
      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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -11,11 +13,11 @@ module Rack
 | 
			
		|||
    # Thus <tt>GET /foo/%2e%2e%2fbar</tt> becomes <tt>GET /bar</tt>.
 | 
			
		||||
    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 @@ module Rack
 | 
			
		|||
        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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +15,7 @@ module Rack
 | 
			
		|||
    # 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -13,14 +15,14 @@ module Rack
 | 
			
		|||
    # 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]) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -18,11 +20,11 @@ module Rack
 | 
			
		|||
    # 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  module Protection
 | 
			
		||||
    VERSION = '3.0.0'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +14,7 @@ module Rack
 | 
			
		|||
    # 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								rack-protection/lib/rack_protection.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								rack-protection/lib/rack_protection.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rack/protection'
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ RSpec.describe Rack::Protection::AuthenticityToken do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::AuthenticityToken do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::AuthenticityToken do
 | 
			
		|||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,37 +1,38 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
RSpec.describe Rack::Protection::Base do
 | 
			
		||||
  subject { described_class.new(-> {}) }
 | 
			
		||||
 | 
			
		||||
  subject { described_class.new(lambda {}) }
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.%2573ession=; domain=example.org; path=/some/path; expires=Thu, 01 Jan 1970
 | 
			
		|||
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 @@ END
 | 
			
		|||
  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 @@ END
 | 
			
		|||
  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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
      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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
      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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
  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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
  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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
  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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
  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 @@ RSpec.describe Rack::Protection::EncryptedCookie do
 | 
			
		|||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 => "<bar>"
 | 
			
		||||
      get '/', foo: '<bar>'
 | 
			
		||||
      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 => "<bar>"}
 | 
			
		||||
      get '/', foo: { bar: '<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 @@ RSpec.describe Rack::Protection::EscapedParams do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection::EscapedParams do
 | 
			
		|||
        temp_file.write('hello world')
 | 
			
		||||
        temp_file.close
 | 
			
		||||
 | 
			
		||||
        post '/', :file => Rack::Test::UploadedFile.new(temp_file.path), :other => '<bar>'
 | 
			
		||||
        post '/', file: Rack::Test::UploadedFile.new(temp_file.path), other: '<bar>'
 | 
			
		||||
        expect(body).to eq("_escaped_params_tmp_file\nhello world\n<bar>")
 | 
			
		||||
      ensure
 | 
			
		||||
        File.unlink(temp_file.path)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ RSpec.describe Rack::Protection::HttpOrigin do
 | 
			
		|||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ RSpec.describe Rack::Protection::IPSpoofing do
 | 
			
		|||
 | 
			
		||||
  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 @@ RSpec.describe Rack::Protection::IPSpoofing do
 | 
			
		|||
 | 
			
		||||
  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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ RSpec.describe Rack::Protection::JsonCsrf do
 | 
			
		|||
 | 
			
		||||
    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 @@ RSpec.describe Rack::Protection::JsonCsrf do
 | 
			
		|||
      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 @@ RSpec.describe Rack::Protection::JsonCsrf do
 | 
			
		|||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ RSpec.describe Rack::Protection::PathTraversal do
 | 
			
		|||
 | 
			
		||||
  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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ RSpec.describe Rack::Protection do
 | 
			
		|||
 | 
			
		||||
  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 @@ RSpec.describe Rack::Protection do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection do
 | 
			
		|||
      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 @@ RSpec.describe Rack::Protection do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection do
 | 
			
		|||
    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 @@ RSpec.describe Rack::Protection do
 | 
			
		|||
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,28 +1,30 @@
 | 
			
		|||
RSpec.describe Rack::Protection::RemoteReferrer do
 | 
			
		||||
  it_behaves_like "any rack application"
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
  it "accepts post requests with no referrer" do
 | 
			
		||||
RSpec.describe Rack::Protection::RemoteReferrer do
 | 
			
		||||
  it_behaves_like 'any rack application'
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,30 +1,32 @@
 | 
			
		|||
RSpec.describe Rack::Protection::SessionHijacking do
 | 
			
		||||
  it_behaves_like "any rack application"
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
  it "accepts a session without changes to tracked parameters" do
 | 
			
		||||
    session = {:foo => :bar}
 | 
			
		||||
RSpec.describe Rack::Protection::SessionHijacking do
 | 
			
		||||
  it_behaves_like 'any rack application'
 | 
			
		||||
 | 
			
		||||
  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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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`.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
if version == "1.3"
 | 
			
		||||
version = if defined? Gem.loaded_specs&.include?('rack')
 | 
			
		||||
            Gem.loaded_specs['rack'].version.to_s
 | 
			
		||||
          else
 | 
			
		||||
            "#{Rack.release}.0"
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ RSpec.shared_examples_for 'any rack application' do
 | 
			
		|||
      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 @@ RSpec.shared_examples_for 'any rack application' do
 | 
			
		|||
 | 
			
		||||
    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 => '<bar>')).to be_ok
 | 
			
		||||
    expect(get('/..', foo: '<bar>')).to be_ok
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'allows passing on values in env' do
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +56,7 @@ RSpec.shared_examples_for 'any rack application' do
 | 
			
		|||
 | 
			
		||||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'forwardable'
 | 
			
		||||
 | 
			
		||||
module SpecHelpers
 | 
			
		||||
| 
						 | 
				
			
			@ -12,12 +14,12 @@ module SpecHelpers
 | 
			
		|||
  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,7 +29,7 @@ module SpecHelpers
 | 
			
		|||
  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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,17 +86,19 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@ require 'yaml'
 | 
			
		|||
require 'erb'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
 | 
			
		||||
  # = Sinatra::ConfigFile
 | 
			
		||||
  #
 | 
			
		||||
  # <tt>Sinatra::ConfigFile</tt> 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 @@ module Sinatra
 | 
			
		|||
        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 @@ module Sinatra
 | 
			
		|||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    class UnsupportedConfigType < Exception
 | 
			
		||||
    class UnsupportedConfigType < StandardError
 | 
			
		||||
      def message
 | 
			
		||||
        'Invalid config file type, use .yml, .yaml or .erb'
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,9 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/base'
 | 
			
		||||
require 'sinatra/capture'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
 | 
			
		||||
  # = Sinatra::ContentFor
 | 
			
		||||
  #
 | 
			
		||||
  # <tt>Sinatra::ContentFor</tt> is a set of helpers that allows you to capture
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +175,7 @@ module Sinatra
 | 
			
		|||
    # for <tt>:head</tt>.
 | 
			
		||||
    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|
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/contrib/setup'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,2 +1,4 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/contrib'
 | 
			
		||||
Sinatra.register Sinatra::Contrib::All
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
  module Contrib
 | 
			
		||||
    VERSION = '3.0.0'
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/base'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
| 
						 | 
				
			
			@ -65,15 +67,15 @@ module Sinatra
 | 
			
		|||
        @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 @@ module Sinatra
 | 
			
		|||
        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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
      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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
      def each_value(&block)
 | 
			
		||||
        return enum_for(__method__) unless block_given?
 | 
			
		||||
 | 
			
		||||
        to_hash.each_value(&block)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -145,16 +153,18 @@ module Sinatra
 | 
			
		|||
        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 @@ module Sinatra
 | 
			
		|||
        "<##{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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
      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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
      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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
      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 @@ module Sinatra
 | 
			
		|||
        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 @@ module Sinatra
 | 
			
		|||
      end
 | 
			
		||||
 | 
			
		||||
      def request_cookies
 | 
			
		||||
        @request.cookies.reject { |key, value| deleted.include? key }
 | 
			
		||||
        @request.cookies.reject { |key, _value| deleted.include? key }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
module Sinatra
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
  # = Sinatra::CustomLogger
 | 
			
		||||
  #
 | 
			
		||||
  # CustomLogger extension allows you to define your own logger instance
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/base'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +19,7 @@ module Sinatra
 | 
			
		|||
    # @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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    # @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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/base'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
 | 
			
		||||
  # = Sinatra::Extension
 | 
			
		||||
  #
 | 
			
		||||
  # <tt>Sinatra::Extension</tt> is a mixin that provides some syntactic sugar
 | 
			
		||||
| 
						 | 
				
			
			@ -81,13 +82,14 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/base'
 | 
			
		||||
require 'multi_json'
 | 
			
		||||
module Sinatra
 | 
			
		||||
 | 
			
		||||
  # = Sinatra::JSON
 | 
			
		||||
  #
 | 
			
		||||
  # <tt>Sinatra::JSON</tt> adds a helper method, called +json+, for (obviously)
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +96,7 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    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 @@ module Sinatra
 | 
			
		|||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
require 'sinatra/base'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
 | 
			
		||||
  # = Sinatra::LinkHeader
 | 
			
		||||
  #
 | 
			
		||||
  # <tt>Sinatra::LinkHeader</tt> adds a set of helper methods to generate link
 | 
			
		||||
| 
						 | 
				
			
			@ -86,8 +85,8 @@ module Sinatra
 | 
			
		|||
      opts[:rel]    = urls.shift unless urls.first.respond_to? :to_str
 | 
			
		||||
      options       = opts.map { |k, v| " #{k}=#{v.to_s.inspect}" }
 | 
			
		||||
      html_pattern  = "<link href=\"%s\"#{options.join} />"
 | 
			
		||||
      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 @@ module Sinatra
 | 
			
		|||
    #   %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)
 | 
			
		||||
        "<link href=\"#{url[1..-2]}\" #{opts.join " "} />"
 | 
			
		||||
        "<link href=\"#{url[1..-2]}\" #{opts.join ' '} />"
 | 
			
		||||
      end.join "\n"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def self.registered(base)
 | 
			
		||||
    def self.registered(_base)
 | 
			
		||||
      puts "WARNING: #{self} is a helpers module, not an extension."
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/base'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,9 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/base'
 | 
			
		||||
require 'mustermann'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
 | 
			
		||||
  # = Sinatra::Namespace
 | 
			
		||||
  #
 | 
			
		||||
  # <tt>Sinatra::Namespace</tt> 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 Sinatra
 | 
			
		|||
      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 @@ module Sinatra
 | 
			
		|||
      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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
      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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
      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 @@ module Sinatra
 | 
			
		|||
          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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ module Sinatra
 | 
			
		|||
        end
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ module Sinatra
 | 
			
		|||
      # 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 @@ module Sinatra
 | 
			
		|||
        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 @@ module Sinatra
 | 
			
		|||
          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 @@ module Sinatra
 | 
			
		|||
      # 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 @@ module Sinatra
 | 
			
		|||
        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 Sinatra
 | 
			
		|||
      # 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 @@ module Sinatra
 | 
			
		|||
        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 @@ module Sinatra
 | 
			
		|||
      # 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/base'
 | 
			
		||||
 | 
			
		||||
module Sinatra
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +62,7 @@ module Sinatra
 | 
			
		|||
        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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
      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 Sinatra
 | 
			
		|||
      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 @@ module Sinatra
 | 
			
		|||
            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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
        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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    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 @@ module Sinatra
 | 
			
		|||
      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 @@ module Sinatra
 | 
			
		|||
    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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ module Sinatra
 | 
			
		|||
 | 
			
		||||
    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 @@ module Sinatra
 | 
			
		|||
      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 @@ module Sinatra
 | 
			
		|||
    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 @@ module Sinatra
 | 
			
		|||
    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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ module Sinatra
 | 
			
		|||
    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 @@ module Sinatra
 | 
			
		|||
      def each
 | 
			
		||||
        # that way body.each.map { ... } works
 | 
			
		||||
        return self unless block_given?
 | 
			
		||||
 | 
			
		||||
        super
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +123,8 @@ module Sinatra
 | 
			
		|||
        @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 @@ module Sinatra
 | 
			
		|||
        data.to_s.bytesize
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      alias syswrite      write
 | 
			
		||||
      alias syswrite write
 | 
			
		||||
      alias write_nonblock write
 | 
			
		||||
 | 
			
		||||
      def print(*args)
 | 
			
		||||
| 
						 | 
				
			
			@ -154,7 +158,7 @@ module Sinatra
 | 
			
		|||
      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 @@ module Sinatra
 | 
			
		|||
        settings.default_encoding
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def closed?
 | 
			
		||||
        @closed
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def settings
 | 
			
		||||
        app.settings
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +184,7 @@ module Sinatra
 | 
			
		|||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'sinatra/base'
 | 
			
		||||
require 'rack'
 | 
			
		||||
begin
 | 
			
		||||
| 
						 | 
				
			
			@ -158,7 +160,7 @@ module Sinatra
 | 
			
		|||
      # @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 @@ module Sinatra
 | 
			
		|||
      # @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 @@ module Sinatra
 | 
			
		|||
    # @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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ module Sinatra
 | 
			
		|||
    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 @@ module Sinatra
 | 
			
		|||
        request_method == 'MOVE'
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      #def lock?
 | 
			
		||||
      # def lock?
 | 
			
		||||
      #  request_method == 'LOCK'
 | 
			
		||||
      #end
 | 
			
		||||
      # end
 | 
			
		||||
 | 
			
		||||
      def unlock?
 | 
			
		||||
        request_method == 'UNLOCK'
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +85,7 @@ module Sinatra
 | 
			
		|||
    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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ RSpec.describe Sinatra::Cookies do
 | 
			
		|||
    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")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -263,6 +263,7 @@ RSpec.describe Sinatra::Namespace do
 | 
			
		|||
 | 
			
		||||
          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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 '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 'rack', '~> 2.2', '>= 2.2.4'
 | 
			
		||||
  s.add_dependency 'rack-protection', version
 | 
			
		||||
  s.add_dependency 'tilt', '~> 2.0'
 | 
			
		||||
 | 
			
		||||
  s.add_development_dependency 'rack-test', '~> 2'
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue