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
|
@ -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'
|
||||
|
@ -19,7 +21,7 @@ module Rack
|
|||
#
|
||||
# It is not OOTB-compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem.
|
||||
# For that, the following patch needs to be applied:
|
||||
#
|
||||
#
|
||||
# Rack::Protection::AuthenticityToken.default_options(key: "csrf.token", authenticity_param: "_csrf")
|
||||
#
|
||||
# == Options
|
||||
|
@ -95,12 +97,12 @@ module 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)
|
||||
|
|
|
@ -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
|
||||
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)
|
||||
|
@ -20,4 +22,4 @@ module NotImplementedAsPending
|
|||
end
|
||||
|
||||
RSpec::Core::Example.send :include, self
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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,10 +29,10 @@ 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
|
||||
Thread.current[:last_env]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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…
Reference in New Issue